diff --git a/.github/workflows/linux-qt.yml b/.github/workflows/linux-qt.yml index 6848f203b..fc9755fa4 100644 --- a/.github/workflows/linux-qt.yml +++ b/.github/workflows/linux-qt.yml @@ -25,10 +25,10 @@ jobs: run: > sudo apt-get update && sudo apt install libx11-dev libxext-dev libwayland-dev libfuse2 clang build-essential qt6-base-dev qt6-tools-dev - - name: Cache CMake dependency source code + - name: Cache CMake Configuration uses: actions/cache@v4 env: - cache-name: ${{ runner.os }}-qt-cache-cmake-dependency-sources + cache-name: ${{ runner.os }}-qt-cache-cmake-configuration with: path: | ${{github.workspace}}/build @@ -36,16 +36,16 @@ jobs: restore-keys: | ${{ env.cache-name }}- - - name: Cache CMake dependency build objects + - name: Cache CMake Build uses: hendrikmuhs/ccache-action@v1.2.14 env: - cache-name: ${{ runner.os }}-qt-cache-cmake-dependency-builds + cache-name: ${{ runner.os }}-qt-cache-cmake-build with: append-timestamp: false key: ${{ env.cache-name }}-${{ hashFiles('**/CMakeLists.txt', 'cmake/**') }} - name: Configure CMake - run: cmake -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 + 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 diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index d4402472a..27bb8a401 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -25,10 +25,10 @@ jobs: run: > sudo apt-get update && sudo apt install libx11-dev libxext-dev libwayland-dev libfuse2 clang build-essential - - name: Cache CMake dependency source code + - name: Cache CMake Configuration uses: actions/cache@v4 env: - cache-name: ${{ runner.os }}-sdl-cache-cmake-dependency-sources + cache-name: ${{ runner.os }}-sdl-cache-cmake-configuration with: path: | ${{github.workspace}}/build @@ -36,16 +36,16 @@ jobs: restore-keys: | ${{ env.cache-name }}- - - name: Cache CMake dependency build objects + - name: Cache CMake Build uses: hendrikmuhs/ccache-action@v1.2.14 env: - cache-name: ${{ runner.os }}-sdl-cache-cmake-dependency-builds + cache-name: ${{ runner.os }}-sdl-cache-cmake-build with: append-timestamp: false key: ${{ env.cache-name }}-${{ hashFiles('**/CMakeLists.txt', 'cmake/**') }} - name: Configure CMake - run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache + run: cmake --fresh -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache - name: Build run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} --parallel diff --git a/.github/workflows/macos-qt.yml b/.github/workflows/macos-qt.yml index beb927a79..4cbfb9652 100644 --- a/.github/workflows/macos-qt.yml +++ b/.github/workflows/macos-qt.yml @@ -40,10 +40,10 @@ jobs: arch: clang_64 archives: qtbase qttools - - name: Cache CMake dependency source code + - name: Cache CMake Configuration uses: actions/cache@v4 env: - cache-name: ${{ runner.os }}-qt-cache-cmake-dependency-sources + cache-name: ${{ runner.os }}-qt-cache-cmake-configuration with: path: | ${{github.workspace}}/build @@ -51,10 +51,10 @@ jobs: restore-keys: | ${{ env.cache-name }}- - - name: Cache CMake dependency build objects + - name: Cache CMake Build uses: hendrikmuhs/ccache-action@v1.2.14 env: - cache-name: ${{runner.os}}-qt-cache-cmake-dependency-builds + cache-name: ${{runner.os}}-qt-cache-cmake-build with: append-timestamp: false create-symlink: true @@ -62,7 +62,7 @@ jobs: variant: sccache - name: Configure CMake - run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DCMAKE_OSX_ARCHITECTURES=x86_64 -DENABLE_QT_GUI=ON -DCMAKE_C_COMPILER_LAUNCHER=sccache -DCMAKE_CXX_COMPILER_LAUNCHER=sccache + run: cmake --fresh -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DCMAKE_OSX_ARCHITECTURES=x86_64 -DENABLE_QT_GUI=ON -DCMAKE_C_COMPILER_LAUNCHER=sccache -DCMAKE_CXX_COMPILER_LAUNCHER=sccache - name: Build run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} --parallel $(sysctl -n hw.ncpu) diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index 9526c6fd6..99c85b7b8 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -31,10 +31,10 @@ jobs: arch -x86_64 /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" arch -x86_64 /usr/local/bin/brew install molten-vk - - name: Cache CMake dependency source code + - name: Cache CMake Configuration uses: actions/cache@v4 env: - cache-name: ${{ runner.os }}-sdl-cache-cmake-dependency-sources + cache-name: ${{ runner.os }}-sdl-cache-cmake-configuration with: path: | ${{github.workspace}}/build @@ -42,10 +42,10 @@ jobs: restore-keys: | ${{ env.cache-name }}- - - name: Cache CMake dependency build objects + - name: Cache CMake Build uses: hendrikmuhs/ccache-action@v1.2.14 env: - cache-name: ${{runner.os}}-sdl-cache-cmake-dependency-builds + cache-name: ${{runner.os}}-sdl-cache-cmake-build with: append-timestamp: false create-symlink: true @@ -53,7 +53,7 @@ jobs: variant: sccache - name: Configure CMake - run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DCMAKE_OSX_ARCHITECTURES=x86_64 -DCMAKE_C_COMPILER_LAUNCHER=sccache -DCMAKE_CXX_COMPILER_LAUNCHER=sccache + run: cmake --fresh -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DCMAKE_OSX_ARCHITECTURES=x86_64 -DCMAKE_C_COMPILER_LAUNCHER=sccache -DCMAKE_CXX_COMPILER_LAUNCHER=sccache - name: Build run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} --parallel $(sysctl -n hw.ncpu) diff --git a/.github/workflows/windows-qt.yml b/.github/workflows/windows-qt.yml index fee202b5c..65faa7507 100644 --- a/.github/workflows/windows-qt.yml +++ b/.github/workflows/windows-qt.yml @@ -30,10 +30,10 @@ jobs: arch: win64_msvc2019_64 archives: qtbase qttools - - name: Cache CMake dependency source code + - name: Cache CMake Configuration uses: actions/cache@v4 env: - cache-name: ${{ runner.os }}-qt-cache-cmake-dependency-sources + cache-name: ${{ runner.os }}-qt-ninja-cache-cmake-configuration with: path: | ${{github.workspace}}/build @@ -41,8 +41,21 @@ jobs: restore-keys: | ${{ env.cache-name }}- + - name: Cache CMake Build + uses: hendrikmuhs/ccache-action@v1.2.14 + env: + cache-name: ${{ runner.os }}-qt-cache-cmake-build + with: + append-timestamp: false + key: ${{ env.cache-name }}-${{ hashFiles('**/CMakeLists.txt', 'cmake/**') }} + + - name: Setup VS Environment + uses: ilammy/msvc-dev-cmd@v1.13.0 + with: + arch: amd64 + - name: Configure CMake - run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -T ClangCL -DENABLE_QT_GUI=ON + run: cmake --fresh -G Ninja -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DENABLE_QT_GUI=ON -DCMAKE_C_COMPILER=clang-cl -DCMAKE_CXX_COMPILER=clang-cl -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache - name: Build run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} --parallel @@ -50,8 +63,8 @@ jobs: - name: Deploy run: | mkdir upload - move build/Release/shadPS4.exe upload - windeployqt --dir upload upload/shadPS4.exe + move build/shadPS4.exe upload + windeployqt --no-compiler-runtime --no-system-d3d-compiler --no-system-dxc-compiler --dir upload upload/shadPS4.exe - name: Get date and git hash id: vars diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index 4bea63b16..9c65a7444 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -20,10 +20,10 @@ jobs: with: submodules: recursive - - name: Cache CMake dependency source code + - name: Cache CMake Configuration uses: actions/cache@v4 env: - cache-name: ${{ runner.os }}-sdl-cache-cmake-dependency-sources + cache-name: ${{ runner.os }}-sdl-ninja-cache-cmake-configuration with: path: | ${{github.workspace}}/build @@ -31,8 +31,21 @@ jobs: restore-keys: | ${{ env.cache-name }}- + - name: Cache CMake Build + uses: hendrikmuhs/ccache-action@v1.2.14 + env: + cache-name: ${{ runner.os }}-sdl-cache-cmake-build + with: + append-timestamp: false + key: ${{ env.cache-name }}-${{ hashFiles('**/CMakeLists.txt', 'cmake/**') }} + + - name: Setup VS Environment + uses: ilammy/msvc-dev-cmd@v1.13.0 + with: + arch: amd64 + - name: Configure CMake - run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -T ClangCL + run: cmake --fresh -G Ninja -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DCMAKE_C_COMPILER=clang-cl -DCMAKE_CXX_COMPILER=clang-cl -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache - name: Build run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} --parallel @@ -49,4 +62,4 @@ jobs: with: name: shadps4-win64-sdl-${{ steps.vars.outputs.date }}-${{ steps.vars.outputs.shorthash }} path: | - ${{github.workspace}}/build/Release/shadPS4.exe + ${{github.workspace}}/build/shadPS4.exe diff --git a/.reuse/dep5 b/.reuse/dep5 index 0140c0c02..30a9065b7 100644 --- a/.reuse/dep5 +++ b/.reuse/dep5 @@ -58,3 +58,7 @@ License: MIT Files: externals/tracy/* Copyright: 2017-2024 Bartosz Taudul License: BSD-3-Clause + +Files: src/imgui/renderer/fonts/NotoSansJP-Regular.ttf +Copyright: 2012 Google Inc. All Rights Reserved. +License: OFL-1.1 diff --git a/CMakeLists.txt b/CMakeLists.txt index 29c467136..ce1e807f6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -232,11 +232,18 @@ set(SYSTEM_LIBS src/core/libraries/system/commondialog.cpp src/core/libraries/system/msgdialog_ui.cpp src/core/libraries/system/posix.cpp src/core/libraries/system/posix.h - src/core/libraries/save_data/error_codes.h + src/core/libraries/save_data/save_backup.cpp + src/core/libraries/save_data/save_backup.h + src/core/libraries/save_data/save_instance.cpp + src/core/libraries/save_data/save_instance.h + src/core/libraries/save_data/save_memory.cpp + src/core/libraries/save_data/save_memory.h src/core/libraries/save_data/savedata.cpp src/core/libraries/save_data/savedata.h - src/core/libraries/system/savedatadialog.cpp - src/core/libraries/system/savedatadialog.h + src/core/libraries/save_data/dialog/savedatadialog.cpp + src/core/libraries/save_data/dialog/savedatadialog.h + src/core/libraries/save_data/dialog/savedatadialog_ui.cpp + src/core/libraries/save_data/dialog/savedatadialog_ui.h src/core/libraries/system/sysmodule.cpp src/core/libraries/system/sysmodule.h src/core/libraries/system/systemservice.cpp @@ -349,9 +356,13 @@ set(COMMON src/common/logging/backend.cpp src/common/concepts.h src/common/config.cpp src/common/config.h + src/common/cstring.h src/common/debug.h src/common/decoder.cpp src/common/decoder.h + src/common/disassembler.cpp + src/common/disassembler.h + src/common/elf_info.h src/common/endian.h src/common/enum.h src/common/io_file.cpp @@ -468,6 +479,7 @@ set(SHADER_RECOMPILER src/shader_recompiler/exception.h src/shader_recompiler/params.h src/shader_recompiler/runtime_info.h src/shader_recompiler/specialization.h + src/shader_recompiler/backend/bindings.h src/shader_recompiler/backend/spirv/emit_spirv.cpp src/shader_recompiler/backend/spirv/emit_spirv.h src/shader_recompiler/backend/spirv/emit_spirv_atomic.cpp @@ -573,6 +585,8 @@ set(VIDEO_CORE src/video_core/amdgpu/liverpool.cpp src/video_core/renderer_vulkan/vk_master_semaphore.h src/video_core/renderer_vulkan/vk_pipeline_cache.cpp src/video_core/renderer_vulkan/vk_pipeline_cache.h + src/video_core/renderer_vulkan/vk_pipeline_common.cpp + src/video_core/renderer_vulkan/vk_pipeline_common.h src/video_core/renderer_vulkan/vk_platform.cpp src/video_core/renderer_vulkan/vk_platform.h src/video_core/renderer_vulkan/vk_rasterizer.cpp @@ -609,6 +623,7 @@ set(VIDEO_CORE src/video_core/amdgpu/liverpool.cpp set(IMGUI src/imgui/imgui_config.h src/imgui/imgui_layer.h src/imgui/imgui_std.h + src/imgui/imgui_texture.h src/imgui/layer/video_info.cpp src/imgui/layer/video_info.h src/imgui/renderer/imgui_core.cpp @@ -617,6 +632,8 @@ set(IMGUI src/imgui/imgui_config.h src/imgui/renderer/imgui_impl_sdl3.h src/imgui/renderer/imgui_impl_vulkan.cpp src/imgui/renderer/imgui_impl_vulkan.h + src/imgui/renderer/texture_manager.cpp + src/imgui/renderer/texture_manager.h ) set(INPUT src/input/controller.cpp @@ -798,6 +815,11 @@ add_subdirectory(${HOST_SHADERS_INCLUDE}) add_dependencies(shadps4 host_shaders) target_include_directories(shadps4 PRIVATE ${HOST_SHADERS_INCLUDE}) +# ImGui resources +add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/src/imgui/renderer) +add_dependencies(shadps4 ImGui_Resources) +target_include_directories(shadps4 PRIVATE ${IMGUI_RESOURCES_INCLUDE}) + if (ENABLE_QT_GUI) set_target_properties(shadps4 PROPERTIES # WIN32_EXECUTABLE ON diff --git a/LICENSES/OFL-1.1.txt b/LICENSES/OFL-1.1.txt new file mode 100644 index 000000000..6fe84ee21 --- /dev/null +++ b/LICENSES/OFL-1.1.txt @@ -0,0 +1,43 @@ +SIL OPEN FONT LICENSE + +Version 1.1 - 26 February 2007 + +PREAMBLE + +The goals of the Open Font License (OFL) are to stimulate worldwide development of collaborative font projects, to support the font creation efforts of academic and linguistic communities, and to provide a free and open framework in which fonts may be shared and improved in partnership with others. + +The OFL allows the licensed fonts to be used, studied, modified and redistributed freely as long as they are not sold by themselves. The fonts, including any derivative works, can be bundled, embedded, redistributed and/or sold with any software provided that any reserved names are not used by derivative works. The fonts and derivatives, however, cannot be released under any other type of license. The requirement for fonts to remain under this license does not apply to any document created using the fonts or their derivatives. + +DEFINITIONS + +"Font Software" refers to the set of files released by the Copyright Holder(s) under this license and clearly marked as such. This may include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the copyright statement(s). + +"Original Version" refers to the collection of Font Software components as distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, or substituting — in part or in whole — any of the components of the Original Version, by changing formats or by porting the Font Software to a new environment. + +"Author" refers to any designer, engineer, programmer, technical writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS + +Permission is hereby granted, free of charge, to any person obtaining a copy of the Font Software, to use, study, copy, merge, embed, modify, redistribute, and sell modified and unmodified copies of the Font Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, redistributed and/or sold with any software, provided that each copy contains the above copyright notice and this license. These can be included either as stand-alone text files, human-readable headers or in the appropriate machine-readable metadata fields within text or binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font Name(s) unless explicit written permission is granted by the corresponding Copyright Holder. This restriction only applies to the primary font name as presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font Software shall not be used to promote, endorse or advertise any Modified Version, except to acknowledge the contribution(s) of the Copyright Holder(s) and the Author(s) or with their explicit written permission. + +5) The Font Software, modified or unmodified, in part or in whole, must be distributed entirely under this license, and must not be distributed under any other license. The requirement for fonts to remain under this license does not apply to any document created using the Font Software. + +TERMINATION + +This license becomes null and void if any of the above conditions are not met. + +DISCLAIMER + +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/README.md b/README.md index 2127a5791..1be14c4fa 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ SPDX-License-Identifier: GPL-2.0-or-later

- + diff --git a/scripts/file_formats/sfo.hexpat b/scripts/file_formats/sfo.hexpat new file mode 100644 index 000000000..cfc1f8789 --- /dev/null +++ b/scripts/file_formats/sfo.hexpat @@ -0,0 +1,52 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +import std.io; +import std.sys; + +struct Header { + u32 magic; + u32 version; + u32 key_table_offset; + u32 data_table_offset; + u32 index_table_entries; +}; + +struct KeyEntry { + char name[]; +} [[inline]]; + +struct DataEntry { + if (fmt == 0x0404) { + u32 int_value; + } else if(fmt == 0x0004) { + char bin_value[size]; + } else if(fmt == 0x0204) { + char str_value[size]; + } else { + std::warning("unknown fmt type"); + } +} [[inline]]; + +struct IndexEntry { + u16 key_offset; + u16 param_fmt; + u32 param_len; + u32 param_max_len; + u32 data_offset; +}; + +struct Entry { + u64 begin = $; + IndexEntry index; + KeyEntry key @ KeyTableOffset + index.key_offset; + DataEntry data @ DataTableOffset + index.data_offset; + u8 data_empty[index.param_max_len - index.param_len] @ DataTableOffset + index.data_offset + index.param_len; + $ = begin + sizeof(IndexEntry); +}; + +Header header @ 0; +std::assert(header.magic == 0x46535000, "Miss match magic"); +std::assert(header.version == 0x00000101, "Miss match version"); + +Entry list[header.index_table_entries] @ 0x14; \ No newline at end of file diff --git a/src/common/cstring.h b/src/common/cstring.h new file mode 100644 index 000000000..fb29443ee --- /dev/null +++ b/src/common/cstring.h @@ -0,0 +1,157 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include + +#include "assert.h" + +namespace Common { + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wtautological-undefined-compare" + +/** + * @brief A null-terminated string with a fixed maximum length + * This class is not meant to be used as a general-purpose string class + * It is meant to be used as `char[N]` where memory layout is fixed + * @tparam N Maximum length of the string + * @tparam T Type of character + */ +template +class CString { + T data[N]{}; + +public: + class Iterator; + + CString() = default; + + template + explicit CString(const CString& other) + requires(M <= N) + { + if (this == nullptr) { + return; + } + std::ranges::copy(other.begin(), other.end(), data); + } + + void FromString(const std::basic_string_view& str) { + if (this == nullptr) { + return; + } + size_t p = str.copy(data, N - 1); + data[p] = '\0'; + } + + void Zero() { + if (this == nullptr) { + return; + } + std::ranges::fill(data, 0); + } + + explicit(false) operator std::basic_string_view() const { + if (this == nullptr) { + return {}; + } + return std::basic_string_view{data}; + } + + explicit operator std::basic_string() const { + if (this == nullptr) { + return {}; + } + return std::basic_string{data}; + } + + std::basic_string to_string() const { + if (this == nullptr) { + return {}; + } + return std::basic_string{data}; + } + + std::basic_string_view to_view() const { + if (this == nullptr) { + return {}; + } + return std::basic_string_view{data}; + } + + char* begin() { + if (this == nullptr) { + return nullptr; + } + return data; + } + + const char* begin() const { + if (this == nullptr) { + return nullptr; + } + return data; + } + + char* end() { + if (this == nullptr) { + return nullptr; + } + return data + N; + } + + const char* end() const { + if (this == nullptr) { + return nullptr; + } + return data + N; + } + + T& operator[](size_t idx) { + return data[idx]; + } + + const T& operator[](size_t idx) const { + return data[idx]; + } + + class Iterator { + T* ptr; + T* end; + + public: + using difference_type = std::ptrdiff_t; + using value_type = T; + using pointer = T*; + using reference = T&; + using iterator_category = std::random_access_iterator_tag; + + Iterator() = default; + explicit Iterator(T* ptr) : ptr(ptr), end(ptr + N) {} + + Iterator& operator++() { + ++ptr; + return *this; + } + + Iterator operator++(int) { + Iterator tmp = *this; + ++ptr; + return tmp; + } + + operator T*() { + ASSERT_MSG(ptr >= end, "CString iterator out of bounds"); + return ptr; + } + }; +}; + +static_assert(sizeof(CString<13>) == sizeof(char[13])); // Ensure size still matches a simple array +static_assert(std::weakly_incrementable::Iterator>); + +#pragma clang diagnostic pop + +} // namespace Common \ No newline at end of file diff --git a/src/common/elf_info.h b/src/common/elf_info.h new file mode 100644 index 000000000..5a2c914e0 --- /dev/null +++ b/src/common/elf_info.h @@ -0,0 +1,72 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include + +#include "assert.h" +#include "singleton.h" +#include "types.h" + +namespace Core { +class Emulator; +} + +namespace Common { + +class ElfInfo { + friend class Core::Emulator; + + bool initialized = false; + + std::string game_serial{}; + std::string title{}; + std::string app_ver{}; + u32 firmware_ver = 0; + u32 raw_firmware_ver = 0; + +public: + static constexpr u32 FW_15 = 0x1500000; + static constexpr u32 FW_16 = 0x1600000; + static constexpr u32 FW_17 = 0x1700000; + static constexpr u32 FW_20 = 0x2000000; + static constexpr u32 FW_25 = 0x2500000; + static constexpr u32 FW_30 = 0x3000000; + static constexpr u32 FW_40 = 0x4000000; + static constexpr u32 FW_45 = 0x4500000; + static constexpr u32 FW_50 = 0x5000000; + static constexpr u32 FW_80 = 0x8000000; + + static ElfInfo& Instance() { + return *Singleton::Instance(); + } + + [[nodiscard]] std::string_view GameSerial() const { + ASSERT(initialized); + return Instance().game_serial; + } + + [[nodiscard]] std::string_view Title() const { + ASSERT(initialized); + return title; + } + + [[nodiscard]] std::string_view AppVer() const { + ASSERT(initialized); + return app_ver; + } + + [[nodiscard]] u32 FirmwareVer() const { + ASSERT(initialized); + return firmware_ver; + } + + [[nodiscard]] u32 RawFirmwareVer() const { + ASSERT(initialized); + return raw_firmware_ver; + } +}; + +} // namespace Common diff --git a/src/common/io_file.cpp b/src/common/io_file.cpp index fbc37a10c..c1d9cc592 100644 --- a/src/common/io_file.cpp +++ b/src/common/io_file.cpp @@ -396,4 +396,18 @@ s64 IOFile::Tell() const { return ftello(file); } +u64 GetDirectorySize(const std::filesystem::path& path) { + if (!fs::exists(path)) { + return 0; + } + + u64 total = 0; + for (const auto& entry : fs::recursive_directory_iterator(path)) { + if (fs::is_regular_file(entry.path())) { + total += fs::file_size(entry.path()); + } + } + return total; +} + } // namespace Common::FS diff --git a/src/common/io_file.h b/src/common/io_file.h index 2c3df3f69..177bddbad 100644 --- a/src/common/io_file.h +++ b/src/common/io_file.h @@ -219,4 +219,6 @@ private: uintptr_t file_mapping = 0; }; +u64 GetDirectorySize(const std::filesystem::path& path); + } // namespace Common::FS diff --git a/src/common/logging/backend.cpp b/src/common/logging/backend.cpp index a21af8bba..7802977f5 100644 --- a/src/common/logging/backend.cpp +++ b/src/common/logging/backend.cpp @@ -144,6 +144,10 @@ public: initialization_in_progress_suppress_logging = false; } + static bool IsActive() { + return instance != nullptr; + } + static void Start() { instance->StartBackendThread(); } @@ -275,6 +279,10 @@ void Initialize(std::string_view log_file) { Impl::Initialize(log_file.empty() ? LOG_FILE : log_file); } +bool IsActive() { + return Impl::IsActive(); +} + void Start() { Impl::Start(); } diff --git a/src/common/logging/backend.h b/src/common/logging/backend.h index 91c9da832..a1ad66369 100644 --- a/src/common/logging/backend.h +++ b/src/common/logging/backend.h @@ -13,6 +13,8 @@ class Filter; /// Initializes the logging system. This should be the first thing called in main. void Initialize(std::string_view log_file = ""); +bool IsActive(); + /// Starts the logging threads. void Start(); diff --git a/src/common/string_util.cpp b/src/common/string_util.cpp index 29e6aeb4f..6d5a254cd 100644 --- a/src/common/string_util.cpp +++ b/src/common/string_util.cpp @@ -14,12 +14,17 @@ namespace Common { -std::string ToLower(std::string str) { - std::transform(str.begin(), str.end(), str.begin(), - [](unsigned char c) { return static_cast(std::tolower(c)); }); +std::string ToLower(std::string_view input) { + std::string str; + str.resize(input.size()); + std::ranges::transform(input, str.begin(), tolower); return str; } +void ToLowerInPlace(std::string& str) { + std::ranges::transform(str, str.begin(), tolower); +} + std::vector SplitString(const std::string& str, char delimiter) { std::istringstream iss(str); std::vector output(1); diff --git a/src/common/string_util.h b/src/common/string_util.h index 8dae6c75b..23e82b93c 100644 --- a/src/common/string_util.h +++ b/src/common/string_util.h @@ -10,7 +10,9 @@ namespace Common { /// Make a string lowercase -[[nodiscard]] std::string ToLower(std::string str); +[[nodiscard]] std::string ToLower(std::string_view str); + +void ToLowerInPlace(std::string& str); std::vector SplitString(const std::string& str, char delimiter); diff --git a/src/common/thread.cpp b/src/common/thread.cpp index f08b36faa..46df68c38 100644 --- a/src/common/thread.cpp +++ b/src/common/thread.cpp @@ -3,12 +3,15 @@ // SPDX-License-Identifier: GPL-2.0-or-later #include +#include #include "common/error.h" #include "common/logging/log.h" #include "common/thread.h" +#include "ntapi.h" #ifdef __APPLE__ #include +#include #include #elif defined(_WIN32) #include @@ -31,6 +34,48 @@ namespace Common { +#ifdef __APPLE__ + +void SetCurrentThreadRealtime(const std::chrono::nanoseconds period_ns) { + // CPU time to grant. + const std::chrono::nanoseconds computation_ns = period_ns / 2; + + // Determine the timebase for converting time to ticks. + struct mach_timebase_info timebase {}; + mach_timebase_info(&timebase); + const auto ticks_per_ns = + static_cast(timebase.denom) / static_cast(timebase.numer); + + const auto period_ticks = + static_cast(static_cast(period_ns.count()) * ticks_per_ns); + const auto computation_ticks = + static_cast(static_cast(computation_ns.count()) * ticks_per_ns); + + thread_time_constraint_policy policy = { + .period = period_ticks, + .computation = computation_ticks, + // Should not matter since preemptible is false, but needs to be >= computation regardless. + .constraint = computation_ticks, + .preemptible = false, + }; + + int ret = thread_policy_set( + pthread_mach_thread_np(pthread_self()), THREAD_TIME_CONSTRAINT_POLICY, + reinterpret_cast(&policy), THREAD_TIME_CONSTRAINT_POLICY_COUNT); + if (ret != KERN_SUCCESS) { + LOG_ERROR(Common, "Could not set thread to real-time with period {} ns: {}", + period_ns.count(), ret); + } +} + +#else + +void SetCurrentThreadRealtime(const std::chrono::nanoseconds period_ns) { + // Not implemented +} + +#endif + #ifdef _WIN32 void SetCurrentThreadPriority(ThreadPriority new_priority) { @@ -59,6 +104,16 @@ void SetCurrentThreadPriority(ThreadPriority new_priority) { SetThreadPriority(handle, windows_priority); } +static void AccurateSleep(std::chrono::nanoseconds duration) { + LARGE_INTEGER interval{ + .QuadPart = -1 * (duration.count() / 100u), + }; + HANDLE timer = ::CreateWaitableTimer(NULL, TRUE, NULL); + SetWaitableTimer(timer, &interval, 0, NULL, NULL, 0); + WaitForSingleObject(timer, INFINITE); + ::CloseHandle(timer); +} + #else void SetCurrentThreadPriority(ThreadPriority new_priority) { @@ -79,6 +134,10 @@ void SetCurrentThreadPriority(ThreadPriority new_priority) { pthread_setschedparam(this_thread, scheduling_type, ¶ms); } +static void AccurateSleep(std::chrono::nanoseconds duration) { + std::this_thread::sleep_for(duration); +} + #endif #ifdef _MSC_VER @@ -121,4 +180,22 @@ void SetCurrentThreadName(const char*) { #endif +AccurateTimer::AccurateTimer(std::chrono::nanoseconds target_interval) + : target_interval(target_interval) {} + +void AccurateTimer::Start() { + auto begin_sleep = std::chrono::high_resolution_clock::now(); + if (total_wait.count() > 0) { + AccurateSleep(total_wait); + } + start_time = std::chrono::high_resolution_clock::now(); + total_wait -= std::chrono::duration_cast(start_time - begin_sleep); +} + +void AccurateTimer::End() { + auto now = std::chrono::high_resolution_clock::now(); + total_wait += + target_interval - std::chrono::duration_cast(now - start_time); +} + } // namespace Common diff --git a/src/common/thread.h b/src/common/thread.h index 39acc1db5..fd962f8e5 100644 --- a/src/common/thread.h +++ b/src/common/thread.h @@ -4,6 +4,7 @@ #pragma once +#include #include "common/types.h" namespace Common { @@ -16,8 +17,24 @@ enum class ThreadPriority : u32 { Critical = 4, }; +void SetCurrentThreadRealtime(std::chrono::nanoseconds period_ns); + void SetCurrentThreadPriority(ThreadPriority new_priority); void SetCurrentThreadName(const char* name); +class AccurateTimer { + std::chrono::nanoseconds target_interval{}; + std::chrono::nanoseconds total_wait{}; + + std::chrono::high_resolution_clock::time_point start_time; + +public: + explicit AccurateTimer(std::chrono::nanoseconds target_interval); + + void Start(); + + void End(); +}; + } // namespace Common diff --git a/src/common/version.h b/src/common/version.h index 80de187b0..12fd17041 100644 --- a/src/common/version.h +++ b/src/common/version.h @@ -8,7 +8,7 @@ namespace Common { -constexpr char VERSION[] = "0.2.1 WIP"; +constexpr char VERSION[] = "0.3.1 WIP"; constexpr bool isRelease = false; } // namespace Common diff --git a/src/core/file_format/psf.cpp b/src/core/file_format/psf.cpp index 3d076acdc..1df5d430e 100644 --- a/src/core/file_format/psf.cpp +++ b/src/core/file_format/psf.cpp @@ -2,61 +2,275 @@ // SPDX-License-Identifier: GPL-2.0-or-later #include + +#include "common/assert.h" #include "common/io_file.h" +#include "common/logging/log.h" #include "core/file_format/psf.h" -PSF::PSF() = default; +static const std::unordered_map psf_known_max_sizes = { + {"ACCOUNT_ID", 8}, {"CATEGORY", 4}, {"DETAIL", 1024}, {"FORMAT", 4}, + {"MAINTITLE", 128}, {"PARAMS", 1024}, {"SAVEDATA_BLOCKS", 8}, {"SAVEDATA_DIRECTORY", 32}, + {"SUBTITLE", 128}, {"TITLE_ID", 12}, +}; +static inline u32 get_max_size(std::string_view key, u32 default_value) { + if (const auto& v = psf_known_max_sizes.find(key); v != psf_known_max_sizes.end()) { + return v->second; + } + return default_value; +} -PSF::~PSF() = default; - -bool PSF::open(const std::string& filepath, const std::vector& psfBuffer) { - if (!psfBuffer.empty()) { - psf.resize(psfBuffer.size()); - psf = psfBuffer; - } else { - Common::FS::IOFile file(filepath, Common::FS::FileAccessMode::Read); - if (!file.IsOpen()) { - return false; - } - - const u64 psfSize = file.GetSize(); - psf.resize(psfSize); - file.Seek(0); - file.Read(psf); - file.Close(); +bool PSF::Open(const std::filesystem::path& filepath) { + if (std::filesystem::exists(filepath)) { + last_write = std::filesystem::last_write_time(filepath); } - // Parse file contents - PSFHeader header; - std::memcpy(&header, psf.data(), sizeof(header)); - for (u32 i = 0; i < header.index_table_entries; i++) { - PSFEntry entry; - std::memcpy(&entry, &psf[sizeof(PSFHeader) + i * sizeof(PSFEntry)], sizeof(entry)); + Common::FS::IOFile file(filepath, Common::FS::FileAccessMode::Read); + if (!file.IsOpen()) { + return false; + } - const std::string key = (char*)&psf[header.key_table_offset + entry.key_offset]; - if (entry.param_fmt == PSFEntry::Fmt::TextRaw || - entry.param_fmt == PSFEntry::Fmt::TextNormal) { - map_strings[key] = (char*)&psf[header.data_table_offset + entry.data_offset]; - } - if (entry.param_fmt == PSFEntry::Fmt::Integer) { - u32 value; - std::memcpy(&value, &psf[header.data_table_offset + entry.data_offset], sizeof(value)); - map_integers[key] = value; + const u64 psfSize = file.GetSize(); + std::vector psf(psfSize); + file.Seek(0); + file.Read(psf); + file.Close(); + return Open(psf); +} + +bool PSF::Open(const std::vector& psf_buffer) { + const u8* psf_data = psf_buffer.data(); + + entry_list.clear(); + map_binaries.clear(); + map_strings.clear(); + map_integers.clear(); + + // Parse file contents + PSFHeader header{}; + std::memcpy(&header, psf_data, sizeof(header)); + + if (header.magic != PSF_MAGIC) { + LOG_ERROR(Core, "Invalid PSF magic number"); + return false; + } + if (header.version != PSF_VERSION_1_1 && header.version != PSF_VERSION_1_0) { + LOG_ERROR(Core, "Unsupported PSF version: 0x{:08x}", header.version); + return false; + } + + for (u32 i = 0; i < header.index_table_entries; i++) { + PSFRawEntry raw_entry{}; + std::memcpy(&raw_entry, psf_data + sizeof(PSFHeader) + i * sizeof(PSFRawEntry), + sizeof(raw_entry)); + + Entry& entry = entry_list.emplace_back(); + entry.key = std::string{(char*)(psf_data + header.key_table_offset + raw_entry.key_offset)}; + entry.param_fmt = static_cast(raw_entry.param_fmt.Raw()); + entry.max_len = raw_entry.param_max_len; + + const u8* data = psf_data + header.data_table_offset + raw_entry.data_offset; + + switch (entry.param_fmt) { + case PSFEntryFmt::Binary: { + std::vector value(raw_entry.param_len); + std::memcpy(value.data(), data, raw_entry.param_len); + map_binaries.emplace(i, std::move(value)); + } break; + case PSFEntryFmt::Text: { + std::string c_str{reinterpret_cast(data)}; + map_strings.emplace(i, std::move(c_str)); + } break; + case PSFEntryFmt::Integer: { + ASSERT_MSG(raw_entry.param_len == sizeof(s32), "PSF integer entry size mismatch"); + s32 integer = *(s32*)data; + map_integers.emplace(i, integer); + } break; + default: + UNREACHABLE_MSG("Unknown PSF entry format"); } } return true; } -std::string PSF::GetString(const std::string& key) { - if (map_strings.find(key) != map_strings.end()) { - return map_strings.at(key); +bool PSF::Encode(const std::filesystem::path& filepath) const { + Common::FS::IOFile file(filepath, Common::FS::FileAccessMode::Write); + if (!file.IsOpen()) { + return false; } - return ""; + + last_write = std::filesystem::file_time_type::clock::now(); + + const auto psf_buffer = Encode(); + return file.Write(psf_buffer) == psf_buffer.size(); } -u32 PSF::GetInteger(const std::string& key) { - if (map_integers.find(key) != map_integers.end()) { - return map_integers.at(key); - } - return -1; +std::vector PSF::Encode() const { + std::vector psf_buffer; + Encode(psf_buffer); + return psf_buffer; +} + +void PSF::Encode(std::vector& psf_buffer) const { + psf_buffer.resize(sizeof(PSFHeader) + sizeof(PSFRawEntry) * entry_list.size()); + + { + auto& header = *(PSFHeader*)psf_buffer.data(); + header.magic = PSF_MAGIC; + header.version = PSF_VERSION_1_1; + header.index_table_entries = entry_list.size(); + } + + const size_t key_table_offset = psf_buffer.size(); + ((PSFHeader*)psf_buffer.data())->key_table_offset = key_table_offset; + for (size_t i = 0; i < entry_list.size(); i++) { + auto& raw_entry = ((PSFRawEntry*)(psf_buffer.data() + sizeof(PSFHeader)))[i]; + const Entry& entry = entry_list[i]; + raw_entry.key_offset = psf_buffer.size() - key_table_offset; + raw_entry.param_fmt.FromRaw(static_cast(entry.param_fmt)); + raw_entry.param_max_len = entry.max_len; + std::ranges::copy(entry.key, std::back_inserter(psf_buffer)); + psf_buffer.push_back(0); // NULL terminator + } + + const size_t data_table_offset = psf_buffer.size(); + ((PSFHeader*)psf_buffer.data())->data_table_offset = data_table_offset; + for (size_t i = 0; i < entry_list.size(); i++) { + if (psf_buffer.size() % 4 != 0) { + std::ranges::fill_n(std::back_inserter(psf_buffer), 4 - psf_buffer.size() % 4, 0); + } + auto& raw_entry = ((PSFRawEntry*)(psf_buffer.data() + sizeof(PSFHeader)))[i]; + const Entry& entry = entry_list[i]; + raw_entry.data_offset = psf_buffer.size() - data_table_offset; + + s32 additional_padding = s32(raw_entry.param_max_len); + + switch (entry.param_fmt) { + case PSFEntryFmt::Binary: { + const auto& value = map_binaries.at(i); + raw_entry.param_len = value.size(); + additional_padding -= s32(raw_entry.param_len); + std::ranges::copy(value, std::back_inserter(psf_buffer)); + } break; + case PSFEntryFmt::Text: { + const auto& value = map_strings.at(i); + raw_entry.param_len = value.size() + 1; + additional_padding -= s32(raw_entry.param_len); + std::ranges::copy(value, std::back_inserter(psf_buffer)); + psf_buffer.push_back(0); // NULL terminator + } break; + case PSFEntryFmt::Integer: { + const auto& value = map_integers.at(i); + raw_entry.param_len = sizeof(s32); + additional_padding -= s32(raw_entry.param_len); + const auto value_bytes = reinterpret_cast(&value); + std::ranges::copy(value_bytes, value_bytes + sizeof(s32), + std::back_inserter(psf_buffer)); + } break; + default: + UNREACHABLE_MSG("Unknown PSF entry format"); + } + ASSERT_MSG(additional_padding >= 0, "PSF entry max size mismatch"); + std::ranges::fill_n(std::back_inserter(psf_buffer), additional_padding, 0); + } +} + +std::optional> PSF::GetBinary(std::string_view key) const { + const auto& [it, index] = FindEntry(key); + if (it == entry_list.end()) { + return {}; + } + ASSERT(it->param_fmt == PSFEntryFmt::Binary); + return std::span{map_binaries.at(index)}; +} + +std::optional PSF::GetString(std::string_view key) const { + const auto& [it, index] = FindEntry(key); + if (it == entry_list.end()) { + return {}; + } + ASSERT(it->param_fmt == PSFEntryFmt::Text); + return std::string_view{map_strings.at(index)}; +} + +std::optional PSF::GetInteger(std::string_view key) const { + const auto& [it, index] = FindEntry(key); + if (it == entry_list.end()) { + return {}; + } + ASSERT(it->param_fmt == PSFEntryFmt::Integer); + return map_integers.at(index); +} + +void PSF::AddBinary(std::string key, std::vector value, bool update) { + auto [it, index] = FindEntry(key); + bool exist = it != entry_list.end(); + if (exist && !update) { + LOG_ERROR(Core, "PSF: Tried to add binary key that already exists: {}", key); + return; + } + if (exist) { + ASSERT_MSG(it->param_fmt == PSFEntryFmt::Binary, "PSF: Change format is not supported"); + it->max_len = get_max_size(key, value.size()); + map_binaries.at(index) = std::move(value); + return; + } + Entry& entry = entry_list.emplace_back(); + entry.max_len = get_max_size(key, value.size()); + entry.key = std::move(key); + entry.param_fmt = PSFEntryFmt::Binary; + map_binaries.emplace(entry_list.size() - 1, std::move(value)); +} + +void PSF::AddString(std::string key, std::string value, bool update) { + auto [it, index] = FindEntry(key); + bool exist = it != entry_list.end(); + if (exist && !update) { + LOG_ERROR(Core, "PSF: Tried to add string key that already exists: {}", key); + return; + } + if (exist) { + ASSERT_MSG(it->param_fmt == PSFEntryFmt::Text, "PSF: Change format is not supported"); + it->max_len = get_max_size(key, value.size() + 1); + map_strings.at(index) = std::move(value); + return; + } + Entry& entry = entry_list.emplace_back(); + entry.max_len = get_max_size(key, value.size() + 1); + entry.key = std::move(key); + entry.param_fmt = PSFEntryFmt::Text; + map_strings.emplace(entry_list.size() - 1, std::move(value)); +} + +void PSF::AddInteger(std::string key, s32 value, bool update) { + auto [it, index] = FindEntry(key); + bool exist = it != entry_list.end(); + if (exist && !update) { + LOG_ERROR(Core, "PSF: Tried to add integer key that already exists: {}", key); + return; + } + if (exist) { + ASSERT_MSG(it->param_fmt == PSFEntryFmt::Integer, "PSF: Change format is not supported"); + it->max_len = sizeof(s32); + map_integers.at(index) = value; + return; + } + Entry& entry = entry_list.emplace_back(); + entry.key = std::move(key); + entry.param_fmt = PSFEntryFmt::Integer; + entry.max_len = sizeof(s32); + map_integers.emplace(entry_list.size() - 1, value); +} + +std::pair::iterator, size_t> PSF::FindEntry(std::string_view key) { + auto entry = + std::ranges::find_if(entry_list, [&](const auto& entry) { return entry.key == key; }); + return {entry, std::distance(entry_list.begin(), entry)}; +} + +std::pair::const_iterator, size_t> PSF::FindEntry( + std::string_view key) const { + auto entry = + std::ranges::find_if(entry_list, [&](const auto& entry) { return entry.key == key; }); + return {entry, std::distance(entry_list.begin(), entry)}; } diff --git a/src/core/file_format/psf.h b/src/core/file_format/psf.h index 9a162b1d5..d25b79eec 100644 --- a/src/core/file_format/psf.h +++ b/src/core/file_format/psf.h @@ -3,11 +3,18 @@ #pragma once +#include +#include #include +#include #include #include #include "common/endian.h" +constexpr u32 PSF_MAGIC = 0x00505346; +constexpr u32 PSF_VERSION_1_1 = 0x00000101; +constexpr u32 PSF_VERSION_1_0 = 0x00000100; + struct PSFHeader { u32_be magic; u32_le version; @@ -15,34 +22,72 @@ struct PSFHeader { u32_le data_table_offset; u32_le index_table_entries; }; +static_assert(sizeof(PSFHeader) == 0x14); -struct PSFEntry { - enum Fmt : u16 { - TextRaw = 0x0400, // String in UTF-8 format and not NULL terminated - TextNormal = 0x0402, // String in UTF-8 format and NULL terminated - Integer = 0x0404, // Unsigned 32-bit integer - }; - +struct PSFRawEntry { u16_le key_offset; u16_be param_fmt; u32_le param_len; u32_le param_max_len; u32_le data_offset; }; +static_assert(sizeof(PSFRawEntry) == 0x10); + +enum class PSFEntryFmt : u16 { + Binary = 0x0004, // Binary data + Text = 0x0204, // String in UTF-8 format and NULL terminated + Integer = 0x0404, // Signed 32-bit integer +}; class PSF { + struct Entry { + std::string key; + PSFEntryFmt param_fmt; + u32 max_len; + }; + public: - PSF(); - ~PSF(); + PSF() = default; + ~PSF() = default; - bool open(const std::string& filepath, const std::vector& psfBuffer); + PSF(const PSF& other) = default; + PSF(PSF&& other) noexcept = default; + PSF& operator=(const PSF& other) = default; + PSF& operator=(PSF&& other) noexcept = default; - std::string GetString(const std::string& key); - u32 GetInteger(const std::string& key); + bool Open(const std::filesystem::path& filepath); + bool Open(const std::vector& psf_buffer); - std::unordered_map map_strings; - std::unordered_map map_integers; + [[nodiscard]] std::vector Encode() const; + void Encode(std::vector& buf) const; + bool Encode(const std::filesystem::path& filepath) const; + + std::optional> GetBinary(std::string_view key) const; + std::optional GetString(std::string_view key) const; + std::optional GetInteger(std::string_view key) const; + + void AddBinary(std::string key, std::vector value, bool update = false); + void AddString(std::string key, std::string value, bool update = false); + void AddInteger(std::string key, s32 value, bool update = false); + + [[nodiscard]] std::filesystem::file_time_type GetLastWrite() const { + return last_write; + } + + [[nodiscard]] const std::vector& GetEntries() const { + return entry_list; + } private: - std::vector psf; + mutable std::filesystem::file_time_type last_write; + + std::vector entry_list; + + std::unordered_map> map_binaries; + std::unordered_map map_strings; + std::unordered_map map_integers; + + [[nodiscard]] std::pair::iterator, size_t> FindEntry(std::string_view key); + [[nodiscard]] std::pair::const_iterator, size_t> FindEntry( + std::string_view key) const; }; diff --git a/src/core/file_sys/fs.cpp b/src/core/file_sys/fs.cpp index 199e42a04..3b060dd83 100644 --- a/src/core/file_sys/fs.cpp +++ b/src/core/file_sys/fs.cpp @@ -9,12 +9,14 @@ namespace Core::FileSys { constexpr int RESERVED_HANDLES = 3; // First 3 handles are stdin,stdout,stderr -void MntPoints::Mount(const std::filesystem::path& host_folder, const std::string& guest_folder) { +void MntPoints::Mount(const std::filesystem::path& host_folder, const std::string& guest_folder, + bool read_only) { std::scoped_lock lock{m_mutex}; - m_mnt_pairs.emplace_back(host_folder, guest_folder); + m_mnt_pairs.emplace_back(host_folder, guest_folder, read_only); } void MntPoints::Unmount(const std::filesystem::path& host_folder, const std::string& guest_folder) { + std::scoped_lock lock{m_mutex}; auto it = std::remove_if(m_mnt_pairs.begin(), m_mnt_pairs.end(), [&](const MntPair& pair) { return pair.mount == guest_folder; }); m_mnt_pairs.erase(it, m_mnt_pairs.end()); @@ -25,7 +27,7 @@ void MntPoints::UnmountAll() { m_mnt_pairs.clear(); } -std::filesystem::path MntPoints::GetHostPath(std::string_view guest_directory) { +std::filesystem::path MntPoints::GetHostPath(std::string_view guest_directory, bool* is_read_only) { // Evil games like Turok2 pass double slashes e.g /app0//game.kpf std::string corrected_path(guest_directory); size_t pos = corrected_path.find("//"); @@ -39,6 +41,10 @@ std::filesystem::path MntPoints::GetHostPath(std::string_view guest_directory) { return ""; } + if (is_read_only) { + *is_read_only = mount->read_only; + } + // Nothing to do if getting the mount itself. if (corrected_path == mount->mount) { return mount->host_path; diff --git a/src/core/file_sys/fs.h b/src/core/file_sys/fs.h index 2c55b0513..eeaeaf781 100644 --- a/src/core/file_sys/fs.h +++ b/src/core/file_sys/fs.h @@ -22,18 +22,22 @@ public: struct MntPair { std::filesystem::path host_path; std::string mount; // e.g /app0/ + bool read_only; }; explicit MntPoints() = default; ~MntPoints() = default; - void Mount(const std::filesystem::path& host_folder, const std::string& guest_folder); + void Mount(const std::filesystem::path& host_folder, const std::string& guest_folder, + bool read_only = false); void Unmount(const std::filesystem::path& host_folder, const std::string& guest_folder); void UnmountAll(); - std::filesystem::path GetHostPath(std::string_view guest_directory); + std::filesystem::path GetHostPath(std::string_view guest_directory, + bool* is_read_only = nullptr); const MntPair* GetMount(const std::string& guest_path) { + std::scoped_lock lock{m_mutex}; const auto it = std::ranges::find_if( m_mnt_pairs, [&](const auto& mount) { return guest_path.starts_with(mount.mount); }); return it == m_mnt_pairs.end() ? nullptr : &*it; diff --git a/src/core/libraries/app_content/app_content.cpp b/src/core/libraries/app_content/app_content.cpp index 125d19684..754343eef 100644 --- a/src/core/libraries/app_content/app_content.cpp +++ b/src/core/libraries/app_content/app_content.cpp @@ -4,6 +4,7 @@ #include #include "app_content.h" +#include "common/assert.h" #include "common/io_file.h" #include "common/logging/log.h" #include "common/path_util.h" @@ -90,37 +91,32 @@ int PS4_SYSV_ABI sceAppContentAddcontUnmount() { return ORBIS_OK; } -int PS4_SYSV_ABI sceAppContentAppParamGetInt(OrbisAppContentAppParamId paramId, s32* value) { - if (value == nullptr) +int PS4_SYSV_ABI sceAppContentAppParamGetInt(OrbisAppContentAppParamId paramId, s32* out_value) { + if (out_value == nullptr) return ORBIS_APP_CONTENT_ERROR_PARAMETER; auto* param_sfo = Common::Singleton::Instance(); + std::optional value; switch (paramId) { case ORBIS_APP_CONTENT_APPPARAM_ID_SKU_FLAG: - *value = ORBIS_APP_CONTENT_APPPARAM_SKU_FLAG_FULL; + value = ORBIS_APP_CONTENT_APPPARAM_SKU_FLAG_FULL; break; case ORBIS_APP_CONTENT_APPPARAM_ID_USER_DEFINED_PARAM_1: - *value = param_sfo->GetInteger("USER_DEFINED_PARAM_1"); + value = param_sfo->GetInteger("USER_DEFINED_PARAM_1"); break; case ORBIS_APP_CONTENT_APPPARAM_ID_USER_DEFINED_PARAM_2: - *value = param_sfo->GetInteger("USER_DEFINED_PARAM_2"); + value = param_sfo->GetInteger("USER_DEFINED_PARAM_2"); break; case ORBIS_APP_CONTENT_APPPARAM_ID_USER_DEFINED_PARAM_3: - *value = param_sfo->GetInteger("USER_DEFINED_PARAM_3"); + value = param_sfo->GetInteger("USER_DEFINED_PARAM_3"); break; case ORBIS_APP_CONTENT_APPPARAM_ID_USER_DEFINED_PARAM_4: - *value = param_sfo->GetInteger("USER_DEFINED_PARAM_4"); + value = param_sfo->GetInteger("USER_DEFINED_PARAM_4"); break; default: - LOG_ERROR(Lib_AppContent, " paramId = {}, value = {} paramId is not valid", paramId, - *value); - return ORBIS_APP_CONTENT_ERROR_PARAMETER; - } - if (*value == -1) { - LOG_ERROR(Lib_AppContent, - " paramId = {}, value = {} value is not valid can't read param.sfo?", paramId, - *value); + LOG_ERROR(Lib_AppContent, " paramId = {} paramId is not valid", paramId); return ORBIS_APP_CONTENT_ERROR_PARAMETER; } + *out_value = value.value_or(0); return ORBIS_OK; } @@ -251,7 +247,11 @@ int PS4_SYSV_ABI sceAppContentInitialize(const OrbisAppContentInitParam* initPar auto* param_sfo = Common::Singleton::Instance(); const auto addons_dir = Common::FS::GetUserPath(Common::FS::PathType::AddonsDir); - title_id = param_sfo->GetString("TITLE_ID"); + if (const auto value = param_sfo->GetString("TITLE_ID"); value.has_value()) { + title_id = *value; + } else { + UNREACHABLE_MSG("Failed to get TITLE_ID"); + } auto addon_path = addons_dir / title_id; if (std::filesystem::exists(addon_path)) { for (const auto& entry : std::filesystem::directory_iterator(addon_path)) { diff --git a/src/core/libraries/avplayer/avplayer_source.cpp b/src/core/libraries/avplayer/avplayer_source.cpp index 51c37df04..fcae180e7 100644 --- a/src/core/libraries/avplayer/avplayer_source.cpp +++ b/src/core/libraries/avplayer/avplayer_source.cpp @@ -7,6 +7,8 @@ #include "common/alignment.h" #include "common/singleton.h" +#include "common/thread.h" + #include "core/file_sys/fs.h" #include "core/libraries/kernel/time_management.h" @@ -432,6 +434,8 @@ void AvPlayerSource::ReleaseAVFormatContext(AVFormatContext* context) { void AvPlayerSource::DemuxerThread(std::stop_token stop) { using namespace std::chrono; + Common::SetCurrentThreadName("shadPS4:AvDemuxer"); + if (!m_audio_stream_index.has_value() && !m_video_stream_index.has_value()) { LOG_WARNING(Lib_AvPlayer, "Could not start DEMUXER thread. No streams enabled."); return; @@ -499,7 +503,7 @@ void AvPlayerSource::DemuxerThread(std::stop_token stop) { } m_state.OnEOF(); - LOG_INFO(Lib_AvPlayer, "Demuxer Thread exited normaly"); + LOG_INFO(Lib_AvPlayer, "Demuxer Thread exited normally"); } AvPlayerSource::AVFramePtr AvPlayerSource::ConvertVideoFrame(const AVFrame& frame) { @@ -598,6 +602,8 @@ Frame AvPlayerSource::PrepareVideoFrame(FrameBuffer buffer, const AVFrame& frame void AvPlayerSource::VideoDecoderThread(std::stop_token stop) { using namespace std::chrono; + Common::SetCurrentThreadName("shadPS4:AvVideoDecoder"); + LOG_INFO(Lib_AvPlayer, "Video Decoder Thread started"); while ((!m_is_eof || m_video_packets.Size() != 0) && !stop.stop_requested()) { if (!m_video_packets_cv.Wait(stop, @@ -653,7 +659,7 @@ void AvPlayerSource::VideoDecoderThread(std::stop_token stop) { } } - LOG_INFO(Lib_AvPlayer, "Video Decoder Thread exited normaly"); + LOG_INFO(Lib_AvPlayer, "Video Decoder Thread exited normally"); } AvPlayerSource::AVFramePtr AvPlayerSource::ConvertAudioFrame(const AVFrame& frame) { @@ -718,6 +724,8 @@ Frame AvPlayerSource::PrepareAudioFrame(FrameBuffer buffer, const AVFrame& frame void AvPlayerSource::AudioDecoderThread(std::stop_token stop) { using namespace std::chrono; + Common::SetCurrentThreadName("shadPS4:AvAudioDecoder"); + LOG_INFO(Lib_AvPlayer, "Audio Decoder Thread started"); while ((!m_is_eof || m_audio_packets.Size() != 0) && !stop.stop_requested()) { if (!m_audio_packets_cv.Wait(stop, @@ -773,7 +781,7 @@ void AvPlayerSource::AudioDecoderThread(std::stop_token stop) { } } - LOG_INFO(Lib_AvPlayer, "Audio Decoder Thread exited normaly"); + LOG_INFO(Lib_AvPlayer, "Audio Decoder Thread exited normally"); } bool AvPlayerSource::HasRunningThreads() const { diff --git a/src/core/libraries/avplayer/avplayer_state.cpp b/src/core/libraries/avplayer/avplayer_state.cpp index a512063f2..654e04836 100644 --- a/src/core/libraries/avplayer/avplayer_state.cpp +++ b/src/core/libraries/avplayer/avplayer_state.cpp @@ -5,6 +5,8 @@ #include "avplayer_source.h" #include "avplayer_state.h" +#include "common/thread.h" + #include "core/libraries/error_codes.h" #include "core/libraries/kernel/time_management.h" @@ -178,6 +180,7 @@ bool AvPlayerState::Start() { void AvPlayerState::AvControllerThread(std::stop_token stop) { using std::chrono::milliseconds; + Common::SetCurrentThreadName("shadPS4:AvController"); while (!stop.stop_requested()) { if (m_event_queue.Size() != 0) { diff --git a/src/core/libraries/gnmdriver/gnmdriver.cpp b/src/core/libraries/gnmdriver/gnmdriver.cpp index f078550af..fd4c261e9 100644 --- a/src/core/libraries/gnmdriver/gnmdriver.cpp +++ b/src/core/libraries/gnmdriver/gnmdriver.cpp @@ -1272,8 +1272,12 @@ int PS4_SYSV_ABI sceGnmRequestMipStatsReportAndReset() { return ORBIS_OK; } -int PS4_SYSV_ABI sceGnmResetVgtControl() { - LOG_ERROR(Lib_GnmDriver, "(STUBBED) called"); +s32 PS4_SYSV_ABI sceGnmResetVgtControl(u32* cmdbuf, u32 size) { + LOG_TRACE(Lib_GnmDriver, "called"); + if (cmdbuf == nullptr || size != 3) { + return -1; + } + PM4CmdSetData::SetContextReg(cmdbuf, 0x2aau, 0xffu); // IA_MULTI_VGT_PARAM return ORBIS_OK; } diff --git a/src/core/libraries/gnmdriver/gnmdriver.h b/src/core/libraries/gnmdriver/gnmdriver.h index 754d488f8..55a70cbf3 100644 --- a/src/core/libraries/gnmdriver/gnmdriver.h +++ b/src/core/libraries/gnmdriver/gnmdriver.h @@ -134,7 +134,7 @@ s32 PS4_SYSV_ABI sceGnmRegisterResource(void* res_handle, void* owner_handle, co int PS4_SYSV_ABI sceGnmRequestFlipAndSubmitDone(); int PS4_SYSV_ABI sceGnmRequestFlipAndSubmitDoneForWorkload(); int PS4_SYSV_ABI sceGnmRequestMipStatsReportAndReset(); -int PS4_SYSV_ABI sceGnmResetVgtControl(); +s32 PS4_SYSV_ABI sceGnmResetVgtControl(u32* cmdbuf, u32 size); int PS4_SYSV_ABI sceGnmSdmaClose(); int PS4_SYSV_ABI sceGnmSdmaConstFill(); int PS4_SYSV_ABI sceGnmSdmaCopyLinear(); diff --git a/src/core/libraries/kernel/event_flag/event_flag.cpp b/src/core/libraries/kernel/event_flag/event_flag.cpp index 4d3925127..c85aa0d90 100644 --- a/src/core/libraries/kernel/event_flag/event_flag.cpp +++ b/src/core/libraries/kernel/event_flag/event_flag.cpp @@ -137,7 +137,7 @@ int PS4_SYSV_ABI sceKernelPollEventFlag(OrbisKernelEventFlag ef, u64 bitPattern, auto result = ef->Poll(bitPattern, wait, clear, pResultPat); - if (result != ORBIS_OK) { + if (result != ORBIS_OK && result != ORBIS_KERNEL_ERROR_EBUSY) { LOG_ERROR(Kernel_Event, "returned {}", result); } diff --git a/src/core/libraries/kernel/file_system.cpp b/src/core/libraries/kernel/file_system.cpp index ae2c6e2be..45ebb4be8 100644 --- a/src/core/libraries/kernel/file_system.cpp +++ b/src/core/libraries/kernel/file_system.cpp @@ -89,6 +89,8 @@ int PS4_SYSV_ABI sceKernelOpen(const char* path, int flags, u16 mode) { } // RW, then scekernelWrite is called and savedata is written just fine now. e = file->f.Open(file->m_host_name, Common::FS::FileAccessMode::ReadWrite); + } else if (write) { + e = file->f.Open(file->m_host_name, Common::FS::FileAccessMode::Write); } else { UNREACHABLE(); } @@ -179,11 +181,16 @@ int PS4_SYSV_ABI sceKernelUnlink(const char* path) { auto* h = Common::Singleton::Instance(); auto* mnt = Common::Singleton::Instance(); - const auto host_path = mnt->GetHostPath(path); + bool ro = false; + const auto host_path = mnt->GetHostPath(path, &ro); if (host_path.empty()) { return SCE_KERNEL_ERROR_EACCES; } + if (ro) { + return SCE_KERNEL_ERROR_EROFS; + } + if (std::filesystem::is_directory(host_path)) { return SCE_KERNEL_ERROR_EPERM; } @@ -270,11 +277,18 @@ int PS4_SYSV_ABI sceKernelMkdir(const char* path, u16 mode) { return SCE_KERNEL_ERROR_EINVAL; } auto* mnt = Common::Singleton::Instance(); - const auto dir_name = mnt->GetHostPath(path); + + bool ro = false; + const auto dir_name = mnt->GetHostPath(path, &ro); + if (std::filesystem::exists(dir_name)) { return SCE_KERNEL_ERROR_EEXIST; } + if (ro) { + return SCE_KERNEL_ERROR_EROFS; + } + // CUSA02456: path = /aotl after sceSaveDataMount(mode = 1) if (dir_name.empty() || !std::filesystem::create_directory(dir_name)) { return SCE_KERNEL_ERROR_EIO; @@ -299,7 +313,8 @@ int PS4_SYSV_ABI posix_mkdir(const char* path, u16 mode) { int PS4_SYSV_ABI sceKernelStat(const char* path, OrbisKernelStat* sb) { LOG_INFO(Kernel_Fs, "(PARTIAL) path = {}", path); auto* mnt = Common::Singleton::Instance(); - const auto path_name = mnt->GetHostPath(path); + bool ro = false; + const auto path_name = mnt->GetHostPath(path, &ro); std::memset(sb, 0, sizeof(OrbisKernelStat)); const bool is_dir = std::filesystem::is_directory(path_name); const bool is_file = std::filesystem::is_regular_file(path_name); @@ -319,6 +334,10 @@ int PS4_SYSV_ABI sceKernelStat(const char* path, OrbisKernelStat* sb) { sb->st_blocks = (sb->st_size + 511) / 512; // TODO incomplete } + if (ro) { + sb->st_mode &= ~0000555u; + } + return ORBIS_OK; } @@ -500,11 +519,18 @@ s64 PS4_SYSV_ABI sceKernelPwrite(int d, void* buf, size_t nbytes, s64 offset) { s32 PS4_SYSV_ABI sceKernelRename(const char* from, const char* to) { auto* mnt = Common::Singleton::Instance(); - const auto src_path = mnt->GetHostPath(from); + bool ro = false; + const auto src_path = mnt->GetHostPath(from, &ro); if (!std::filesystem::exists(src_path)) { return ORBIS_KERNEL_ERROR_ENOENT; } - const auto dst_path = mnt->GetHostPath(to); + if (ro) { + return SCE_KERNEL_ERROR_EROFS; + } + const auto dst_path = mnt->GetHostPath(to, &ro); + if (ro) { + return SCE_KERNEL_ERROR_EROFS; + } const bool src_is_dir = std::filesystem::is_directory(src_path); const bool dst_is_dir = std::filesystem::is_directory(dst_path); if (src_is_dir && !dst_is_dir) { diff --git a/src/core/libraries/kernel/libkernel.cpp b/src/core/libraries/kernel/libkernel.cpp index d56f4dc41..65d3dde14 100644 --- a/src/core/libraries/kernel/libkernel.cpp +++ b/src/core/libraries/kernel/libkernel.cpp @@ -8,6 +8,7 @@ #include "common/assert.h" #include "common/debug.h" +#include "common/elf_info.h" #include "common/logging/log.h" #include "common/polyfill_thread.h" #include "common/singleton.h" @@ -243,8 +244,7 @@ int PS4_SYSV_ABI sceKernelConvertUtcToLocaltime(time_t time, time_t* local_time, } int PS4_SYSV_ABI sceKernelGetCompiledSdkVersion(int* ver) { - auto* param_sfo = Common::Singleton::Instance(); - int version = param_sfo->GetInteger("SYSTEM_VER"); + int version = Common::ElfInfo::Instance().RawFirmwareVer(); LOG_INFO(Kernel, "returned system version = {:#x}", version); *ver = version; return (version > 0) ? ORBIS_OK : ORBIS_KERNEL_ERROR_EINVAL; @@ -425,6 +425,7 @@ void LibKernel_Register(Core::Loader::SymbolsResolver* sym) { LIB_FUNCTION("WFcfL2lzido", "libkernel", 1, "libkernel", 1, 1, sceKernelQueryMemoryProtection); LIB_FUNCTION("BHouLQzh0X0", "libkernel", 1, "libkernel", 1, 1, sceKernelDirectMemoryQuery); LIB_FUNCTION("MBuItvba6z8", "libkernel", 1, "libkernel", 1, 1, sceKernelReleaseDirectMemory); + LIB_FUNCTION("PGhQHd-dzv8", "libkernel", 1, "libkernel", 1, 1, sceKernelMmap); LIB_FUNCTION("cQke9UuBQOk", "libkernel", 1, "libkernel", 1, 1, sceKernelMunmap); LIB_FUNCTION("mL8NDH86iQI", "libkernel", 1, "libkernel", 1, 1, sceKernelMapNamedFlexibleMemory); LIB_FUNCTION("aNz11fnnzi4", "libkernel", 1, "libkernel", 1, 1, diff --git a/src/core/libraries/kernel/threads/semaphore.cpp b/src/core/libraries/kernel/threads/semaphore.cpp index 312f82b69..ff5368023 100644 --- a/src/core/libraries/kernel/threads/semaphore.cpp +++ b/src/core/libraries/kernel/threads/semaphore.cpp @@ -32,6 +32,10 @@ public: return ORBIS_KERNEL_ERROR_EBUSY; } + if (timeout && *timeout == 0) { + return SCE_KERNEL_ERROR_ETIMEDOUT; + } + // Create waiting thread object and add it into the list of waiters. WaitingThread waiter{need_count, is_fifo}; const auto it = AddWaiter(&waiter); diff --git a/src/core/libraries/kernel/time_management.cpp b/src/core/libraries/kernel/time_management.cpp index 7a6ba4f62..5e5e0ef27 100644 --- a/src/core/libraries/kernel/time_management.cpp +++ b/src/core/libraries/kernel/time_management.cpp @@ -147,13 +147,20 @@ int PS4_SYSV_ABI sceKernelGettimeofday(OrbisKernelTimeval* tp) { } #ifdef _WIN64 - auto now = std::chrono::system_clock::now(); - auto duration = now.time_since_epoch(); - auto seconds = std::chrono::duration_cast(duration); - auto microsecs = std::chrono::duration_cast(duration - seconds); + FILETIME filetime; + GetSystemTimeAsFileTime(&filetime); - tp->tv_sec = seconds.count(); - tp->tv_usec = microsecs.count(); + constexpr u64 UNIX_TIME_START = 0x295E9648864000; + constexpr u64 TICKS_PER_SECOND = 1000000; + + u64 ticks = filetime.dwHighDateTime; + ticks <<= 32; + ticks |= filetime.dwLowDateTime; + ticks /= 10; + ticks -= UNIX_TIME_START; + + tp->tv_sec = ticks / TICKS_PER_SECOND; + tp->tv_usec = ticks % TICKS_PER_SECOND; #else timeval tv; gettimeofday(&tv, nullptr); diff --git a/src/core/libraries/libs.cpp b/src/core/libraries/libs.cpp index da41eaf00..5b6c17b10 100644 --- a/src/core/libraries/libs.cpp +++ b/src/core/libraries/libs.cpp @@ -27,12 +27,12 @@ #include "core/libraries/playgo/playgo.h" #include "core/libraries/random/random.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/system/commondialog.h" #include "core/libraries/system/msgdialog.h" #include "core/libraries/system/posix.h" -#include "core/libraries/system/savedatadialog.h" #include "core/libraries/system/sysmodule.h" #include "core/libraries/system/systemservice.h" #include "core/libraries/system/userservice.h" @@ -57,11 +57,11 @@ void InitHLELibs(Core::Loader::SymbolsResolver* sym) { Libraries::Net::RegisterlibSceNet(sym); Libraries::NetCtl::RegisterlibSceNetCtl(sym); Libraries::SaveData::RegisterlibSceSaveData(sym); + Libraries::SaveData::Dialog::RegisterlibSceSaveDataDialog(sym); Libraries::Ssl::RegisterlibSceSsl(sym); Libraries::SysModule::RegisterlibSceSysmodule(sym); Libraries::Posix::Registerlibsceposix(sym); Libraries::AudioIn::RegisterlibSceAudioIn(sym); - Libraries::SaveDataDialog::RegisterlibSceSaveDataDialog(sym); Libraries::NpManager::RegisterlibSceNpManager(sym); Libraries::NpScore::RegisterlibSceNpScore(sym); Libraries::NpTrophy::RegisterlibSceNpTrophy(sym); diff --git a/src/core/libraries/np_trophy/trophy_ui.h b/src/core/libraries/np_trophy/trophy_ui.h index d730aca57..060d80dec 100644 --- a/src/core/libraries/np_trophy/trophy_ui.h +++ b/src/core/libraries/np_trophy/trophy_ui.h @@ -31,10 +31,6 @@ public: void Finish(); void Draw() override; - - bool ShouldGrabGamepad() override { - return false; - } }; }; // namespace Libraries::NpTrophy \ No newline at end of file diff --git a/src/core/libraries/save_data/dialog/savedatadialog.cpp b/src/core/libraries/save_data/dialog/savedatadialog.cpp new file mode 100644 index 000000000..0ad7d7dc0 --- /dev/null +++ b/src/core/libraries/save_data/dialog/savedatadialog.cpp @@ -0,0 +1,164 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "common/elf_info.h" +#include "common/logging/log.h" +#include "core/libraries/libs.h" +#include "core/libraries/system/commondialog.h" +#include "magic_enum.hpp" +#include "savedatadialog.h" +#include "savedatadialog_ui.h" + +namespace Libraries::SaveData::Dialog { + +using CommonDialog::Error; +using CommonDialog::Result; +using CommonDialog::Status; + +static auto g_status = Status::NONE; +static SaveDialogState g_state{}; +static SaveDialogResult g_result{}; +static SaveDialogUi g_save_dialog_ui; + +Error PS4_SYSV_ABI sceSaveDataDialogClose() { + LOG_DEBUG(Lib_SaveDataDialog, "called"); + if (g_status != Status::RUNNING) { + return Error::NOT_RUNNING; + } + g_save_dialog_ui.Finish(ButtonId::INVALID); + g_save_dialog_ui = SaveDialogUi{}; + return Error::OK; +} + +Error PS4_SYSV_ABI sceSaveDataDialogGetResult(OrbisSaveDataDialogResult* result) { + LOG_DEBUG(Lib_SaveDataDialog, "called"); + if (g_status != Status::FINISHED) { + return Error::NOT_FINISHED; + } + if (result == nullptr) { + return Error::ARG_NULL; + } + g_result.CopyTo(*result); + return Error::OK; +} + +Status PS4_SYSV_ABI sceSaveDataDialogGetStatus() { + LOG_TRACE(Lib_SaveDataDialog, "called status={}", magic_enum::enum_name(g_status)); + return g_status; +} + +Error PS4_SYSV_ABI sceSaveDataDialogInitialize() { + LOG_DEBUG(Lib_SaveDataDialog, "called"); + if (!CommonDialog::g_isInitialized) { + return Error::NOT_SYSTEM_INITIALIZED; + } + if (g_status != Status::NONE) { + return Error::ALREADY_INITIALIZED; + } + if (CommonDialog::g_isUsed) { + return Error::BUSY; + } + g_status = Status::INITIALIZED; + CommonDialog::g_isUsed = true; + + return Error::OK; +} + +s32 PS4_SYSV_ABI sceSaveDataDialogIsReadyToDisplay() { + return 1; +} + +Error PS4_SYSV_ABI sceSaveDataDialogOpen(const OrbisSaveDataDialogParam* param) { + if (g_status != Status::INITIALIZED && g_status != Status::FINISHED) { + LOG_INFO(Lib_SaveDataDialog, "called without initialize"); + return Error::INVALID_STATE; + } + if (param == nullptr) { + LOG_DEBUG(Lib_SaveDataDialog, "called param:(NULL)"); + return Error::ARG_NULL; + } + LOG_DEBUG(Lib_SaveDataDialog, "called param->mode: {}", magic_enum::enum_name(param->mode)); + ASSERT(param->size == sizeof(OrbisSaveDataDialogParam)); + ASSERT(param->baseParam.size == sizeof(CommonDialog::BaseParam)); + g_result = {}; + g_state = SaveDialogState{*param}; + g_status = Status::RUNNING; + g_save_dialog_ui = SaveDialogUi(&g_state, &g_status, &g_result); + return Error::OK; +} + +Error PS4_SYSV_ABI sceSaveDataDialogProgressBarInc(OrbisSaveDataDialogProgressBarTarget target, + u32 delta) { + LOG_DEBUG(Lib_SaveDataDialog, "called"); + if (g_status != Status::RUNNING) { + return Error::NOT_RUNNING; + } + if (g_state.GetMode() != SaveDataDialogMode::PROGRESS_BAR) { + return Error::NOT_SUPPORTED; + } + if (target != OrbisSaveDataDialogProgressBarTarget::DEFAULT) { + return Error::PARAM_INVALID; + } + g_state.GetState().progress += delta; + return Error::OK; +} + +Error PS4_SYSV_ABI sceSaveDataDialogProgressBarSetValue(OrbisSaveDataDialogProgressBarTarget target, + u32 rate) { + LOG_DEBUG(Lib_SaveDataDialog, "called"); + if (g_status != Status::RUNNING) { + return Error::NOT_RUNNING; + } + if (g_state.GetMode() != SaveDataDialogMode::PROGRESS_BAR) { + return Error::NOT_SUPPORTED; + } + if (target != OrbisSaveDataDialogProgressBarTarget::DEFAULT) { + return Error::PARAM_INVALID; + } + g_state.GetState().progress = rate; + return Error::OK; +} + +Error PS4_SYSV_ABI sceSaveDataDialogTerminate() { + LOG_DEBUG(Lib_SaveDataDialog, "called"); + if (g_status == Status::RUNNING) { + sceSaveDataDialogClose(); + } + if (g_status == Status::NONE) { + return Error::NOT_INITIALIZED; + } + g_save_dialog_ui = SaveDialogUi{}; + g_status = Status::NONE; + CommonDialog::g_isUsed = false; + return Error::OK; +} + +Status PS4_SYSV_ABI sceSaveDataDialogUpdateStatus() { + LOG_TRACE(Lib_SaveDataDialog, "called status={}", magic_enum::enum_name(g_status)); + return g_status; +} + +void RegisterlibSceSaveDataDialog(Core::Loader::SymbolsResolver* sym) { + LIB_FUNCTION("fH46Lag88XY", "libSceSaveDataDialog", 1, "libSceSaveDataDialog", 1, 1, + sceSaveDataDialogClose); + LIB_FUNCTION("yEiJ-qqr6Cg", "libSceSaveDataDialog", 1, "libSceSaveDataDialog", 1, 1, + sceSaveDataDialogGetResult); + LIB_FUNCTION("ERKzksauAJA", "libSceSaveDataDialog", 1, "libSceSaveDataDialog", 1, 1, + sceSaveDataDialogGetStatus); + LIB_FUNCTION("s9e3+YpRnzw", "libSceSaveDataDialog", 1, "libSceSaveDataDialog", 1, 1, + sceSaveDataDialogInitialize); + LIB_FUNCTION("en7gNVnh878", "libSceSaveDataDialog", 1, "libSceSaveDataDialog", 1, 1, + sceSaveDataDialogIsReadyToDisplay); + LIB_FUNCTION("4tPhsP6FpDI", "libSceSaveDataDialog", 1, "libSceSaveDataDialog", 1, 1, + sceSaveDataDialogOpen); + LIB_FUNCTION("V-uEeFKARJU", "libSceSaveDataDialog", 1, "libSceSaveDataDialog", 1, 1, + sceSaveDataDialogProgressBarInc); + LIB_FUNCTION("hay1CfTmLyA", "libSceSaveDataDialog", 1, "libSceSaveDataDialog", 1, 1, + sceSaveDataDialogProgressBarSetValue); + LIB_FUNCTION("YuH2FA7azqQ", "libSceSaveDataDialog", 1, "libSceSaveDataDialog", 1, 1, + sceSaveDataDialogTerminate); + LIB_FUNCTION("KK3Bdg1RWK0", "libSceSaveDataDialog", 1, "libSceSaveDataDialog", 1, 1, + sceSaveDataDialogUpdateStatus); +}; + +} // namespace Libraries::SaveData::Dialog diff --git a/src/core/libraries/save_data/dialog/savedatadialog.h b/src/core/libraries/save_data/dialog/savedatadialog.h new file mode 100644 index 000000000..34afe98a7 --- /dev/null +++ b/src/core/libraries/save_data/dialog/savedatadialog.h @@ -0,0 +1,33 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "common/types.h" +#include "core/libraries/system/commondialog.h" + +namespace Core::Loader { +class SymbolsResolver; +} + +namespace Libraries::SaveData::Dialog { + +struct OrbisSaveDataDialogParam; +struct OrbisSaveDataDialogResult; +enum class OrbisSaveDataDialogProgressBarTarget : u32; + +CommonDialog::Error PS4_SYSV_ABI sceSaveDataDialogClose(); +CommonDialog::Error PS4_SYSV_ABI sceSaveDataDialogGetResult(OrbisSaveDataDialogResult* result); +CommonDialog::Status PS4_SYSV_ABI sceSaveDataDialogGetStatus(); +CommonDialog::Error PS4_SYSV_ABI sceSaveDataDialogInitialize(); +s32 PS4_SYSV_ABI sceSaveDataDialogIsReadyToDisplay(); +CommonDialog::Error PS4_SYSV_ABI sceSaveDataDialogOpen(const OrbisSaveDataDialogParam* param); +CommonDialog::Error PS4_SYSV_ABI +sceSaveDataDialogProgressBarInc(OrbisSaveDataDialogProgressBarTarget target, u32 delta); +CommonDialog::Error PS4_SYSV_ABI +sceSaveDataDialogProgressBarSetValue(OrbisSaveDataDialogProgressBarTarget target, u32 rate); +CommonDialog::Error PS4_SYSV_ABI sceSaveDataDialogTerminate(); +CommonDialog::Status PS4_SYSV_ABI sceSaveDataDialogUpdateStatus(); + +void RegisterlibSceSaveDataDialog(Core::Loader::SymbolsResolver* sym); +} // namespace Libraries::SaveData::Dialog diff --git a/src/core/libraries/save_data/dialog/savedatadialog_ui.cpp b/src/core/libraries/save_data/dialog/savedatadialog_ui.cpp new file mode 100644 index 000000000..793b4dd38 --- /dev/null +++ b/src/core/libraries/save_data/dialog/savedatadialog_ui.cpp @@ -0,0 +1,849 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include +#include +#include + +#include "common/elf_info.h" +#include "common/singleton.h" +#include "common/string_util.h" +#include "core/file_sys/fs.h" +#include "core/libraries/save_data/save_instance.h" +#include "imgui/imgui_std.h" +#include "savedatadialog_ui.h" + +using namespace ImGui; +using namespace Libraries::CommonDialog; +using Common::ElfInfo; + +constexpr u32 OrbisSaveDataBlockSize = 32768; // 32 KiB + +constexpr auto SAVE_ICON_SIZE = ImVec2{152.0f, 85.0f}; +constexpr auto SAVE_ICON_PADDING = ImVec2{8.0f, 2.0f}; + +static constexpr ImVec2 BUTTON_SIZE{100.0f, 30.0f}; +constexpr auto FOOTER_HEIGHT = BUTTON_SIZE.y + 15.0f; +static constexpr float PROGRESS_BAR_WIDTH{0.8f}; + +static ::Core::FileSys::MntPoints* g_mnt = + Common::Singleton<::Core::FileSys::MntPoints>::Instance(); + +static std::string SpaceSizeToString(size_t size) { + std::string size_str; + if (size > 1024 * 1024 * 1024) { // > 1GB + size_str = fmt::format("{:.2f} GB", double(size / 1024 / 1024) / 1024.0f); + } else if (size > 1024 * 1024) { // > 1MB + size_str = fmt::format("{:.2f} MB", double(size / 1024) / 1024.0f); + } else if (size > 1024) { // > 1KB + size_str = fmt::format("{:.2f} KB", double(size) / 1024.0f); + } else { + size_str = fmt::format("{} B", size); + } + return size_str; +} + +namespace Libraries::SaveData::Dialog { + +void SaveDialogResult::CopyTo(OrbisSaveDataDialogResult& result) const { + result.mode = this->mode; + result.result = this->result; + result.buttonId = this->button_id; + if (mode == SaveDataDialogMode::LIST || ElfInfo::Instance().FirmwareVer() >= ElfInfo::FW_45) { + if (result.dirName != nullptr) { + result.dirName->data.FromString(this->dir_name); + } + if (result.param != nullptr && this->param.GetString(SaveParams::MAINTITLE).has_value()) { + result.param->FromSFO(this->param); + } + } + result.userData = this->user_data; +} + +SaveDialogState::SaveDialogState(const OrbisSaveDataDialogParam& param) { + this->mode = param.mode; + this->type = param.dispType; + this->user_data = param.userData; + if (param.optionParam != nullptr) { + this->enable_back = {param.optionParam->back == OptionBack::ENABLE}; + } + + const auto& game_serial = Common::ElfInfo::Instance().GameSerial(); + + const auto item = param.items; + this->user_id = item->userId; + + if (item->titleId == nullptr) { + this->title_id = game_serial; + } else { + this->title_id = item->titleId->data.to_string(); + } + + for (u32 i = 0; i < item->dirNameNum; i++) { + const auto dir_name = item->dirName[i].data.to_view(); + + if (dir_name.empty()) { + continue; + } + + auto dir_path = SaveInstance::MakeDirSavePath(user_id, title_id, dir_name); + + auto param_sfo_path = dir_path / "sce_sys" / "param.sfo"; + if (!std::filesystem::exists(param_sfo_path)) { + continue; + } + + PSF param_sfo; + param_sfo.Open(param_sfo_path); + + auto last_write = param_sfo.GetLastWrite(); +#ifdef _WIN32 + auto utc_time = std::chrono::file_clock::to_utc(last_write); +#else + auto utc_time = std::chrono::file_clock::to_sys(last_write); +#endif + std::string date_str = fmt::format("{:%d %b, %Y %R}", utc_time); + + size_t size = Common::FS::GetDirectorySize(dir_path); + std::string size_str = SpaceSizeToString(size); + + auto icon_path = dir_path / "sce_sys" / "icon0.png"; + RefCountedTexture icon; + if (std::filesystem::exists(icon_path)) { + icon = RefCountedTexture::DecodePngFile(icon_path); + } + + bool is_corrupted = std::filesystem::exists(dir_path / "sce_sys" / "corrupted"); + + this->save_list.emplace_back(Item{ + .dir_name = std::string{dir_name}, + .icon = icon, + + .title = std::string{param_sfo.GetString(SaveParams::MAINTITLE).value_or("Unknown")}, + .subtitle = std::string{param_sfo.GetString(SaveParams::SUBTITLE).value_or("")}, + .details = std::string{param_sfo.GetString(SaveParams::DETAIL).value_or("")}, + .date = date_str, + .size = size_str, + .last_write = param_sfo.GetLastWrite(), + .pfo = param_sfo, + .is_corrupted = is_corrupted, + }); + } + + if (type == DialogType::SAVE && item->newItem != nullptr) { + RefCountedTexture icon; + std::string title{"New Save"}; + + const auto new_item = item->newItem; + if (new_item->iconBuf && new_item->iconSize) { + auto buf = (u8*)new_item->iconBuf; + icon = RefCountedTexture::DecodePngTexture({buf, buf + new_item->iconSize}); + } else { + const auto& src_icon = g_mnt->GetHostPath("/app0/sce_sys/save_data.png"); + if (std::filesystem::exists(src_icon)) { + icon = RefCountedTexture::DecodePngFile(src_icon); + } + } + if (new_item->title != nullptr) { + title = std::string{new_item->title}; + } + + this->new_item = Item{ + .dir_name = "", + .icon = icon, + .title = title, + }; + } + + if (item->focusPos != FocusPos::DIRNAME) { + this->focus_pos = item->focusPos; + } else { + this->focus_pos = item->focusPosDirName->data.to_string(); + } + this->style = item->itemStyle; + + switch (mode) { + case SaveDataDialogMode::USER_MSG: { + this->state = UserState{param}; + } break; + case SaveDataDialogMode::SYSTEM_MSG: + this->state = SystemState{*this, param}; + break; + case SaveDataDialogMode::ERROR_CODE: { + this->state = ErrorCodeState{param}; + } break; + case SaveDataDialogMode::PROGRESS_BAR: { + this->state = ProgressBarState{*this, param}; + } break; + default: + break; + } +} + +SaveDialogState::UserState::UserState(const OrbisSaveDataDialogParam& param) { + auto& user = *param.userMsgParam; + this->type = user.buttonType; + this->msg_type = user.msgType; + this->msg = user.msg != nullptr ? std::string{user.msg} : std::string{}; +} + +SaveDialogState::SystemState::SystemState(const SaveDialogState& state, + const OrbisSaveDataDialogParam& param) { +#define M(save, load, del) \ + if (type == DialogType::SAVE) \ + this->msg = save; \ + else if (type == DialogType::LOAD) \ + this->msg = load; \ + else if (type == DialogType::DELETE) \ + this->msg = del; \ + else \ + UNREACHABLE() + + auto type = param.dispType; + auto& sys = *param.sysMsgParam; + switch (sys.msgType) { + case SystemMessageType::NODATA: { + return_cancel = true; + this->msg = "There is no saved data"; + } break; + case SystemMessageType::CONFIRM: + show_no = true; + M("Do you want to save?", "Do you want to load this saved data?", + "Do you want to delete this saved data?"); + break; + case SystemMessageType::OVERWRITE: + show_no = true; + M("Do you want to overwrite the existing saved data?", "##UNKNOWN##", "##UNKNOWN##"); + break; + case SystemMessageType::NOSPACE: + return_cancel = true; + M(fmt::format( + "There is not enough space to save the data. To continue {} free space is required.", + SpaceSizeToString(sys.value * OrbisSaveDataBlockSize)), + "##UNKNOWN##", "##UNKNOWN##"); + break; + case SystemMessageType::PROGRESS: + hide_ok = true; + show_cancel = state.enable_back.value_or(false); + M("Saving...", "Loading...", "Deleting..."); + break; + case SystemMessageType::FILE_CORRUPTED: + return_cancel = true; + this->msg = "The saved data is corrupted."; + break; + case SystemMessageType::FINISHED: + return_cancel = true; + M("Saved successfully.", "Loading complete.", "Deletion complete."); + break; + case SystemMessageType::NOSPACE_CONTINUABLE: + return_cancel = true; + M(fmt::format("There is not enough space to save the data. {} free space is required.", + SpaceSizeToString(sys.value * OrbisSaveDataBlockSize)), + "##UNKNOWN##", "##UNKNOWN##"); + break; + case SystemMessageType::CORRUPTED_AND_DELETED: { + show_cancel = state.enable_back.value_or(true); + const char* msg1 = "The saved data is corrupted and will be deleted."; + M(msg1, msg1, "##UNKNOWN##"); + } break; + case SystemMessageType::CORRUPTED_AND_CREATED: { + show_cancel = state.enable_back.value_or(true); + const char* msg2 = "The saved data is corrupted. This saved data will be deleted and a new " + "one will be created."; + M(msg2, msg2, "##UNKNOWN##"); + } break; + case SystemMessageType::CORRUPTED_AND_RESTORE: { + show_cancel = state.enable_back.value_or(true); + const char* msg3 = + "The saved data is corrupted. The data that was backed up by the system will be " + "restored."; + M(msg3, msg3, "##UNKNOWN##"); + } break; + case SystemMessageType::TOTAL_SIZE_EXCEEDED: + M("Cannot create more saved data", "##UNKNOWN##", "##UNKNOWN##"); + break; + default: + msg = fmt::format("Unknown message type: {}", magic_enum::enum_name(sys.msgType)); + break; + } + +#undef M +} + +SaveDialogState::ErrorCodeState::ErrorCodeState(const OrbisSaveDataDialogParam& param) { + auto& err = *param.errorCodeParam; + constexpr auto NOT_FOUND = 0x809F0008; + constexpr auto BROKEN = 0x809F000F; + switch (err.errorCode) { + case NOT_FOUND: + this->error_msg = "There is not saved data."; + break; + case BROKEN: + this->error_msg = "The data is corrupted."; + break; + default: + this->error_msg = fmt::format("An error has occurred. ({:X})", err.errorCode); + break; + } +} +SaveDialogState::ProgressBarState::ProgressBarState(const SaveDialogState& state, + const OrbisSaveDataDialogParam& param) { + static auto fw_ver = ElfInfo::Instance().FirmwareVer(); + + this->progress = 0; + + auto& bar = *param.progressBarParam; + + if (bar.msg != nullptr) { + this->msg = std::string{bar.msg}; + } else { + switch (bar.sysMsgType) { + case ProgressSystemMessageType::INVALID: + this->msg = "INVALID"; + break; + case ProgressSystemMessageType::PROGRESS: + switch (state.type) { + case DialogType::SAVE: + this->msg = "Saving..."; + break; + case DialogType::LOAD: + this->msg = "Loading..."; + break; + case DialogType::DELETE: + this->msg = "Deleting..."; + break; + } + break; + case ProgressSystemMessageType::RESTORE: + this->msg = "Restoring saved data..."; + break; + } + } +} + +SaveDialogUi::SaveDialogUi(SaveDialogState* state, Status* status, SaveDialogResult* result) + : state(state), status(status), result(result) { + if (status && *status == Status::RUNNING) { + first_render = true; + AddLayer(this); + } +} + +SaveDialogUi::~SaveDialogUi() { + Finish(ButtonId::INVALID); +} + +SaveDialogUi::SaveDialogUi(SaveDialogUi&& other) noexcept + : Layer(other), state(other.state), status(other.status), result(other.result) { + std::scoped_lock lock(draw_mutex, other.draw_mutex); + other.state = nullptr; + other.status = nullptr; + other.result = nullptr; + if (status && *status == Status::RUNNING) { + first_render = true; + AddLayer(this); + } +} + +SaveDialogUi& SaveDialogUi::operator=(SaveDialogUi other) { + std::scoped_lock lock(draw_mutex, other.draw_mutex); + using std::swap; + swap(state, other.state); + swap(status, other.status); + swap(result, other.result); + if (status && *status == Status::RUNNING) { + first_render = true; + AddLayer(this); + } + return *this; +} + +void SaveDialogUi::Finish(ButtonId buttonId, Result r) { + std::unique_lock lock(draw_mutex); + if (result) { + result->mode = this->state->mode; + result->result = r; + result->button_id = buttonId; + result->user_data = this->state->user_data; + if (state && state->mode != SaveDataDialogMode::LIST && !state->save_list.empty()) { + result->dir_name = state->save_list.front().dir_name; + } + } + if (status) { + *status = Status::FINISHED; + } + RemoveLayer(this); +} + +void SaveDialogUi::Draw() { + std::unique_lock lock{draw_mutex}; + + if (status == nullptr || *status != Status::RUNNING || state == nullptr) { + return; + } + + const auto& ctx = *GetCurrentContext(); + const auto& io = ctx.IO; + + ImVec2 window_size; + + if (state->GetMode() == SaveDataDialogMode::LIST) { + window_size = ImVec2{ + std::min(io.DisplaySize.x - 200.0f, 1100.0f), + std::min(io.DisplaySize.y - 100.0f, 700.0f), + }; + } else { + window_size = ImVec2{ + std::min(io.DisplaySize.x, 600.0f), + std::min(io.DisplaySize.y, 300.0f), + }; + } + + CentralizeWindow(); + SetNextWindowSize(window_size); + SetNextWindowCollapsed(false); + if (first_render || !io.NavActive) { + SetNextWindowFocus(); + } + if (Begin("Save Data Dialog##SaveDataDialog", nullptr, + ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoSavedSettings)) { + DrawPrettyBackground(); + + Separator(); + // Draw title bigger + SetWindowFontScale(1.7f); + switch (state->type) { + case DialogType::SAVE: + TextUnformatted("Save"); + break; + case DialogType::LOAD: + TextUnformatted("Load"); + break; + case DialogType::DELETE: + TextUnformatted("Delete"); + break; + } + SetWindowFontScale(1.0f); + Separator(); + + BeginGroup(); + switch (state->GetMode()) { + case SaveDataDialogMode::LIST: + DrawList(); + break; + case SaveDataDialogMode::USER_MSG: + DrawUser(); + break; + case SaveDataDialogMode::SYSTEM_MSG: + DrawSystemMessage(); + break; + case SaveDataDialogMode::ERROR_CODE: + DrawErrorCode(); + break; + case SaveDataDialogMode::PROGRESS_BAR: + DrawProgressBar(); + break; + default: + TextColored(ImVec4(1.0f, 0.0f, 0.0f, 1.0f), "!!!Unknown dialog mode!!!"); + } + EndGroup(); + } + End(); + + first_render = false; + if (*status == Status::FINISHED) { + if (state) { + *state = SaveDialogState{}; + } + state = nullptr; + status = nullptr; + result = nullptr; + } +} + +void SaveDialogUi::DrawItem(int _id, const SaveDialogState::Item& item, bool clickable) { + constexpr auto text_spacing = 0.95f; + + auto& ctx = *GetCurrentContext(); + auto& window = *ctx.CurrentWindow; + + auto content_region_avail = GetContentRegionAvail(); + const auto outer_pos = window.DC.CursorPos; + const auto pos = outer_pos + SAVE_ICON_PADDING; + + const ImVec2 size = {content_region_avail.x - SAVE_ICON_PADDING.x, + SAVE_ICON_SIZE.y + SAVE_ICON_PADDING.y}; + const ImRect bb{outer_pos, outer_pos + size + SAVE_ICON_PADDING}; + + const ImGuiID id = GetID(_id); + + ItemSize(size + ImVec2{0.0f, SAVE_ICON_PADDING.y * 2.0f}); + if (!ItemAdd(bb, id)) { + return; + } + + window.DrawList->AddRectFilled(bb.Min + SAVE_ICON_PADDING, bb.Max - SAVE_ICON_PADDING, + GetColorU32(ImVec4{0.3f})); + + bool hovered = false; + if (clickable) { + bool held; + bool pressed = ButtonBehavior(bb, id, &hovered, &held); + if (pressed) { + result->dir_name = item.dir_name; + result->param = item.pfo; + Finish(ButtonId::INVALID); + } + RenderNavHighlight(bb, id); + } + + if (item.icon) { + auto texture = item.icon.GetTexture(); + window.DrawList->AddImage(texture.im_id, pos, pos + SAVE_ICON_SIZE); + } else { + // placeholder + window.DrawList->AddRectFilled(pos, pos + SAVE_ICON_SIZE, GetColorU32(ImVec4{0.7f})); + } + + auto pos_x = SAVE_ICON_SIZE.x + 5.0f; + auto pos_y = 2.0f; + + if (!item.title.empty()) { + const char* begin = &item.title.front(); + const char* end = &item.title.back() + 1; + SetWindowFontScale(1.5f); + RenderText(pos + ImVec2{pos_x, pos_y}, begin, end, false); + pos_y += ctx.FontSize * text_spacing; + } + SetWindowFontScale(1.1f); + + if (item.is_corrupted) { + pos_y -= ctx.FontSize * text_spacing * 0.3f; + const auto bright = (int)std::abs(std::sin(ctx.Time) * 0.15f * 255.0f); + PushStyleColor(ImGuiCol_Text, IM_COL32(bright + 216, bright, bright, 0xFF)); + RenderText(pos + ImVec2{pos_x, pos_y}, "Corrupted", nullptr, false); + PopStyleColor(); + pos_y += ctx.FontSize * text_spacing * 0.8f; + } + + if (state->style == ItemStyle::TITLE_SUBTITLE_DATESIZE) { + if (!item.subtitle.empty()) { + const char* begin = &item.subtitle.front(); + const char* end = &item.subtitle.back() + 1; + RenderText(pos + ImVec2{pos_x, pos_y}, begin, end, false); + } + pos_y += ctx.FontSize * text_spacing; + } + + { + float width = 0.0f; + if (!item.date.empty()) { + const char* d_begin = &item.date.front(); + const char* d_end = &item.date.back() + 1; + width = CalcTextSize(d_begin, d_end).x + 15.0f; + RenderText(pos + ImVec2{pos_x, pos_y}, d_begin, d_end, false); + } + if (!item.size.empty()) { + const char* s_begin = &item.size.front(); + const char* s_end = &item.size.back() + 1; + RenderText(pos + ImVec2{pos_x + width, pos_y}, s_begin, s_end, false); + } + pos_y += ctx.FontSize * text_spacing; + } + + if (state->style == ItemStyle::TITLE_DATASIZE_SUBTITLE && !item.subtitle.empty()) { + const char* begin = &item.subtitle.front(); + const char* end = &item.subtitle.back() + 1; + RenderText(pos + ImVec2{pos_x, pos_y}, begin, end, false); + } + + SetWindowFontScale(1.0f); + + if (hovered) { + window.DrawList->AddRect(bb.Min, bb.Max, GetColorU32(ImGuiCol_Border), 0.0f, 0, 2.0f); + } +} + +void SaveDialogUi::DrawList() { + auto availableSize = GetContentRegionAvail(); + + constexpr auto footerHeight = 30.0f; + availableSize.y -= footerHeight + 1.0f; + + BeginChild("##ScrollingRegion", availableSize, ImGuiChildFlags_NavFlattened); + int i = 0; + if (state->new_item.has_value()) { + DrawItem(i++, state->new_item.value()); + } + for (const auto& item : state->save_list) { + DrawItem(i++, item); + } + if (first_render) { // Make the initial focus + if (std::holds_alternative(state->focus_pos)) { + auto pos = std::get(state->focus_pos); + if (pos == FocusPos::LISTHEAD || pos == FocusPos::DATAHEAD) { + SetItemCurrentNavFocus(GetID(0)); + } else if (pos == FocusPos::LISTTAIL || pos == FocusPos::DATATAIL) { + SetItemCurrentNavFocus(GetID(std::max(i - 1, 0))); + } else { // Date + int idx = 0; + int max_idx = 0; + bool is_min = pos == FocusPos::DATAOLDEST; + std::filesystem::file_time_type max_write{}; + if (state->new_item.has_value()) { + idx++; + } + for (const auto& item : state->save_list) { + if (item.last_write > max_write ^ is_min) { + max_write = item.last_write; + max_idx = idx; + } + idx++; + } + SetItemCurrentNavFocus(GetID(max_idx)); + } + } else if (std::holds_alternative(state->focus_pos)) { + auto dir_name = std::get(state->focus_pos); + if (dir_name.empty()) { + SetItemCurrentNavFocus(GetID(0)); + } else { + int idx = 0; + if (state->new_item.has_value()) { + if (dir_name == state->new_item->dir_name) { + SetItemCurrentNavFocus(GetID(idx)); + } + idx++; + } + for (const auto& item : state->save_list) { + if (item.dir_name == dir_name) { + SetItemCurrentNavFocus(GetID(idx)); + break; + } + idx++; + } + } + } + } + EndChild(); + + Separator(); + if (state->enable_back.value_or(true)) { + constexpr auto back = "Back"; + constexpr float pad = 7.0f; + const auto txt_size = CalcTextSize(back); + const auto button_size = ImVec2{ + std::max(txt_size.x, 100.0f) + pad * 2.0f, + footerHeight - pad, + }; + SetCursorPosX(GetContentRegionAvail().x - button_size.x); + if (Button(back, button_size)) { + result->dir_name.clear(); + Finish(ButtonId::INVALID, Result::USER_CANCELED); + } + if (IsKeyPressed(ImGuiKey_GamepadFaceRight)) { + SetItemCurrentNavFocus(); + } + } +} + +void SaveDialogUi::DrawUser() { + const auto& user_state = state->GetState(); + const auto btn_type = user_state.type; + + const auto ws = GetWindowSize(); + + if (!state->save_list.empty()) { + DrawItem(0, state->save_list.front(), false); + } else if (state->new_item) { + DrawItem(0, *state->new_item, false); + } + + auto has_btn = btn_type != ButtonType::NONE; + ImVec2 btn_space; + if (has_btn) { + btn_space = ImVec2{0.0f, FOOTER_HEIGHT}; + } + + const auto& msg = user_state.msg; + if (!msg.empty()) { + const char* begin = &msg.front(); + const char* end = &msg.back() + 1; + if (user_state.msg_type == UserMessageType::ERROR) { + PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 0.0f, 0.0f, 1.0f)); + // Maybe make the text bold? + } + DrawCenteredText(begin, end, GetContentRegionAvail() - btn_space); + if (user_state.msg_type == UserMessageType::ERROR) { + PopStyleColor(); + } + } + + if (has_btn) { + int count = 1; + if (btn_type == ButtonType::YESNO || btn_type == ButtonType::OKCANCEL) { + ++count; + } + + SetCursorPos({ + ws.x / 2.0f - BUTTON_SIZE.x / 2.0f * static_cast(count), + ws.y - FOOTER_HEIGHT + 5.0f, + }); + + BeginGroup(); + if (btn_type == ButtonType::YESNO) { + if (Button("Yes", BUTTON_SIZE)) { + Finish(ButtonId::YES); + } + SameLine(); + if (Button("No", BUTTON_SIZE)) { + if (ElfInfo::Instance().FirmwareVer() < ElfInfo::FW_45) { + Finish(ButtonId::INVALID, Result::USER_CANCELED); + } else { + Finish(ButtonId::NO); + } + } + if (first_render || IsKeyPressed(ImGuiKey_GamepadFaceRight)) { + SetItemCurrentNavFocus(); + } + } else { + if (Button("OK", BUTTON_SIZE)) { + if (btn_type == ButtonType::OK && + ElfInfo::Instance().FirmwareVer() < ElfInfo::FW_45) { + Finish(ButtonId::INVALID, Result::USER_CANCELED); + } else { + Finish(ButtonId::OK); + } + } + if (first_render) { + SetItemCurrentNavFocus(); + } + if (btn_type == ButtonType::OKCANCEL) { + SameLine(); + if (Button("Cancel", BUTTON_SIZE)) { + Finish(ButtonId::INVALID, Result::USER_CANCELED); + } + if (IsKeyPressed(ImGuiKey_GamepadFaceRight)) { + SetItemCurrentNavFocus(); + } + } + } + EndGroup(); + } +} + +void SaveDialogUi::DrawSystemMessage() { + const auto& sys_state = state->GetState(); + + if (!state->save_list.empty()) { + DrawItem(0, state->save_list.front(), false); + } else if (state->new_item) { + DrawItem(0, *state->new_item, false); + } + + const auto ws = GetWindowSize(); + const auto& msg = sys_state.msg; + if (!msg.empty()) { + const char* begin = &msg.front(); + const char* end = &msg.back() + 1; + DrawCenteredText(begin, end, GetContentRegionAvail() - ImVec2{0.0f, FOOTER_HEIGHT}); + } + int count = 1; + if (sys_state.hide_ok) { + --count; + } + if (sys_state.show_no || sys_state.show_cancel) { + ++count; + } + + SetCursorPos({ + ws.x / 2.0f - BUTTON_SIZE.x / 2.0f * static_cast(count), + ws.y - FOOTER_HEIGHT + 5.0f, + }); + BeginGroup(); + if (Button(sys_state.show_no ? "Yes" : "OK", BUTTON_SIZE)) { + if (sys_state.return_cancel && ElfInfo::Instance().FirmwareVer() < ElfInfo::FW_45) { + Finish(ButtonId::INVALID, Result::USER_CANCELED); + } else { + Finish(ButtonId::YES); + } + } + SameLine(); + if (sys_state.show_no) { + if (Button("No", BUTTON_SIZE)) { + if (ElfInfo::Instance().FirmwareVer() < ElfInfo::FW_45) { + Finish(ButtonId::INVALID, Result::USER_CANCELED); + } else { + Finish(ButtonId::NO); + } + } + } else if (sys_state.show_cancel) { + if (Button("Cancel", BUTTON_SIZE)) { + Finish(ButtonId::INVALID, Result::USER_CANCELED); + } + } + if (first_render || IsKeyPressed(ImGuiKey_GamepadFaceRight)) { + SetItemCurrentNavFocus(); + } + EndGroup(); +} + +void SaveDialogUi::DrawErrorCode() { + const auto& err_state = state->GetState(); + + if (!state->save_list.empty()) { + DrawItem(0, state->save_list.front(), false); + } else if (state->new_item) { + DrawItem(0, *state->new_item, false); + } + + const auto ws = GetWindowSize(); + const auto& msg = err_state.error_msg; + if (!msg.empty()) { + const char* begin = &msg.front(); + const char* end = &msg.back() + 1; + DrawCenteredText(begin, end, GetContentRegionAvail() - ImVec2{0.0f, FOOTER_HEIGHT}); + } + + SetCursorPos({ + ws.x / 2.0f - BUTTON_SIZE.x / 2.0f, + ws.y - FOOTER_HEIGHT + 5.0f, + }); + if (Button("OK", BUTTON_SIZE)) { + if (ElfInfo::Instance().FirmwareVer() < ElfInfo::FW_45) { + Finish(ButtonId::INVALID, Result::USER_CANCELED); + } else { + Finish(ButtonId::OK); + } + } + if (first_render) { + SetItemCurrentNavFocus(); + } +} + +void SaveDialogUi::DrawProgressBar() { + const auto& bar_state = state->GetState(); + + const auto ws = GetWindowSize(); + + if (!state->save_list.empty()) { + DrawItem(0, state->save_list.front(), false); + } else if (state->new_item) { + DrawItem(0, *state->new_item, false); + } + + const auto& msg = bar_state.msg; + if (!msg.empty()) { + const char* begin = &msg.front(); + const char* end = &msg.back() + 1; + DrawCenteredText(begin, end, GetContentRegionAvail() - ImVec2{0.0f, FOOTER_HEIGHT}); + } + + SetCursorPos({ + ws.x * ((1 - PROGRESS_BAR_WIDTH) / 2.0f), + ws.y - FOOTER_HEIGHT + 5.0f, + }); + + ProgressBar(static_cast(bar_state.progress) / 100.0f, + {PROGRESS_BAR_WIDTH * ws.x, BUTTON_SIZE.y}); +} +}; // namespace Libraries::SaveData::Dialog \ No newline at end of file diff --git a/src/core/libraries/save_data/dialog/savedatadialog_ui.h b/src/core/libraries/save_data/dialog/savedatadialog_ui.h new file mode 100644 index 000000000..3f414470f --- /dev/null +++ b/src/core/libraries/save_data/dialog/savedatadialog_ui.h @@ -0,0 +1,319 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include +#include + +#include "core/file_format/psf.h" +#include "core/libraries/save_data/savedata.h" +#include "core/libraries/system/commondialog.h" +#include "imgui/imgui_layer.h" +#include "imgui/imgui_texture.h" + +namespace Libraries::SaveData::Dialog { + +using OrbisUserServiceUserId = s32; + +enum class SaveDataDialogMode : u32 { + INVALID = 0, + LIST = 1, + USER_MSG = 2, + SYSTEM_MSG = 3, + ERROR_CODE = 4, + PROGRESS_BAR = 5, +}; + +enum class DialogType : u32 { + SAVE = 1, + LOAD = 2, + DELETE = 3, +}; + +enum class DialogAnimation : u32 { + ON = 0, + OFF = 1, +}; + +enum class ButtonId : u32 { + INVALID = 0, + OK = 1, + YES = 1, + NO = 2, +}; + +enum class ButtonType : u32 { + OK = 0, + YESNO = 1, + NONE = 2, + OKCANCEL = 3, +}; + +enum class UserMessageType : u32 { + NORMAL = 0, + ERROR = 1, +}; + +enum class FocusPos : u32 { + LISTHEAD = 0, + LISTTAIL = 1, + DATAHEAD = 2, + DATATAIL = 3, + DATALTATEST = 4, + DATAOLDEST = 5, + DIRNAME = 6, +}; + +enum class ItemStyle : u32 { + TITLE_DATASIZE_SUBTITLE = 0, + TITLE_SUBTITLE_DATESIZE = 1, + TITLE_DATESIZE = 2, +}; + +enum class SystemMessageType : u32 { + NODATA = 1, + CONFIRM = 2, + OVERWRITE = 3, + NOSPACE = 4, + PROGRESS = 5, + FILE_CORRUPTED = 6, + FINISHED = 7, + NOSPACE_CONTINUABLE = 8, + CORRUPTED_AND_DELETED = 10, + CORRUPTED_AND_CREATED = 11, + CORRUPTED_AND_RESTORE = 13, + TOTAL_SIZE_EXCEEDED = 14, +}; + +enum class ProgressBarType : u32 { + PERCENTAGE = 0, +}; + +enum class ProgressSystemMessageType : u32 { + INVALID = 0, + PROGRESS = 1, + RESTORE = 2, +}; + +enum class OptionBack : u32 { + ENABLE = 0, + DISABLE = 1, +}; + +enum class OrbisSaveDataDialogProgressBarTarget : u32 { + DEFAULT = 0, +}; + +struct AnimationParam { + DialogAnimation userOK; + DialogAnimation userCancel; + std::array _reserved; +}; + +struct SaveDialogNewItem { + const char* title; + void* iconBuf; + size_t iconSize; + std::array _reserved; +}; + +struct SaveDialogItems { + OrbisUserServiceUserId userId; + s32 : 32; + const OrbisSaveDataTitleId* titleId; + const OrbisSaveDataDirName* dirName; + u32 dirNameNum; + s32 : 32; + const SaveDialogNewItem* newItem; + FocusPos focusPos; + s32 : 32; + const OrbisSaveDataDirName* focusPosDirName; + ItemStyle itemStyle; + std::array _reserved; +}; + +struct UserMessageParam { + ButtonType buttonType; + UserMessageType msgType; + const char* msg; + std::array _reserved; +}; + +struct SystemMessageParam { + SystemMessageType msgType; + s32 : 32; + u64 value; + std::array _reserved; +}; + +struct ErrorCodeParam { + u32 errorCode; + std::array _reserved; +}; + +struct ProgressBarParam { + ProgressBarType barType; + s32 : 32; + const char* msg; + ProgressSystemMessageType sysMsgType; + std::array _reserved; +}; + +struct OptionParam { + OptionBack back; + std::array _reserved; +}; + +struct OrbisSaveDataDialogParam { + CommonDialog::BaseParam baseParam; + s32 size; + SaveDataDialogMode mode; + DialogType dispType; + s32 : 32; + AnimationParam* animParam; + SaveDialogItems* items; + UserMessageParam* userMsgParam; + SystemMessageParam* sysMsgParam; + ErrorCodeParam* errorCodeParam; + ProgressBarParam* progressBarParam; + void* userData; + OptionParam* optionParam; + std::array _reserved; +}; + +struct OrbisSaveDataDialogResult { + SaveDataDialogMode mode{}; + CommonDialog::Result result{}; + ButtonId buttonId{}; + s32 : 32; + OrbisSaveDataDirName* dirName; + OrbisSaveDataParam* param; + void* userData; + std::array _reserved; +}; + +struct SaveDialogResult { + SaveDataDialogMode mode{}; + CommonDialog::Result result{CommonDialog::Result::OK}; + ButtonId button_id{ButtonId::INVALID}; + std::string dir_name{}; + PSF param{}; + void* user_data{}; + + void CopyTo(OrbisSaveDataDialogResult& result) const; +}; + +class SaveDialogState { + friend class SaveDialogUi; + +public: + struct UserState { + ButtonType type{}; + UserMessageType msg_type{}; + std::string msg{}; + + UserState(const OrbisSaveDataDialogParam& param); + }; + struct SystemState { + std::string msg{}; + bool hide_ok{}; + bool show_no{}; // Yes instead of OK + bool show_cancel{}; + + bool return_cancel{}; + + SystemState(const SaveDialogState& state, const OrbisSaveDataDialogParam& param); + }; + struct ErrorCodeState { + std::string error_msg{}; + + ErrorCodeState(const OrbisSaveDataDialogParam& param); + }; + struct ProgressBarState { + std::string msg{}; + u32 progress{}; + + ProgressBarState(const SaveDialogState& state, const OrbisSaveDataDialogParam& param); + }; + + struct Item { + std::string dir_name{}; + ImGui::RefCountedTexture icon{}; + + std::string title{}; + std::string subtitle{}; + std::string details{}; + std::string date{}; + std::string size{}; + + std::filesystem::file_time_type last_write{}; + PSF pfo{}; + bool is_corrupted{}; + }; + +private: + SaveDataDialogMode mode{}; + DialogType type{}; + void* user_data{}; + std::optional enable_back{}; + + OrbisUserServiceUserId user_id{}; + std::string title_id{}; + std::vector save_list{}; + std::variant focus_pos{std::monostate{}}; + ItemStyle style{}; + + std::optional new_item{}; + + std::variant state{ + std::monostate{}}; + +public: + explicit SaveDialogState(const OrbisSaveDataDialogParam& param); + + SaveDialogState() = default; + + [[nodiscard]] SaveDataDialogMode GetMode() const { + return mode; + } + + template + [[nodiscard]] T& GetState() { + return std::get(state); + } +}; + +class SaveDialogUi final : public ImGui::Layer { + bool first_render{false}; + SaveDialogState* state{}; + CommonDialog::Status* status{}; + SaveDialogResult* result{}; + + std::recursive_mutex draw_mutex{}; + +public: + explicit SaveDialogUi(SaveDialogState* state = nullptr, CommonDialog::Status* status = nullptr, + SaveDialogResult* result = nullptr); + + ~SaveDialogUi() override; + SaveDialogUi(const SaveDialogUi& other) = delete; + SaveDialogUi(SaveDialogUi&& other) noexcept; + SaveDialogUi& operator=(SaveDialogUi other); + + void Finish(ButtonId buttonId, CommonDialog::Result r = CommonDialog::Result::OK); + + void Draw() override; + +private: + void DrawItem(int id, const SaveDialogState::Item& item, bool clickable = true); + + void DrawList(); + void DrawUser(); + void DrawSystemMessage(); + void DrawErrorCode(); + void DrawProgressBar(); +}; + +}; // namespace Libraries::SaveData::Dialog \ No newline at end of file diff --git a/src/core/libraries/save_data/error_codes.h b/src/core/libraries/save_data/error_codes.h deleted file mode 100644 index a4a1b7a5a..000000000 --- a/src/core/libraries/save_data/error_codes.h +++ /dev/null @@ -1,28 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -constexpr int ORBIS_SAVE_DATA_ERROR_PARAMETER = 0x809F0000; -constexpr int ORBIS_SAVE_DATA_ERROR_NOT_INITIALIZED = 0x809F0001; -constexpr int ORBIS_SAVE_DATA_ERROR_OUT_OF_MEMORY = 0x809F0002; -constexpr int ORBIS_SAVE_DATA_ERROR_BUSY = 0x809F0003; -constexpr int ORBIS_SAVE_DATA_ERROR_NOT_MOUNTED = 0x809F0004; -constexpr int ORBIS_SAVE_DATA_ERROR_NO_PERMISSION = 0x809F0005; -constexpr int ORBIS_SAVE_DATA_ERROR_FINGERPRINT_MISMATCH = 0x809F0006; -constexpr int ORBIS_SAVE_DATA_ERROR_EXISTS = 0x809F0007; -constexpr int ORBIS_SAVE_DATA_ERROR_NOT_FOUND = 0x809F0008; -constexpr int ORBIS_SAVE_DATA_ERROR_NO_SPACE_FS = 0x809F000A; -constexpr int ORBIS_SAVE_DATA_ERROR_INTERNAL = 0x809F000B; -constexpr int ORBIS_SAVE_DATA_ERROR_MOUNT_FULL = 0x809F000C; -constexpr int ORBIS_SAVE_DATA_ERROR_BAD_MOUNTED = 0x809F000D; -constexpr int ORBIS_SAVE_DATA_ERROR_FILE_NOT_FOUND = 0x809F000E; -constexpr int ORBIS_SAVE_DATA_ERROR_BROKEN = 0x809F000F; -constexpr int ORBIS_SAVE_DATA_ERROR_INVALID_LOGIN_USER = 0x809F0011; -constexpr int ORBIS_SAVE_DATA_ERROR_MEMORY_NOT_READY = 0x809F0012; -constexpr int ORBIS_SAVE_DATA_ERROR_BACKUP_BUSY = 0x809F0013; -constexpr int ORBIS_SAVE_DATA_ERROR_NOT_REGIST_CALLBACK = 0x809F0015; -constexpr int ORBIS_SAVE_DATA_ERROR_BUSY_FOR_SAVING = 0x809F0016; -constexpr int ORBIS_SAVE_DATA_ERROR_LIMITATION_OVER = 0x809F0017; -constexpr int ORBIS_SAVE_DATA_ERROR_EVENT_BUSY = 0x809F0018; -constexpr int ORBIS_SAVE_DATA_ERROR_PARAMSFO_TRANSFER_TITLE_ID_NOT_FOUND = 0x809F0019; diff --git a/src/core/libraries/save_data/save_backup.cpp b/src/core/libraries/save_data/save_backup.cpp new file mode 100644 index 000000000..8f7e0d69a --- /dev/null +++ b/src/core/libraries/save_data/save_backup.cpp @@ -0,0 +1,248 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include +#include +#include + +#include + +#include "save_backup.h" +#include "save_instance.h" + +#include "common/logging/log.h" +#include "common/logging/log_entry.h" +#include "common/polyfill_thread.h" +#include "common/thread.h" + +constexpr std::string_view sce_sys = "sce_sys"; // system folder inside save +constexpr std::string_view backup_dir = "sce_backup"; // backup folder +constexpr std::string_view backup_dir_tmp = "sce_backup_tmp"; // in-progress backup folder +constexpr std::string_view backup_dir_old = "sce_backup_old"; // previous backup folder + +namespace fs = std::filesystem; + +namespace Libraries::SaveData::Backup { + +static std::jthread g_backup_thread; +static std::counting_semaphore g_backup_thread_semaphore{0}; + +static std::mutex g_backup_running_mutex; + +static std::mutex g_backup_queue_mutex; +static std::deque g_backup_queue; +static std::deque g_result_queue; + +static std::atomic_int g_backup_progress = 0; +static std::atomic g_backup_status = WorkerStatus::NotStarted; + +static void backup(const std::filesystem::path& dir_name) { + std::unique_lock lk{g_backup_running_mutex}; + if (!fs::exists(dir_name)) { + return; + } + + const auto backup_dir = dir_name / ::backup_dir; + const auto backup_dir_tmp = dir_name / ::backup_dir_tmp; + const auto backup_dir_old = dir_name / ::backup_dir_old; + + fs::remove_all(backup_dir_tmp); + fs::remove_all(backup_dir_old); + + std::vector backup_files; + for (const auto& entry : fs::directory_iterator(dir_name)) { + const auto filename = entry.path().filename(); + if (filename != ::backup_dir) { + backup_files.push_back(entry.path()); + } + } + + g_backup_progress = 0; + + int total_count = static_cast(backup_files.size()); + int current_count = 0; + + fs::create_directory(backup_dir_tmp); + for (const auto& file : backup_files) { + fs::copy(file, backup_dir_tmp / file.filename(), fs::copy_options::recursive); + current_count++; + g_backup_progress = current_count * 100 / total_count; + } + bool has_existing_backup = fs::exists(backup_dir); + if (has_existing_backup) { + fs::rename(backup_dir, backup_dir_old); + } + fs::rename(backup_dir_tmp, backup_dir); + if (has_existing_backup) { + fs::remove_all(backup_dir_old); + } +} + +static void BackupThreadBody() { + Common::SetCurrentThreadName("SaveData_BackupThread"); + while (g_backup_status != WorkerStatus::Stopping) { + g_backup_status = WorkerStatus::Waiting; + + bool wait; + BackupRequest req; + { + std::scoped_lock lk{g_backup_queue_mutex}; + wait = g_backup_queue.empty(); + if (!wait) { + req = g_backup_queue.front(); + } + } + if (wait) { + g_backup_thread_semaphore.acquire(); + { + std::scoped_lock lk{g_backup_queue_mutex}; + if (g_backup_queue.empty()) { + continue; + } + req = g_backup_queue.front(); + } + } + if (req.save_path.empty()) { + break; + } + g_backup_status = WorkerStatus::Running; + + LOG_INFO(Lib_SaveData, "Backing up the following directory: {}", req.save_path.string()); + try { + backup(req.save_path); + } catch (const std::filesystem::filesystem_error& err) { + LOG_ERROR(Lib_SaveData, "Failed to backup {}: {}", req.save_path.string(), err.what()); + } + LOG_DEBUG(Lib_SaveData, "Backing up the following directory: {} finished", + req.save_path.string()); + { + std::scoped_lock lk{g_backup_queue_mutex}; + g_backup_queue.front().done = true; + } + std::this_thread::sleep_for(std::chrono::seconds(10)); // Don't backup too often + { + std::scoped_lock lk{g_backup_queue_mutex}; + g_backup_queue.pop_front(); + g_result_queue.push_back(std::move(req)); + if (g_result_queue.size() > 20) { + g_result_queue.pop_front(); + } + } + } + g_backup_status = WorkerStatus::NotStarted; +} + +void StartThread() { + if (g_backup_status != WorkerStatus::NotStarted) { + return; + } + LOG_DEBUG(Lib_SaveData, "Starting backup thread"); + g_backup_status = WorkerStatus::Waiting; + g_backup_thread = std::jthread{BackupThreadBody}; +} + +void StopThread() { + if (g_backup_status == WorkerStatus::NotStarted || g_backup_status == WorkerStatus::Stopping) { + return; + } + LOG_DEBUG(Lib_SaveData, "Stopping backup thread"); + { + std::scoped_lock lk{g_backup_queue_mutex}; + g_backup_queue.emplace_back(BackupRequest{}); + } + g_backup_thread_semaphore.release(); + g_backup_status = WorkerStatus::Stopping; +} + +bool NewRequest(OrbisUserServiceUserId user_id, std::string_view title_id, + std::string_view dir_name, OrbisSaveDataEventType origin) { + auto save_path = SaveInstance::MakeDirSavePath(user_id, title_id, dir_name); + + if (g_backup_status != WorkerStatus::Waiting && g_backup_status != WorkerStatus::Running) { + LOG_ERROR(Lib_SaveData, "Called backup while status is {}. Backup request to {} ignored", + magic_enum::enum_name(g_backup_status.load()), save_path.string()); + return false; + } + { + std::scoped_lock lk{g_backup_queue_mutex}; + for (const auto& it : g_backup_queue) { + if (it.dir_name == dir_name) { + LOG_TRACE(Lib_SaveData, "Backup request to {} ignored. Already queued", dir_name); + return false; + } + } + g_backup_queue.push_back(BackupRequest{ + .user_id = user_id, + .title_id = std::string{title_id}, + .dir_name = std::string{dir_name}, + .origin = origin, + .save_path = std::move(save_path), + }); + } + g_backup_thread_semaphore.release(); + return true; +} + +bool Restore(const std::filesystem::path& save_path) { + LOG_INFO(Lib_SaveData, "Restoring backup for {}", save_path.string()); + std::unique_lock lk{g_backup_running_mutex}; + if (!fs::exists(save_path) || !fs::exists(save_path / backup_dir)) { + return false; + } + for (const auto& entry : fs::directory_iterator(save_path)) { + const auto filename = entry.path().filename(); + if (filename != backup_dir) { + fs::remove_all(entry.path()); + } + } + + for (const auto& entry : fs::directory_iterator(save_path / backup_dir)) { + const auto filename = entry.path().filename(); + fs::copy(entry.path(), save_path / filename, fs::copy_options::recursive); + } + + return true; +} + +WorkerStatus GetWorkerStatus() { + return g_backup_status; +} + +bool IsBackupExecutingFor(const std::filesystem::path& save_path) { + std::scoped_lock lk{g_backup_queue_mutex}; + const auto& it = + std::ranges::find(g_backup_queue, save_path, [](const auto& v) { return v.save_path; }); + return it != g_backup_queue.end() && !it->done; +} + +std::filesystem::path MakeBackupPath(const std::filesystem::path& save_path) { + return save_path / backup_dir; +} + +std::optional PopLastEvent() { + std::scoped_lock lk{g_backup_queue_mutex}; + if (g_result_queue.empty()) { + return std::nullopt; + } + auto req = std::move(g_result_queue.front()); + g_result_queue.pop_front(); + return req; +} + +void PushBackupEvent(BackupRequest&& req) { + std::scoped_lock lk{g_backup_queue_mutex}; + g_result_queue.push_back(std::move(req)); + if (g_result_queue.size() > 20) { + g_result_queue.pop_front(); + } +} + +float GetProgress() { + return static_cast(g_backup_progress) / 100.0f; +} + +void ClearProgress() { + g_backup_progress = 0; +} + +} // namespace Libraries::SaveData::Backup \ No newline at end of file diff --git a/src/core/libraries/save_data/save_backup.h b/src/core/libraries/save_data/save_backup.h new file mode 100644 index 000000000..e49c69f60 --- /dev/null +++ b/src/core/libraries/save_data/save_backup.h @@ -0,0 +1,67 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include + +#include "common/types.h" + +namespace Libraries::SaveData { + +using OrbisUserServiceUserId = s32; + +namespace Backup { + +enum class WorkerStatus { + NotStarted, + Waiting, + Running, + Stopping, +}; + +enum class OrbisSaveDataEventType : u32 { + UMOUNT_BACKUP = 1, + BACKUP = 2, + SAVE_DATA_MEMORY_SYNC = 3, +}; + +struct BackupRequest { + bool done{}; + + OrbisUserServiceUserId user_id{}; + std::string title_id{}; + std::string dir_name{}; + OrbisSaveDataEventType origin{}; + + std::filesystem::path save_path{}; +}; + +// No problem calling this function if the backup thread is already running +void StartThread(); + +void StopThread(); + +bool NewRequest(OrbisUserServiceUserId user_id, std::string_view title_id, + std::string_view dir_name, OrbisSaveDataEventType origin); + +bool Restore(const std::filesystem::path& save_path); + +WorkerStatus GetWorkerStatus(); + +bool IsBackupExecutingFor(const std::filesystem::path& save_path); + +std::filesystem::path MakeBackupPath(const std::filesystem::path& save_path); + +std::optional PopLastEvent(); + +void PushBackupEvent(BackupRequest&& req); + +float GetProgress(); + +void ClearProgress(); + +} // namespace Backup + +} // namespace Libraries::SaveData diff --git a/src/core/libraries/save_data/save_instance.cpp b/src/core/libraries/save_data/save_instance.cpp new file mode 100644 index 000000000..2624ca363 --- /dev/null +++ b/src/core/libraries/save_data/save_instance.cpp @@ -0,0 +1,228 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include + +#include + +#include "common/assert.h" +#include "common/config.h" +#include "common/path_util.h" +#include "common/singleton.h" +#include "core/file_sys/fs.h" +#include "save_instance.h" + +constexpr u32 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(); + +namespace fs = std::filesystem; + +// clang-format off +static const std::unordered_map default_title = { + {"ja_JP", "セーブデータ"}, + {"en", "Saved Data"}, + {"fr", "Données sauvegardées"}, + {"es_ES", "Datos guardados"}, + {"de", "Gespeicherte Daten"}, + {"it", "Dati salvati"}, + {"nl", "Opgeslagen data"}, + {"pt_PT", "Dados guardados"}, + {"ru", "Сохраненные данные"}, + {"ko_KR", "저장 데이터"}, + {"zh_CN", "保存数据"}, + {"fi", "Tallennetut tiedot"}, + {"sv_SE", "Sparade data"}, + {"da_DK", "Gemte data"}, + {"no_NO", "Lagrede data"}, + {"pl_PL", "Zapisane dane"}, + {"pt_BR", "Dados salvos"}, + {"tr_TR", "Kayıtlı Veriler"}, +}; +// clang-format on + +namespace Libraries::SaveData { + +std::filesystem::path SaveInstance::MakeTitleSavePath(OrbisUserServiceUserId user_id, + std::string_view game_serial) { + return Common::FS::GetUserPath(Common::FS::PathType::SaveDataDir) / std::to_string(user_id) / + game_serial; +} + +std::filesystem::path SaveInstance::MakeDirSavePath(OrbisUserServiceUserId user_id, + std::string_view game_serial, + std::string_view dir_name) { + return Common::FS::GetUserPath(Common::FS::PathType::SaveDataDir) / std::to_string(user_id) / + 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()); + } + if (max_blocks <= 0) { + max_blocks = OrbisSaveDataBlocksMax; + } + + return max_blocks; +} + +std::filesystem::path SaveInstance::GetParamSFOPath(const std::filesystem::path& dir_path) { + return dir_path / sce_sys / "param.sfo"; +} + +void SaveInstance::SetupDefaultParamSFO(PSF& param_sfo, std::string dir_name, + std::string game_serial) { + std::string locale = Config::getEmulatorLanguage(); + if (!default_title.contains(locale)) { + locale = "en"; + } + +#define P(type, key, ...) param_sfo.Add##type(std::string{key}, __VA_ARGS__) + // TODO Link with user service + P(Binary, SaveParams::ACCOUNT_ID, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}); + P(String, SaveParams::MAINTITLE, default_title.at(locale)); + P(String, SaveParams::SUBTITLE, ""); + P(String, SaveParams::DETAIL, ""); + 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)); +#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) { + ASSERT(slot_num >= 0 && slot_num < 16); + + save_path = MakeDirSavePath(user_id, game_serial, dir_name); + + const auto sce_sys_path = save_path / sce_sys; + param_sfo_path = sce_sys_path / "param.sfo"; + corrupt_file_path = sce_sys_path / "corrupted"; + + mount_point = "/savedata" + std::to_string(slot_num); + + this->exists = fs::exists(param_sfo_path); + this->mounted = g_mnt->GetMount(mount_point) != nullptr; +} + +SaveInstance::~SaveInstance() { + if (mounted) { + Umount(); + } +} +SaveInstance::SaveInstance(SaveInstance&& other) noexcept { + if (this == &other) + return; + *this = std::move(other); +} + +SaveInstance& SaveInstance::operator=(SaveInstance&& other) noexcept { + if (this == &other) + return *this; + slot_num = other.slot_num; + user_id = other.user_id; + game_serial = std::move(other.game_serial); + dir_name = std::move(other.dir_name); + save_path = std::move(other.save_path); + param_sfo_path = std::move(other.param_sfo_path); + corrupt_file_path = std::move(other.corrupt_file_path); + corrupt_file = std::move(other.corrupt_file); + param_sfo = std::move(other.param_sfo); + mount_point = std::move(other.mount_point); + max_blocks = other.max_blocks; + exists = other.exists; + mounted = other.mounted; + read_only = other.read_only; + + other.mounted = false; + + return *this; +} + +void SaveInstance::SetupAndMount(bool read_only, bool copy_icon, bool ignore_corrupt) { + if (mounted) { + UNREACHABLE_MSG("Save instance is already mounted"); + } + this->exists = fs::exists(param_sfo_path); // check again just in case + if (!exists) { + CreateFiles(); + if (copy_icon) { + const auto& src_icon = g_mnt->GetHostPath("/app0/sce_sys/save_data.png"); + if (fs::exists(src_icon)) { + fs::copy_file(src_icon, GetIconPath()); + } + } + exists = true; + } else { + if (!ignore_corrupt && fs::exists(corrupt_file_path)) { + throw std::filesystem::filesystem_error( + "Corrupted save data", corrupt_file_path, + std::make_error_code(std::errc::illegal_byte_sequence)); + } + if (!param_sfo.Open(param_sfo_path)) { + throw std::filesystem::filesystem_error( + "Failed to read param.sfo", param_sfo_path, + std::make_error_code(std::errc::illegal_byte_sequence)); + } + } + + if (!ignore_corrupt && !read_only) { + int err = corrupt_file.Open(corrupt_file_path, Common::FS::FileAccessMode::Write); + if (err != 0) { + throw std::filesystem::filesystem_error( + "Failed to open corrupted file", corrupt_file_path, + std::make_error_code(std::errc::illegal_byte_sequence)); + } + } + + max_blocks = GetMaxBlocks(save_path); + + g_mnt->Mount(save_path, mount_point, read_only); + mounted = true; + this->read_only = read_only; +} + +void SaveInstance::Umount() { + if (!mounted) { + UNREACHABLE_MSG("Save instance is not mounted"); + return; + } + mounted = false; + 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)); + } + param_sfo = PSF(); + + corrupt_file.Close(); + fs::remove(corrupt_file_path); + g_mnt->Unmount(save_path, mount_point); +} + +void SaveInstance::CreateFiles() { + const auto sce_sys_dir = save_path / sce_sys; + fs::create_directories(sce_sys_dir); + + SetupDefaultParamSFO(param_sfo, dir_name, game_serial); + + 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 new file mode 100644 index 000000000..f07011047 --- /dev/null +++ b/src/core/libraries/save_data/save_instance.h @@ -0,0 +1,138 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include + +#include "common/io_file.h" +#include "core/file_format/psf.h" + +namespace Core::FileSys { +class MntPoints; +} + +namespace Libraries::SaveData { + +// Used constexpr to easily use as string +namespace SaveParams { +constexpr std::string_view ACCOUNT_ID = "ACCOUNT_ID"; +constexpr std::string_view ATTRIBUTE = "ATTRIBUTE"; +constexpr std::string_view CATEGORY = "CATEGORY"; +constexpr std::string_view DETAIL = "DETAIL"; +constexpr std::string_view FORMAT = "FORMAT"; +constexpr std::string_view MAINTITLE = "MAINTITLE"; +constexpr std::string_view PARAMS = "PARAMS"; +constexpr std::string_view SAVEDATA_BLOCKS = "SAVEDATA_BLOCKS"; +constexpr std::string_view SAVEDATA_DIRECTORY = "SAVEDATA_DIRECTORY"; +constexpr std::string_view SAVEDATA_LIST_PARAM = "SAVEDATA_LIST_PARAM"; +constexpr std::string_view SUBTITLE = "SUBTITLE"; +constexpr std::string_view TITLE_ID = "TITLE_ID"; +} // namespace SaveParams + +using OrbisUserServiceUserId = s32; + +class SaveInstance { + int slot_num{}; + int user_id{}; + std::string game_serial; + std::string dir_name; + + std::filesystem::path save_path; + std::filesystem::path param_sfo_path; + std::filesystem::path corrupt_file_path; + + Common::FS::IOFile corrupt_file; + + PSF param_sfo; + std::string mount_point; + + int max_blocks{}; + bool exists{}; + bool mounted{}; + bool read_only{}; + +public: + // Location of all save data for a title + static std::filesystem::path MakeTitleSavePath(OrbisUserServiceUserId user_id, + std::string_view game_serial); + + // Location of a specific save data directory + static std::filesystem::path MakeDirSavePath(OrbisUserServiceUserId user_id, + std::string_view game_serial, + std::string_view dir_name); + + static int GetMaxBlocks(const std::filesystem::path& save_path); + + // Get param.sfo path from a dir_path generated by MakeDirSavePath + static std::filesystem::path GetParamSFOPath(const std::filesystem::path& dir_path); + + static void SetupDefaultParamSFO(PSF& param_sfo, std::string dir_name, std::string game_serial); + + explicit SaveInstance(int slot_num, OrbisUserServiceUserId user_id, std::string game_serial, + std::string_view dir_name, int max_blocks = 0); + + ~SaveInstance(); + + SaveInstance(const SaveInstance& other) = delete; + SaveInstance(SaveInstance&& other) noexcept; + + SaveInstance& operator=(const SaveInstance& other) = delete; + SaveInstance& operator=(SaveInstance&& other) noexcept; + + void SetupAndMount(bool read_only = false, bool copy_icon = false, bool ignore_corrupt = false); + + void Umount(); + + [[nodiscard]] std::filesystem::path GetIconPath() const noexcept { + return save_path / "sce_sys" / "icon0.png"; + } + + [[nodiscard]] bool Exists() const noexcept { + return exists; + } + + [[nodiscard]] OrbisUserServiceUserId GetUserId() const noexcept { + return user_id; + } + + [[nodiscard]] std::string_view GetTitleId() const noexcept { + return game_serial; + } + + [[nodiscard]] const std::string& GetDirName() const noexcept { + return dir_name; + } + + [[nodiscard]] const std::filesystem::path& GetSavePath() const noexcept { + return save_path; + } + + [[nodiscard]] const PSF& GetParamSFO() const noexcept { + return param_sfo; + } + + [[nodiscard]] PSF& GetParamSFO() noexcept { + return param_sfo; + } + + [[nodiscard]] const std::string& GetMountPoint() const noexcept { + return mount_point; + } + + [[nodiscard]] int GetMaxBlocks() const noexcept { + return max_blocks; + } + + [[nodiscard]] bool Mounted() const noexcept { + return mounted; + } + + [[nodiscard]] bool IsReadOnly() const noexcept { + return read_only; + } + + void CreateFiles(); +}; + +} // namespace Libraries::SaveData diff --git a/src/core/libraries/save_data/save_memory.cpp b/src/core/libraries/save_data/save_memory.cpp new file mode 100644 index 000000000..6a685ee39 --- /dev/null +++ b/src/core/libraries/save_data/save_memory.cpp @@ -0,0 +1,287 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "save_memory.h" + +#include +#include +#include +#include +#include + +#include + +#include "common/assert.h" +#include "common/logging/log.h" +#include "common/polyfill_thread.h" +#include "common/singleton.h" +#include "common/thread.h" +#include "core/file_sys/fs.h" +#include "save_instance.h" + +using Common::FS::IOFile; +namespace fs = std::filesystem; + +constexpr std::string_view sce_sys = "sce_sys"; // system folder inside save +constexpr std::string_view DirnameSaveDataMemory = "sce_sdmemory"; +constexpr std::string_view FilenameSaveDataMemory = "memory.dat"; + +namespace Libraries::SaveData::SaveMemory { + +static Core::FileSys::MntPoints* g_mnt = Common::Singleton::Instance(); + +static OrbisUserServiceUserId g_user_id{}; +static std::string g_game_serial{}; +static std::filesystem::path g_save_path{}; +static std::filesystem::path g_param_sfo_path{}; +static PSF g_param_sfo; + +static bool g_save_memory_initialized = false; +static std::mutex g_saving_memory_mutex; +static std::vector g_save_memory; + +static std::filesystem::path g_icon_path; +static std::vector g_icon_memory; + +static std::condition_variable g_trigger_save_memory; +static std::atomic_bool g_saving_memory = false; +static std::atomic_bool g_save_event = false; +static std::jthread g_save_memory_thread; + +static std::atomic_bool g_memory_dirty = false; +static std::atomic_bool g_param_dirty = false; +static std::atomic_bool g_icon_dirty = false; + +static void SaveFileSafe(void* buf, size_t count, const std::filesystem::path& path) { + const auto& dir = path.parent_path(); + const auto& name = path.filename(); + const auto tmp_path = dir / (name.string() + ".tmp"); + + IOFile file(tmp_path, Common::FS::FileAccessMode::Write); + file.WriteRaw(buf, count); + file.Close(); + + fs::remove(path); + fs::rename(tmp_path, path); +} + +[[noreturn]] void SaveThreadLoop() { + Common::SetCurrentThreadName("SaveData_SaveDataMemoryThread"); + std::mutex mtx; + while (true) { + { + std::unique_lock lk{mtx}; + g_trigger_save_memory.wait(lk); + } + // Save the memory + g_saving_memory = true; + std::scoped_lock lk{g_saving_memory_mutex}; + try { + LOG_DEBUG(Lib_SaveData, "Saving save data memory {}", g_save_path.string()); + + if (g_memory_dirty) { + g_memory_dirty = false; + SaveFileSafe(g_save_memory.data(), g_save_memory.size(), + g_save_path / FilenameSaveDataMemory); + } + if (g_param_dirty) { + g_param_dirty = false; + static std::vector buf; + g_param_sfo.Encode(buf); + SaveFileSafe(buf.data(), buf.size(), g_param_sfo_path); + } + if (g_icon_dirty) { + g_icon_dirty = false; + SaveFileSafe(g_icon_memory.data(), g_icon_memory.size(), g_icon_path); + } + + if (g_save_event) { + Backup::PushBackupEvent(Backup::BackupRequest{ + .user_id = g_user_id, + .title_id = g_game_serial, + .dir_name = std::string{DirnameSaveDataMemory}, + .origin = Backup::OrbisSaveDataEventType::SAVE_DATA_MEMORY_SYNC, + .save_path = g_save_path, + }); + g_save_event = false; + } + } catch (const fs::filesystem_error& e) { + LOG_ERROR(Lib_SaveData, "Failed to save save data memory: {}", e.what()); + MsgDialog::ShowMsgDialog(MsgDialog::MsgDialogState{ + MsgDialog::MsgDialogState::UserState{ + .type = MsgDialog::ButtonType::OK, + .msg = fmt::format("Failed to save save data memory.\nCode: <{}>\n{}", + e.code().message(), e.what()), + }, + }); + } + g_saving_memory = false; + } +} + +void SetDirectories(OrbisUserServiceUserId user_id, std::string _game_serial) { + g_user_id = user_id; + g_game_serial = std::move(_game_serial); + g_save_path = SaveInstance::MakeDirSavePath(user_id, g_game_serial, DirnameSaveDataMemory); + g_param_sfo_path = SaveInstance::GetParamSFOPath(g_save_path); + g_param_sfo = PSF(); + g_icon_path = g_save_path / sce_sys / "icon0.png"; +} + +const std::filesystem::path& GetSavePath() { + return g_save_path; +} + +size_t CreateSaveMemory(size_t memory_size) { + size_t existed_size = 0; + + static std::once_flag init_save_thread_flag; + std::call_once(init_save_thread_flag, + [] { g_save_memory_thread = std::jthread{SaveThreadLoop}; }); + + g_save_memory.resize(memory_size); + SaveInstance::SetupDefaultParamSFO(g_param_sfo, std::string{DirnameSaveDataMemory}, + g_game_serial); + + g_save_memory_initialized = true; + + if (!fs::exists(g_param_sfo_path)) { + // Create save memory + fs::create_directories(g_save_path / sce_sys); + + IOFile memory_file{g_save_path / FilenameSaveDataMemory, Common::FS::FileAccessMode::Write}; + bool ok = memory_file.SetSize(memory_size); + if (!ok) { + LOG_ERROR(Lib_SaveData, "Failed to set memory size"); + throw std::filesystem::filesystem_error( + "Failed to set save memory size", g_save_path / FilenameSaveDataMemory, + std::make_error_code(std::errc::no_space_on_device)); + } + memory_file.Close(); + } else { + // Load save memory + + bool ok = g_param_sfo.Open(g_param_sfo_path); + if (!ok) { + LOG_ERROR(Lib_SaveData, "Failed to open SFO at {}", g_param_sfo_path.string()); + throw std::filesystem::filesystem_error( + "failed to open SFO", g_param_sfo_path, + std::make_error_code(std::errc::illegal_byte_sequence)); + } + + IOFile memory_file{g_save_path / FilenameSaveDataMemory, Common::FS::FileAccessMode::Read}; + if (!memory_file.IsOpen()) { + LOG_ERROR(Lib_SaveData, "Failed to open save memory"); + throw std::filesystem::filesystem_error( + "failed to open save memory", g_save_path / FilenameSaveDataMemory, + std::make_error_code(std::errc::permission_denied)); + } + size_t save_size = memory_file.GetSize(); + existed_size = save_size; + memory_file.Seek(0); + memory_file.ReadRaw(g_save_memory.data(), std::min(save_size, memory_size)); + memory_file.Close(); + } + + return existed_size; +} + +void SetIcon(void* buf, size_t buf_size) { + if (buf == nullptr) { + const auto& src_icon = g_mnt->GetHostPath("/app0/sce_sys/save_data.png"); + if (fs::exists(src_icon)) { + fs::copy_file(src_icon, g_icon_path); + } + IOFile file(g_icon_path, Common::FS::FileAccessMode::Read); + size_t size = file.GetSize(); + file.Seek(0); + g_icon_memory.resize(size); + file.ReadRaw(g_icon_memory.data(), size); + file.Close(); + } else { + g_icon_memory.resize(buf_size); + std::memcpy(g_icon_memory.data(), buf, buf_size); + IOFile file(g_icon_path, Common::FS::FileAccessMode::Append); + file.Seek(0); + file.WriteRaw(g_icon_memory.data(), buf_size); + file.Close(); + } +} + +void WriteIcon(void* buf, size_t buf_size) { + if (buf_size != g_icon_memory.size()) { + g_icon_memory.resize(buf_size); + } + std::memcpy(g_icon_memory.data(), buf, buf_size); + g_icon_dirty = true; +} + +bool IsSaveMemoryInitialized() { + return g_save_memory_initialized; +} + +PSF& GetParamSFO() { + return g_param_sfo; +} + +std::span GetIcon() { + return {g_icon_memory}; +} + +void SaveSFO(bool sync) { + if (!sync) { + g_param_dirty = true; + return; + } + const bool ok = g_param_sfo.Encode(g_param_sfo_path); + if (!ok) { + LOG_ERROR(Lib_SaveData, "Failed to encode param.sfo"); + throw std::filesystem::filesystem_error("Failed to write param.sfo", g_param_sfo_path, + std::make_error_code(std::errc::permission_denied)); + } +} +bool IsSaving() { + return g_saving_memory; +} + +bool TriggerSaveWithoutEvent() { + if (g_saving_memory) { + return false; + } + g_trigger_save_memory.notify_one(); + return true; +} + +bool TriggerSave() { + if (g_saving_memory) { + return false; + } + g_save_event = true; + g_trigger_save_memory.notify_one(); + return true; +} + +void ReadMemory(void* buf, size_t buf_size, int64_t offset) { + std::scoped_lock lk{g_saving_memory_mutex}; + if (offset > g_save_memory.size()) { + UNREACHABLE_MSG("ReadMemory out of bounds"); + } + if (offset + buf_size > g_save_memory.size()) { + UNREACHABLE_MSG("ReadMemory out of bounds"); + } + std::memcpy(buf, g_save_memory.data() + offset, buf_size); +} + +void WriteMemory(void* buf, size_t buf_size, int64_t offset) { + std::scoped_lock lk{g_saving_memory_mutex}; + if (offset > g_save_memory.size()) { + UNREACHABLE_MSG("WriteMemory out of bounds"); + } + if (offset + buf_size > g_save_memory.size()) { + UNREACHABLE_MSG("WriteMemory out of bounds"); + } + std::memcpy(g_save_memory.data() + offset, buf, buf_size); + g_memory_dirty = true; +} + +} // namespace Libraries::SaveData::SaveMemory \ No newline at end of file diff --git a/src/core/libraries/save_data/save_memory.h b/src/core/libraries/save_data/save_memory.h new file mode 100644 index 000000000..04eeaa652 --- /dev/null +++ b/src/core/libraries/save_data/save_memory.h @@ -0,0 +1,49 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include "save_backup.h" + +class PSF; + +namespace Libraries::SaveData { +using OrbisUserServiceUserId = s32; +} + +namespace Libraries::SaveData::SaveMemory { + +void SetDirectories(OrbisUserServiceUserId user_id, std::string game_serial); + +[[nodiscard]] const std::filesystem::path& GetSavePath(); + +// returns the size of the existed save memory +size_t CreateSaveMemory(size_t memory_size); + +// Initialize the icon. Set buf to null to read the standard icon. +void SetIcon(void* buf, size_t buf_size); + +// Update the icon +void WriteIcon(void* buf, size_t buf_size); + +[[nodiscard]] bool IsSaveMemoryInitialized(); + +[[nodiscard]] PSF& GetParamSFO(); + +[[nodiscard]] std::span GetIcon(); + +// Save now or wait for the background thread to save +void SaveSFO(bool sync = false); + +[[nodiscard]] bool IsSaving(); + +bool TriggerSaveWithoutEvent(); + +bool TriggerSave(); + +void ReadMemory(void* buf, size_t buf_size, int64_t offset); + +void WriteMemory(void* buf, size_t buf_size, int64_t offset); + +} // namespace Libraries::SaveData::SaveMemory \ No newline at end of file diff --git a/src/core/libraries/save_data/savedata.cpp b/src/core/libraries/save_data/savedata.cpp index 52349b355..d62c1b9a1 100644 --- a/src/core/libraries/save_data/savedata.cpp +++ b/src/core/libraries/save_data/savedata.cpp @@ -4,30 +4,552 @@ #include #include +#include +#include + #include "common/assert.h" +#include "common/cstring.h" +#include "common/elf_info.h" +#include "common/enum.h" #include "common/logging/log.h" #include "common/path_util.h" -#include "common/singleton.h" +#include "common/string_util.h" #include "core/file_format/psf.h" #include "core/file_sys/fs.h" #include "core/libraries/error_codes.h" #include "core/libraries/libs.h" #include "core/libraries/save_data/savedata.h" -#include "error_codes.h" +#include "core/libraries/system/msgdialog.h" +#include "save_backup.h" +#include "save_instance.h" +#include "save_memory.h" + +namespace fs = std::filesystem; +namespace chrono = std::chrono; + +using Common::CString; +using Common::ElfInfo; namespace Libraries::SaveData { -bool is_rw_mode = false; -static constexpr std::string_view g_mount_point = "/savedata0"; // temp mount point (todo) -std::string game_serial; + +enum class Error : u32 { + OK = 0, + USER_SERVICE_NOT_INITIALIZED = 0x80960002, + PARAMETER = 0x809F0000, + NOT_INITIALIZED = 0x809F0001, + OUT_OF_MEMORY = 0x809F0002, + BUSY = 0x809F0003, + NOT_MOUNTED = 0x809F0004, + EXISTS = 0x809F0007, + NOT_FOUND = 0x809F0008, + NO_SPACE_FS = 0x809F000A, + INTERNAL = 0x809F000B, + MOUNT_FULL = 0x809F000C, + BAD_MOUNTED = 0x809F000D, + BROKEN = 0x809F000F, + INVALID_LOGIN_USER = 0x809F0011, + MEMORY_NOT_READY = 0x809F0012, + BACKUP_BUSY = 0x809F0013, + BUSY_FOR_SAVING = 0x809F0016, +}; + +enum class OrbisSaveDataSaveDataMemoryOption : u32 { + NONE = 0, + SET_PARAM = 1 << 0, + DOUBLE_BUFFER = 1 << 1, + UNLOCK_LIMITATIONS = 1 << 2, +}; + +using OrbisUserServiceUserId = s32; +using OrbisSaveDataBlocks = u64; + +constexpr u32 OrbisSaveDataBlockSize = 32768; // 32 KiB +constexpr u32 OrbisSaveDataBlocksMin2 = 96; // 3MiB +constexpr u32 OrbisSaveDataBlocksMax = 32768; // 1 GiB + +// Maximum size for a mount point "/savedataN", where N is a number +constexpr size_t OrbisSaveDataMountPointDataMaxsize = 16; + +constexpr size_t OrbisSaveDataFingerprintDataSize = 65; // Maximum fingerprint size + +enum class OrbisSaveDataMountMode : u32 { + RDONLY = 1 << 0, + RDWR = 1 << 1, + CREATE = 1 << 2, + DESTRUCT_OFF = 1 << 3, + COPY_ICON = 1 << 4, + CREATE2 = 1 << 5, +}; +DECLARE_ENUM_FLAG_OPERATORS(OrbisSaveDataMountMode); + +enum class OrbisSaveDataMountStatus : u32 { + NOTHING = 0, + CREATED = 1, +}; + +enum class OrbisSaveDataParamType : u32 { + ALL = 0, + TITLE = 1, + SUB_TITLE = 2, + DETAIL = 3, + USER_PARAM = 4, + MTIME = 5, +}; + +enum class OrbisSaveDataSortKey : u32 { + DIRNAME = 0, + USER_PARAM = 1, + BLOCKS = 2, + MTIME = 3, + FREE_BLOCKS = 5, +}; + +enum class OrbisSaveDataSortOrder : u32 { + ASCENT = 0, + DESCENT = 1, +}; + +struct OrbisSaveDataFingerprint { + CString data; + std::array _pad; +}; + +struct OrbisSaveDataBackup { + OrbisUserServiceUserId userId; + s32 : 32; + const OrbisSaveDataTitleId* titleId; + const OrbisSaveDataDirName* dirName; + const OrbisSaveDataFingerprint* param; + std::array _reserved; +}; + +struct OrbisSaveDataCheckBackupData { + OrbisUserServiceUserId userId; + s32 : 32; + const OrbisSaveDataTitleId* titleId; + const OrbisSaveDataDirName* dirName; + OrbisSaveDataParam* param; + OrbisSaveDataIcon* icon; + std::array _reserved; +}; + +struct OrbisSaveDataDelete { + OrbisUserServiceUserId userId; + s32 : 32; + const OrbisSaveDataTitleId* titleId; + const OrbisSaveDataDirName* dirName; + u32 _unused; + std::array _reserved; + s32 : 32; +}; + +struct OrbisSaveDataIcon { + void* buf; + size_t bufSize; + size_t dataSize; + std::array _reserved; + + Error LoadIcon(const std::filesystem::path& icon_path) { + try { + const Common::FS::IOFile file(icon_path, Common::FS::FileAccessMode::Read); + dataSize = file.GetSize(); + file.Seek(0); + file.ReadRaw(buf, std::min(bufSize, dataSize)); + } catch (const fs::filesystem_error& e) { + LOG_ERROR(Lib_SaveData, "Failed to load icon: {}", e.what()); + return Error::INTERNAL; + } + return Error::OK; + } +}; + +struct OrbisSaveDataMemoryData { + void* buf; + size_t bufSize; + s64 offset; + u8 _reserved[40]; +}; + +struct OrbisSaveDataMemoryGet2 { + OrbisUserServiceUserId userId; + std::array _pad; + OrbisSaveDataMemoryData* data; + OrbisSaveDataParam* param; + OrbisSaveDataIcon* icon; + std::array _reserved; +}; + +struct OrbisSaveDataMemorySet2 { + OrbisUserServiceUserId userId; + std::array _pad; + const OrbisSaveDataMemoryData* data; + const OrbisSaveDataParam* param; + const OrbisSaveDataIcon* icon; + std::array _reserved; +}; + +struct OrbisSaveDataMemorySetup2 { + OrbisSaveDataSaveDataMemoryOption option; + OrbisUserServiceUserId userId; + size_t memorySize; + size_t iconMemorySize; + // +4.5 + const OrbisSaveDataParam* initParam; + // +4.5 + const OrbisSaveDataIcon* initIcon; + std::array _reserved; +}; + +struct OrbisSaveDataMemorySetupResult { + size_t existedMemorySize; + std::array _reserved; +}; + +struct OrbisSaveDataMemorySync { + OrbisUserServiceUserId userId; + std::array _reserved; +}; + +struct OrbisSaveDataMount2 { + OrbisUserServiceUserId userId; + s32 : 32; + const OrbisSaveDataDirName* dirName; + OrbisSaveDataBlocks blocks; + OrbisSaveDataMountMode mountMode; + std::array _reserved; + s32 : 32; +}; + +struct OrbisSaveDataMount { + OrbisUserServiceUserId userId; + s32 : 32; + const OrbisSaveDataTitleId* titleId; + const OrbisSaveDataDirName* dirName; + const OrbisSaveDataFingerprint* fingerprint; + OrbisSaveDataBlocks blocks; + OrbisSaveDataMountMode mountMode; + std::array _reserved; +}; + +struct OrbisSaveDataMountInfo { + OrbisSaveDataBlocks blocks; + OrbisSaveDataBlocks freeBlocks; + std::array _reserved; +}; + +struct OrbisSaveDataMountPoint { + CString data; +}; + +struct OrbisSaveDataMountResult { + OrbisSaveDataMountPoint mount_point; + OrbisSaveDataBlocks required_blocks; + u32 _unused; + // +4.5 + OrbisSaveDataMountStatus mount_status; + std::array _reserved; + s32 : 32; +}; + +struct OrbisSaveDataRestoreBackupData { + OrbisUserServiceUserId userId; + s32 : 32; + const OrbisSaveDataTitleId* titleId; + const OrbisSaveDataDirName* dirName; + const OrbisSaveDataFingerprint* fingerprint; + u32 _unused; + std::array _reserved; + s32 : 32; +}; + +struct OrbisSaveDataDirNameSearchCond { + OrbisUserServiceUserId userId; + int : 32; + const OrbisSaveDataTitleId* titleId; + const OrbisSaveDataDirName* dirName; + OrbisSaveDataSortKey key; + OrbisSaveDataSortOrder order; + std::array _reserved; +}; + +struct OrbisSaveDataSearchInfo { + u64 blocks; + u64 freeBlocks; + std::array _reserved; +}; + +struct OrbisSaveDataDirNameSearchResult { + u32 hitNum; + int : 32; + OrbisSaveDataDirName* dirNames; + u32 dirNamesNum; + // +1.7 + u32 setNum; + // +1.7 + OrbisSaveDataParam* params; + // +2.5 + OrbisSaveDataSearchInfo* infos; + std::array _reserved; + int : 32; +}; + +struct OrbisSaveDataEventParam { // dummy structure + OrbisSaveDataEventParam() = delete; +}; + +using OrbisSaveDataEventType = Backup::OrbisSaveDataEventType; + +struct OrbisSaveDataEvent { + OrbisSaveDataEventType type; + s32 errorCode; + OrbisUserServiceUserId userId; + std::array _pad; + OrbisSaveDataTitleId titleId; + OrbisSaveDataDirName dirName; + std::array _reserved; +}; + +static bool g_initialized = false; +static std::string g_game_serial; +static u32 g_fw_ver; +static std::array, 16> g_mount_slots; + +static void initialize() { + g_initialized = true; + g_game_serial = ElfInfo::Instance().GameSerial(); + g_fw_ver = ElfInfo::Instance().FirmwareVer(); +} + +// game_00other | game*other + +static bool match(std::string_view str, std::string_view pattern) { + auto str_it = str.begin(); + auto pat_it = pattern.begin(); + while (str_it != str.end() && pat_it != pattern.end()) { + if (*pat_it == '%') { // 0 or more wildcard + for (auto str_wild_it = str_it; str_wild_it <= str.end(); ++str_wild_it) { + if (match({str_wild_it, str.end()}, {pat_it + 1, pattern.end()})) { + return true; + } + } + return false; + } + if (*pat_it == '_') { // 1 character wildcard + ++str_it; + ++pat_it; + } else if (*pat_it != *str_it) { + return false; + } + ++str_it; + ++pat_it; + } + return str_it == str.end() && pat_it == pattern.end(); +} + +static Error setNotInitializedError() { + if (g_fw_ver < ElfInfo::FW_20) { + return Error::INTERNAL; + } + if (g_fw_ver < ElfInfo::FW_25) { + return Error::USER_SERVICE_NOT_INITIALIZED; + } + return Error::NOT_INITIALIZED; +} + +static Error saveDataMount(const OrbisSaveDataMount2* mount_info, + OrbisSaveDataMountResult* mount_result) { + + if (mount_info->userId < 0) { + return Error::INVALID_LOGIN_USER; + } + if (mount_info->dirName == nullptr) { + LOG_INFO(Lib_SaveData, "called without dirName"); + return Error::PARAMETER; + } + + // check backup status + { + const auto save_path = SaveInstance::MakeDirSavePath(mount_info->userId, g_game_serial, + mount_info->dirName->data); + if (Backup::IsBackupExecutingFor(save_path) && g_fw_ver) { + return Error::BACKUP_BUSY; + } + } + + auto mount_mode = mount_info->mountMode; + const bool is_ro = True(mount_mode & OrbisSaveDataMountMode::RDONLY); + + const bool create = True(mount_mode & OrbisSaveDataMountMode::CREATE); + const bool create_if_not_exist = + True(mount_mode & OrbisSaveDataMountMode::CREATE2) && g_fw_ver >= ElfInfo::FW_45; + ASSERT(!create || !create_if_not_exist); // Can't have both + + const bool copy_icon = True(mount_mode & OrbisSaveDataMountMode::COPY_ICON); + + const bool ignore_corrupt = + True(mount_mode & OrbisSaveDataMountMode::DESTRUCT_OFF) || g_fw_ver < ElfInfo::FW_16; + + const std::string_view dir_name{mount_info->dirName->data}; + + // find available mount point + int slot_num = -1; + for (size_t i = 0; i < g_mount_slots.size(); i++) { + const auto& instance = g_mount_slots[i]; + if (instance.has_value()) { + const auto& slot_name = instance->GetDirName(); + if (slot_name == dir_name) { + return Error::BUSY; + } + } else { + slot_num = static_cast(i); + break; + } + } + if (slot_num == -1) { + return Error::MOUNT_FULL; + } + + SaveInstance save_instance{slot_num, mount_info->userId, g_game_serial, dir_name, + (int)mount_info->blocks}; + + if (save_instance.Mounted()) { + UNREACHABLE_MSG("Save instance should not be mounted"); + } + + if (!create && !create_if_not_exist && !save_instance.Exists()) { + return Error::NOT_FOUND; + } + if (create && save_instance.Exists()) { + return Error::EXISTS; + } + + bool to_be_created = !save_instance.Exists(); + + if (to_be_created) { // Check size + + if (mount_info->blocks < OrbisSaveDataBlocksMin2 || + mount_info->blocks > OrbisSaveDataBlocksMax) { + LOG_INFO(Lib_SaveData, "called with invalid block size"); + } + + const auto root_save = Common::FS::GetUserPath(Common::FS::PathType::SaveDataDir); + fs::create_directories(root_save); + const auto available = fs::space(root_save).available; + + auto requested_size = mount_info->blocks * OrbisSaveDataBlockSize; + if (requested_size > available) { + mount_result->required_blocks = (requested_size - available) / OrbisSaveDataBlockSize; + return Error::NO_SPACE_FS; + } + } + + try { + save_instance.SetupAndMount(is_ro, copy_icon, ignore_corrupt); + } catch (const fs::filesystem_error& e) { + if (e.code() == std::errc::illegal_byte_sequence) { + LOG_ERROR(Lib_SaveData, "Corrupted save data"); + return Error::BROKEN; + } + if (e.code() == std::errc::no_space_on_device) { + return Error::NO_SPACE_FS; + } + LOG_ERROR(Lib_SaveData, "Failed to mount save data: {}", e.what()); + return Error::INTERNAL; + } + + mount_result->mount_point.data.FromString(save_instance.GetMountPoint()); + + if (g_fw_ver >= ElfInfo::FW_45) { + mount_result->mount_status = create_if_not_exist && to_be_created + ? OrbisSaveDataMountStatus::CREATED + : OrbisSaveDataMountStatus::NOTHING; + } + + g_mount_slots[slot_num].emplace(std::move(save_instance)); + + return Error::OK; +} + +static Error Umount(const OrbisSaveDataMountPoint* mountPoint, bool call_backup = false) { + if (!g_initialized) { + LOG_INFO(Lib_SaveData, "called without initialize"); + return setNotInitializedError(); + } + if (mountPoint == nullptr) { + LOG_INFO(Lib_SaveData, "called with invalid parameter"); + return Error::PARAMETER; + } + LOG_DEBUG(Lib_SaveData, "Umount mountPoint:{}", mountPoint->data.to_view()); + const std::string_view mount_point_str{mountPoint->data}; + for (auto& instance : g_mount_slots) { + if (instance.has_value()) { + const auto& slot_name = instance->GetMountPoint(); + if (slot_name == mount_point_str) { + if (call_backup) { + Backup::StartThread(); + Backup::NewRequest(instance->GetUserId(), instance->GetTitleId(), + instance->GetDirName(), + OrbisSaveDataEventType::UMOUNT_BACKUP); + } + // TODO: check if is busy + instance->Umount(); + instance.reset(); + return Error::OK; + } + } + } + return Error::NOT_FOUND; +} + +void OrbisSaveDataParam::FromSFO(const PSF& sfo) { + memset(this, 0, sizeof(OrbisSaveDataParam)); + title.FromString(sfo.GetString(SaveParams::MAINTITLE).value_or("Unknown")); + subTitle.FromString(sfo.GetString(SaveParams::SUBTITLE).value_or("")); + detail.FromString(sfo.GetString(SaveParams::DETAIL).value_or("")); + userParam = sfo.GetInteger(SaveParams::SAVEDATA_LIST_PARAM).value_or(0); + const auto time_since_epoch = sfo.GetLastWrite().time_since_epoch(); + mtime = chrono::duration_cast(time_since_epoch).count(); +} + +void OrbisSaveDataParam::ToSFO(PSF& sfo) const { + sfo.AddString(std::string{SaveParams::MAINTITLE}, std::string{title}, true); + sfo.AddString(std::string{SaveParams::SUBTITLE}, std::string{subTitle}, true); + sfo.AddString(std::string{SaveParams::DETAIL}, std::string{detail}, true); + sfo.AddInteger(std::string{SaveParams::SAVEDATA_LIST_PARAM}, static_cast(userParam), true); +} int PS4_SYSV_ABI sceSaveDataAbort() { LOG_ERROR(Lib_SaveData, "(STUBBED) called"); return ORBIS_OK; } -int PS4_SYSV_ABI sceSaveDataBackup() { - LOG_ERROR(Lib_SaveData, "(STUBBED) called"); - return ORBIS_OK; +Error PS4_SYSV_ABI sceSaveDataBackup(const OrbisSaveDataBackup* backup) { + if (!g_initialized) { + LOG_INFO(Lib_SaveData, "called without initialize"); + return setNotInitializedError(); + } + if (backup == nullptr || backup->dirName == nullptr) { + LOG_INFO(Lib_SaveData, "called with invalid parameter"); + return Error::PARAMETER; + } + + const std::string_view dir_name{backup->dirName->data}; + LOG_DEBUG(Lib_SaveData, "called dirName: {}", dir_name); + + std::string_view title{backup->titleId != nullptr ? std::string_view{backup->titleId->data} + : std::string_view{g_game_serial}}; + + for (const auto& instance : g_mount_slots) { + if (instance.has_value() && instance->GetUserId() == backup->userId && + instance->GetTitleId() == title && instance->GetDirName() == dir_name) { + return Error::BUSY; + } + } + + Backup::StartThread(); + Backup::NewRequest(backup->userId, title, dir_name, OrbisSaveDataEventType::BACKUP); + + return Error::OK; } int PS4_SYSV_ABI sceSaveDataBindPsnAccount() { @@ -50,15 +572,55 @@ int PS4_SYSV_ABI sceSaveDataChangeInternal() { return ORBIS_OK; } -int PS4_SYSV_ABI sceSaveDataCheckBackupData(const OrbisSaveDataCheckBackupData* check) { - auto* mnt = Common::Singleton::Instance(); - const auto mount_dir = mnt->GetHostPath(check->dirName->data); - if (!std::filesystem::exists(mount_dir)) { - return ORBIS_SAVE_DATA_ERROR_NOT_FOUND; +Error PS4_SYSV_ABI sceSaveDataCheckBackupData(const OrbisSaveDataCheckBackupData* check) { + if (!g_initialized) { + LOG_INFO(Lib_SaveData, "called without initialize"); + return setNotInitializedError(); } - LOG_INFO(Lib_SaveData, "called = {}", mount_dir.string()); + if (check == nullptr || check->dirName == nullptr) { + LOG_INFO(Lib_SaveData, "called with invalid parameter"); + return Error::PARAMETER; + } + LOG_DEBUG(Lib_SaveData, "called with titleId={}", check->titleId->data.to_view()); - return ORBIS_OK; + const std::string_view title{check->titleId != nullptr ? std::string_view{check->titleId->data} + : std::string_view{g_game_serial}}; + + const auto save_path = + SaveInstance::MakeDirSavePath(check->userId, title, check->dirName->data); + + for (const auto& instance : g_mount_slots) { + if (instance.has_value() && instance->GetSavePath() == save_path) { + return Error::BUSY; + } + } + + if (Backup::IsBackupExecutingFor(save_path)) { + return Error::BACKUP_BUSY; + } + + const auto backup_path = Backup::MakeBackupPath(save_path); + if (!fs::exists(backup_path)) { + return Error::NOT_FOUND; + } + + if (check->param != nullptr) { + PSF sfo; + if (!sfo.Open(backup_path / "sce_sys" / "param.sfo")) { + LOG_ERROR(Lib_SaveData, "Failed to read SFO at {}", backup_path.string()); + return Error::INTERNAL; + } + check->param->FromSFO(sfo); + } + + if (check->icon != nullptr) { + const auto icon_path = backup_path / "sce_sys" / "icon0.png"; + if (fs::exists(icon_path) && check->icon->LoadIcon(icon_path) != Error::OK) { + return Error::INTERNAL; + } + } + + return Error::OK; } int PS4_SYSV_ABI sceSaveDataCheckBackupDataForCdlg() { @@ -96,9 +658,14 @@ int PS4_SYSV_ABI sceSaveDataCheckSaveDataVersionLatest() { return ORBIS_OK; } -int PS4_SYSV_ABI sceSaveDataClearProgress() { - LOG_ERROR(Lib_SaveData, "(STUBBED) called"); - return ORBIS_OK; +Error PS4_SYSV_ABI sceSaveDataClearProgress() { + if (!g_initialized) { + LOG_INFO(Lib_SaveData, "called without initialize"); + return setNotInitializedError(); + } + LOG_DEBUG(Lib_SaveData, "called"); + Backup::ClearProgress(); + return Error::OK; } int PS4_SYSV_ABI sceSaveDataCopy5() { @@ -146,15 +713,35 @@ int PS4_SYSV_ABI sceSaveDataDebugTarget() { return ORBIS_OK; } -int PS4_SYSV_ABI sceSaveDataDelete(const OrbisSaveDataDelete* del) { - const auto& mount_dir = Common::FS::GetUserPath(Common::FS::PathType::SaveDataDir) / - std::to_string(1) / game_serial / std::string(del->dirName->data); - LOG_INFO(Lib_SaveData, "called: dirname = {}, mount_dir = {}", (char*)del->dirName->data, - mount_dir.string()); - if (std::filesystem::exists(mount_dir) && std::filesystem::is_directory(mount_dir)) { - std::filesystem::remove_all(mount_dir); +Error PS4_SYSV_ABI sceSaveDataDelete(const OrbisSaveDataDelete* del) { + if (!g_initialized) { + LOG_INFO(Lib_SaveData, "called without initialize"); + return setNotInitializedError(); } - return ORBIS_OK; + if (del == nullptr) { + LOG_INFO(Lib_SaveData, "called with invalid parameter"); + return Error::PARAMETER; + } + const std::string_view dirName{del->dirName->data}; + LOG_DEBUG(Lib_SaveData, "called dirName: {}", dirName); + if (dirName.empty()) { + return Error::PARAMETER; + } + for (const auto& instance : g_mount_slots) { + if (instance.has_value() && instance->GetDirName() == dirName) { + return Error::BUSY; + } + } + const auto save_path = SaveInstance::MakeDirSavePath(del->userId, g_game_serial, dirName); + try { + if (fs::exists(save_path)) { + fs::remove_all(save_path); + } + } catch (const fs::filesystem_error& e) { + LOG_ERROR(Lib_SaveData, "Failed to delete save data: {}", e.what()); + return Error::INTERNAL; + } + return Error::OK; } int PS4_SYSV_ABI sceSaveDataDelete5() { @@ -177,89 +764,123 @@ int PS4_SYSV_ABI sceSaveDataDeleteUser() { return ORBIS_OK; } -int PS4_SYSV_ABI sceSaveDataDirNameSearch(const OrbisSaveDataDirNameSearchCond* cond, - OrbisSaveDataDirNameSearchResult* result) { - if (cond == nullptr || result == nullptr) - return ORBIS_SAVE_DATA_ERROR_PARAMETER; - LOG_INFO(Lib_SaveData, "Number of directories = {}", result->dirNamesNum); - const auto& mount_dir = Common::FS::GetUserPath(Common::FS::PathType::SaveDataDir) / - std::to_string(cond->userId) / game_serial; - if (!mount_dir.empty() && std::filesystem::exists(mount_dir)) { - int maxDirNum = result->dirNamesNum; // Games set a maximum of directories to search for - int i = 0; +Error PS4_SYSV_ABI sceSaveDataDirNameSearch(const OrbisSaveDataDirNameSearchCond* cond, + OrbisSaveDataDirNameSearchResult* result) { + if (!g_initialized) { + LOG_INFO(Lib_SaveData, "called without initialize"); + return setNotInitializedError(); + } + if (cond == nullptr || result == nullptr || cond->key > OrbisSaveDataSortKey::FREE_BLOCKS || + cond->order > OrbisSaveDataSortOrder::DESCENT) { + LOG_INFO(Lib_SaveData, "called with invalid parameter"); + return Error::PARAMETER; + } + LOG_DEBUG(Lib_SaveData, "called"); + const std::string_view title_id{cond->titleId == nullptr + ? std::string_view{g_game_serial} + : std::string_view{cond->titleId->data}}; + const auto save_path = SaveInstance::MakeTitleSavePath(cond->userId, title_id); - if (cond->dirName == nullptr || std::string_view(cond->dirName->data).empty()) { - // Look for all dirs if no dir is provided. - for (const auto& entry : std::filesystem::directory_iterator(mount_dir)) { - if (i >= maxDirNum) - break; - - if (std::filesystem::is_directory(entry.path()) && - entry.path().filename().string() != "sdmemory") { - // sceSaveDataDirNameSearch does not search for dataMemory1/2 dirs. - // copy dir name to be used by sceSaveDataMount in read mode. - strncpy(result->dirNames[i].data, entry.path().filename().string().c_str(), 32); - i++; - result->hitNum = i; - result->dirNamesNum = i; - result->setNum = i; - } - } - } else { - // Game checks for a specific directory. - LOG_INFO(Lib_SaveData, "dirName = {}", cond->dirName->data); - - // Games can pass '%' as a wildcard - // e.g. `SAVELIST%` searches for all folders with names starting with `SAVELIST` - std::string baseName(cond->dirName->data); - u64 wildcardPos = baseName.find('%'); - if (wildcardPos != std::string::npos) { - baseName = baseName.substr(0, wildcardPos); - } - - for (const auto& entry : std::filesystem::directory_iterator(mount_dir)) { - if (i >= maxDirNum) - break; - - if (std::filesystem::is_directory(entry.path())) { - std::string dirName = entry.path().filename().string(); - - if (wildcardPos != std::string::npos) { - if (dirName.compare(0, baseName.size(), baseName) != 0) { - continue; - } - } else if (wildcardPos == std::string::npos && dirName != cond->dirName->data) { - continue; - } - - strncpy(result->dirNames[i].data, cond->dirName->data, 32); - - i++; - result->hitNum = i; - result->dirNamesNum = i; - result->setNum = i; - } - } - } - - if (result->params != nullptr) { - Common::FS::IOFile file(mount_dir / cond->dirName->data / "param.txt", - Common::FS::FileAccessMode::Read); - if (file.IsOpen()) { - file.ReadRaw((void*)result->params, sizeof(OrbisSaveDataParam)); - file.Close(); - } - } - } else { + if (!fs::exists(save_path)) { result->hitNum = 0; - result->dirNamesNum = 0; - result->setNum = 0; + if (g_fw_ver >= ElfInfo::FW_17) { + result->setNum = 0; + } + return Error::OK; } - if (result->infos != nullptr) { - result->infos->blocks = ORBIS_SAVE_DATA_BLOCK_SIZE; - result->infos->freeBlocks = ORBIS_SAVE_DATA_BLOCK_SIZE; + + std::vector dir_list; + + for (const auto& path : fs::directory_iterator{save_path}) { + auto dir_name = path.path().filename().string(); + // skip non-directories, sce_* and directories without param.sfo + if (fs::is_directory(path) && !dir_name.starts_with("sce_") && + fs::exists(SaveInstance::GetParamSFOPath(path))) { + dir_list.push_back(dir_name); + } } - return ORBIS_OK; + if (cond->dirName != nullptr) { + // Filter names + const auto pat = Common::ToLower(std::string_view{cond->dirName->data}); + if (!pat.empty()) { + std::erase_if(dir_list, [&](const std::string& dir_name) { + return !match(Common::ToLower(dir_name), pat); + }); + } + } + + std::unordered_map map_dir_sfo; + std::unordered_map map_max_blocks; + std::unordered_map map_free_size; + + for (const auto& dir_name : dir_list) { + const auto dir_path = SaveInstance::MakeDirSavePath(cond->userId, title_id, dir_name); + const auto sfo_path = SaveInstance::GetParamSFOPath(dir_path); + PSF sfo; + if (!sfo.Open(sfo_path)) { + LOG_ERROR(Lib_SaveData, "Failed to read SFO: {}", sfo_path.string()); + 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); + map_free_size.emplace(dir_name, total - size / OrbisSaveDataBlockSize); + map_max_blocks.emplace(dir_name, total); + } + +#define PROJ(x) [&](const auto& v) { return x; } + switch (cond->key) { + case OrbisSaveDataSortKey::DIRNAME: + std::ranges::stable_sort(dir_list); + break; + case OrbisSaveDataSortKey::USER_PARAM: + std::ranges::stable_sort( + dir_list, {}, + PROJ(map_dir_sfo.at(v).GetInteger(SaveParams::SAVEDATA_LIST_PARAM).value_or(0))); + break; + case OrbisSaveDataSortKey::BLOCKS: + std::ranges::stable_sort(dir_list, {}, PROJ(map_max_blocks.at(v))); + break; + case OrbisSaveDataSortKey::FREE_BLOCKS: + std::ranges::stable_sort(dir_list, {}, PROJ(map_free_size.at(v))); + break; + case OrbisSaveDataSortKey::MTIME: + std::ranges::stable_sort(dir_list, {}, PROJ(map_dir_sfo.at(v).GetLastWrite())); + break; + } +#undef PROJ + + if (cond->order == OrbisSaveDataSortOrder::DESCENT) { + std::ranges::reverse(dir_list); + } + + size_t max_count = std::min(static_cast(result->dirNamesNum), dir_list.size()); + if (g_fw_ver >= ElfInfo::FW_17) { + result->hitNum = dir_list.size(); + result->setNum = max_count; + } else { + result->hitNum = max_count; + } + + for (size_t i = 0; i < max_count; i++) { + auto& name_data = result->dirNames[i].data; + name_data.FromString(dir_list[i]); + + if (g_fw_ver >= ElfInfo::FW_17 && result->params != nullptr) { + auto& sfo = map_dir_sfo.at(dir_list[i]); + auto& param_data = result->params[i]; + param_data.FromSFO(sfo); + } + + if (g_fw_ver >= ElfInfo::FW_25 && result->infos != nullptr) { + auto& info = result->infos[i]; + info.blocks = map_max_blocks.at(dir_list[i]); + info.freeBlocks = map_free_size.at(dir_list[i]); + } + } + + return Error::OK; } int PS4_SYSV_ABI sceSaveDataDirNameSearchInternal() { @@ -322,15 +943,30 @@ int PS4_SYSV_ABI sceSaveDataGetEventInfo() { return ORBIS_OK; } -int PS4_SYSV_ABI sceSaveDataGetEventResult(const OrbisSaveDataEventParam* eventParam, - OrbisSaveDataEvent* event) { - // eventParam can be 0/null. - if (event == nullptr) - return ORBIS_SAVE_DATA_ERROR_NOT_INITIALIZED; +Error PS4_SYSV_ABI sceSaveDataGetEventResult(const OrbisSaveDataEventParam*, + OrbisSaveDataEvent* event) { + if (!g_initialized) { + LOG_INFO(Lib_SaveData, "called without initialize"); + return setNotInitializedError(); + } + if (event == nullptr) { + LOG_INFO(Lib_SaveData, "called with invalid parameter"); + return Error::PARAMETER; + } + LOG_TRACE(Lib_SaveData, "called"); - LOG_INFO(Lib_SaveData, "called: Todo."); - event->userId = 1; - return ORBIS_OK; + auto last_event = Backup::PopLastEvent(); + if (!last_event.has_value()) { + return Error::NOT_FOUND; + } + + event->type = last_event->origin; + event->errorCode = 0; + event->userId = last_event->user_id; + event->titleId.data.FromString(last_event->title_id); + event->dirName.data.FromString(last_event->dir_name); + + return Error::OK; } int PS4_SYSV_ABI sceSaveDataGetFormat() { @@ -343,65 +979,119 @@ int PS4_SYSV_ABI sceSaveDataGetMountedSaveDataCount() { return ORBIS_OK; } -int PS4_SYSV_ABI sceSaveDataGetMountInfo(const OrbisSaveDataMountPoint* mountPoint, - OrbisSaveDataMountInfo* info) { - LOG_INFO(Lib_SaveData, "called"); - info->blocks = ORBIS_SAVE_DATA_BLOCKS_MAX; - info->freeBlocks = ORBIS_SAVE_DATA_BLOCKS_MAX; - return ORBIS_OK; +Error PS4_SYSV_ABI sceSaveDataGetMountInfo(const OrbisSaveDataMountPoint* mountPoint, + OrbisSaveDataMountInfo* info) { + if (!g_initialized) { + LOG_INFO(Lib_SaveData, "called without initialize"); + return setNotInitializedError(); + } + if (mountPoint == nullptr || info == nullptr) { + LOG_INFO(Lib_SaveData, "called with invalid parameter"); + return Error::PARAMETER; + } + LOG_DEBUG(Lib_SaveData, "called"); + const std::string_view mount_point_str{mountPoint->data}; + for (const auto& instance : g_mount_slots) { + if (instance.has_value() && instance->GetMountPoint() == mount_point_str) { + const u32 blocks = instance->GetMaxBlocks(); + const u64 size = Common::FS::GetDirectorySize(instance->GetSavePath()); + info->blocks = blocks; + info->freeBlocks = blocks - size / OrbisSaveDataBlockSize; + return Error::OK; + } + } + return Error::NOT_MOUNTED; } -int PS4_SYSV_ABI sceSaveDataGetParam(const OrbisSaveDataMountPoint* mountPoint, - const OrbisSaveDataParamType paramType, void* paramBuf, - const size_t paramBufSize, size_t* gotSize) { +Error PS4_SYSV_ABI sceSaveDataGetParam(const OrbisSaveDataMountPoint* mountPoint, + OrbisSaveDataParamType paramType, void* paramBuf, + size_t paramBufSize, size_t* gotSize) { + if (!g_initialized) { + LOG_INFO(Lib_SaveData, "called without initialize"); + return setNotInitializedError(); + } + if (paramType > OrbisSaveDataParamType::MTIME || paramBuf == nullptr) { + LOG_INFO(Lib_SaveData, "called with invalid parameter"); + return Error::PARAMETER; + } + LOG_DEBUG(Lib_SaveData, "called: paramType = {}", magic_enum::enum_name(paramType)); + const PSF* param_sfo = nullptr; - if (mountPoint == nullptr) - return ORBIS_SAVE_DATA_ERROR_PARAMETER; - - auto* mnt = Common::Singleton::Instance(); - const auto mount_dir = mnt->GetHostPath(mountPoint->data); - Common::FS::IOFile file(mount_dir / "param.txt", Common::FS::FileAccessMode::Read); - OrbisSaveDataParam params; - file.Read(params); - - LOG_INFO(Lib_SaveData, "called"); - - switch (paramType) { - case ORBIS_SAVE_DATA_PARAM_TYPE_ALL: { - memcpy(paramBuf, ¶ms, sizeof(OrbisSaveDataParam)); - *gotSize = sizeof(OrbisSaveDataParam); - } break; - case ORBIS_SAVE_DATA_PARAM_TYPE_TITLE: { - std::memcpy(paramBuf, ¶ms.title, ORBIS_SAVE_DATA_TITLE_MAXSIZE); - *gotSize = ORBIS_SAVE_DATA_TITLE_MAXSIZE; - } break; - case ORBIS_SAVE_DATA_PARAM_TYPE_SUB_TITLE: { - std::memcpy(paramBuf, ¶ms.subTitle, ORBIS_SAVE_DATA_SUBTITLE_MAXSIZE); - *gotSize = ORBIS_SAVE_DATA_SUBTITLE_MAXSIZE; - } break; - case ORBIS_SAVE_DATA_PARAM_TYPE_DETAIL: { - std::memcpy(paramBuf, ¶ms.detail, ORBIS_SAVE_DATA_DETAIL_MAXSIZE); - *gotSize = ORBIS_SAVE_DATA_DETAIL_MAXSIZE; - } break; - case ORBIS_SAVE_DATA_PARAM_TYPE_USER_PARAM: { - std::memcpy(paramBuf, ¶ms.userParam, sizeof(u32)); - *gotSize = sizeof(u32); - } break; - case ORBIS_SAVE_DATA_PARAM_TYPE_MTIME: { - std::memcpy(paramBuf, ¶ms.mtime, sizeof(time_t)); - *gotSize = sizeof(time_t); - } break; - default: { - UNREACHABLE_MSG("Unknown Param = {}", paramType); - } break; + const std::string_view mount_point_str{mountPoint->data}; + for (const auto& instance : g_mount_slots) { + if (instance.has_value() && instance->GetMountPoint() == mount_point_str) { + param_sfo = &instance->GetParamSFO(); + break; + } + } + if (param_sfo == nullptr) { + return Error::NOT_MOUNTED; } - return ORBIS_OK; + switch (paramType) { + case OrbisSaveDataParamType::ALL: { + const auto param = static_cast(paramBuf); + ASSERT(paramBufSize == sizeof(OrbisSaveDataParam)); + param->FromSFO(*param_sfo); + if (gotSize != nullptr) { + *gotSize = sizeof(OrbisSaveDataParam); + } + break; + } + case OrbisSaveDataParamType::TITLE: + case OrbisSaveDataParamType::SUB_TITLE: + case OrbisSaveDataParamType::DETAIL: { + const auto param = static_cast(paramBuf); + std::string_view key; + if (paramType == OrbisSaveDataParamType::TITLE) { + key = SaveParams::MAINTITLE; + } else if (paramType == OrbisSaveDataParamType::SUB_TITLE) { + key = SaveParams::SUBTITLE; + } else if (paramType == OrbisSaveDataParamType::DETAIL) { + key = SaveParams::DETAIL; + } else { + UNREACHABLE(); + } + const size_t s = param_sfo->GetString(key).value_or("").copy(param, paramBufSize - 1); + param[s] = '\0'; // null terminate + if (gotSize != nullptr) { + *gotSize = s + 1; + } + } break; + case OrbisSaveDataParamType::USER_PARAM: { + const auto param = static_cast(paramBuf); + *param = param_sfo->GetInteger(SaveParams::SAVEDATA_LIST_PARAM).value_or(0); + if (gotSize != nullptr) { + *gotSize = sizeof(u32); + } + } break; + case OrbisSaveDataParamType::MTIME: { + const auto param = static_cast(paramBuf); + const auto last_write = param_sfo->GetLastWrite().time_since_epoch(); + *param = chrono::duration_cast(last_write).count(); + if (gotSize != nullptr) { + *gotSize = sizeof(time_t); + } + } break; + default: + UNREACHABLE(); + } + + return Error::OK; } -int PS4_SYSV_ABI sceSaveDataGetProgress() { - LOG_ERROR(Lib_SaveData, "(STUBBED) called"); - return ORBIS_OK; +Error PS4_SYSV_ABI sceSaveDataGetProgress(float* progress) { + if (!g_initialized) { + LOG_INFO(Lib_SaveData, "called without initialize"); + return setNotInitializedError(); + } + if (progress == nullptr) { + LOG_INFO(Lib_SaveData, "called with invalid parameter"); + return Error::PARAMETER; + } + LOG_DEBUG(Lib_SaveData, "called"); + *progress = Backup::GetProgress(); + return Error::OK; } int PS4_SYSV_ABI sceSaveDataGetSaveDataCount() { @@ -409,44 +1099,56 @@ int PS4_SYSV_ABI sceSaveDataGetSaveDataCount() { return ORBIS_OK; } -int PS4_SYSV_ABI sceSaveDataGetSaveDataMemory(const u32 userId, void* buf, const size_t bufSize, - const int64_t offset) { - const auto& mount_dir = Common::FS::GetUserPath(Common::FS::PathType::SaveDataDir) / - std::to_string(userId) / game_serial / "sdmemory/save_mem1.sav"; - - Common::FS::IOFile file(mount_dir, Common::FS::FileAccessMode::Read); - if (!file.IsOpen()) { - return false; - } - file.Seek(offset); - size_t nbytes = file.ReadRaw(buf, bufSize); - LOG_INFO(Lib_SaveData, "called: bufSize = {}, offset = {}", bufSize, offset, nbytes); - - return ORBIS_OK; +Error PS4_SYSV_ABI sceSaveDataGetSaveDataMemory(const OrbisUserServiceUserId userId, void* buf, + const size_t bufSize, const int64_t offset) { + LOG_DEBUG(Lib_SaveData, "Redirecting to sceSaveDataGetSaveDataMemory2"); + OrbisSaveDataMemoryData data{}; + data.buf = buf; + data.bufSize = bufSize; + data.offset = offset; + OrbisSaveDataMemoryGet2 param{}; + param.userId = userId; + param.data = &data; + param.param = nullptr; + param.icon = nullptr; + return sceSaveDataGetSaveDataMemory2(¶m); } -int PS4_SYSV_ABI sceSaveDataGetSaveDataMemory2(OrbisSaveDataMemoryGet2* getParam) { - const auto& mount_dir = Common::FS::GetUserPath(Common::FS::PathType::SaveDataDir) / - std::to_string(getParam->userId) / game_serial / "sdmemory"; - if (getParam == nullptr) - return ORBIS_SAVE_DATA_ERROR_PARAMETER; - if (getParam->data != nullptr) { - Common::FS::IOFile file(mount_dir / "save_mem2.sav", Common::FS::FileAccessMode::Read); - if (!file.IsOpen()) { - return false; - } - file.Seek(getParam->data->offset); - file.ReadRaw(getParam->data->buf, getParam->data->bufSize); - LOG_INFO(Lib_SaveData, "called: bufSize = {}, offset = {}", getParam->data->bufSize, - getParam->data->offset); +Error PS4_SYSV_ABI sceSaveDataGetSaveDataMemory2(OrbisSaveDataMemoryGet2* getParam) { + if (!g_initialized) { + LOG_INFO(Lib_SaveData, "called without initialize"); + return setNotInitializedError(); + } + if (getParam == nullptr) { + LOG_INFO(Lib_SaveData, "called with invalid parameter"); + return Error::PARAMETER; + } + if (!SaveMemory::IsSaveMemoryInitialized()) { + LOG_INFO(Lib_SaveData, "called without save memory initialized"); + return Error::MEMORY_NOT_READY; + } + if (SaveMemory::IsSaving()) { + LOG_TRACE(Lib_SaveData, "called while saving"); + return Error::BUSY_FOR_SAVING; + } + LOG_DEBUG(Lib_SaveData, "called"); + auto data = getParam->data; + if (data != nullptr) { + SaveMemory::ReadMemory(data->buf, data->bufSize, data->offset); + } + auto param = getParam->param; + if (param != nullptr) { + param->FromSFO(SaveMemory::GetParamSFO()); + } + auto icon = getParam->icon; + if (icon != nullptr) { + auto icon_mem = SaveMemory::GetIcon(); + size_t total = std::min(icon->bufSize, icon_mem.size()); + std::memcpy(icon->buf, icon_mem.data(), total); + icon->dataSize = total; } - if (getParam->param != nullptr) { - Common::FS::IOFile file(mount_dir / "param.txt", Common::FS::FileAccessMode::Read); - file.ReadRaw(getParam->param, sizeof(OrbisSaveDataParam)); - } - - return ORBIS_OK; + return Error::OK; } int PS4_SYSV_ABI sceSaveDataGetSaveDataRootDir() { @@ -474,25 +1176,22 @@ int PS4_SYSV_ABI sceSaveDataGetUpdatedDataCount() { return ORBIS_OK; } -int PS4_SYSV_ABI sceSaveDataInitialize() { - LOG_INFO(Lib_SaveData, "called"); - static auto* param_sfo = Common::Singleton::Instance(); - game_serial = std::string(param_sfo->GetString("CONTENT_ID"), 7, 9); - return ORBIS_OK; +Error PS4_SYSV_ABI sceSaveDataInitialize(void*) { + LOG_DEBUG(Lib_SaveData, "called"); + initialize(); + return Error::OK; } -int PS4_SYSV_ABI sceSaveDataInitialize2() { - LOG_INFO(Lib_SaveData, "called"); - static auto* param_sfo = Common::Singleton::Instance(); - game_serial = std::string(param_sfo->GetString("CONTENT_ID"), 7, 9); - return ORBIS_OK; +Error PS4_SYSV_ABI sceSaveDataInitialize2(void*) { + LOG_DEBUG(Lib_SaveData, "called"); + initialize(); + return Error::OK; } -int PS4_SYSV_ABI sceSaveDataInitialize3() { - LOG_INFO(Lib_SaveData, "called"); - static auto* param_sfo = Common::Singleton::Instance(); - game_serial = std::string(param_sfo->GetString("CONTENT_ID"), 7, 9); - return ORBIS_OK; +Error PS4_SYSV_ABI sceSaveDataInitialize3(void*) { + LOG_DEBUG(Lib_SaveData, "called"); + initialize(); + return Error::OK; } int PS4_SYSV_ABI sceSaveDataInitializeForCdlg() { @@ -510,101 +1209,69 @@ int PS4_SYSV_ABI sceSaveDataIsMounted() { return ORBIS_OK; } -int PS4_SYSV_ABI sceSaveDataLoadIcon(const OrbisSaveDataMountPoint* mountPoint, - OrbisSaveDataIcon* icon) { - auto* mnt = Common::Singleton::Instance(); - const auto mount_dir = mnt->GetHostPath(mountPoint->data); - LOG_INFO(Lib_SaveData, "called: dir = {}", mount_dir.string()); - - if (icon != nullptr) { - Common::FS::IOFile file(mount_dir / "save_data.png", Common::FS::FileAccessMode::Read); - icon->bufSize = file.GetSize(); - file.ReadRaw(icon->buf, icon->bufSize); +Error PS4_SYSV_ABI sceSaveDataLoadIcon(const OrbisSaveDataMountPoint* mountPoint, + OrbisSaveDataIcon* icon) { + if (!g_initialized) { + LOG_INFO(Lib_SaveData, "called without initialize"); + return setNotInitializedError(); } - return ORBIS_OK; + if (mountPoint == nullptr || icon == nullptr || icon->buf == nullptr) { + LOG_INFO(Lib_SaveData, "called with invalid parameter"); + return Error::PARAMETER; + } + LOG_DEBUG(Lib_SaveData, "called"); + std::filesystem::path path; + const std::string_view mount_point_str{mountPoint->data}; + for (const auto& instance : g_mount_slots) { + if (instance.has_value() && instance->GetMountPoint() == mount_point_str) { + path = instance->GetIconPath(); + break; + } + } + if (path.empty()) { + return Error::NOT_MOUNTED; + } + if (!fs::exists(path)) { + return Error::NOT_FOUND; + } + + return icon->LoadIcon(path); } -s32 saveDataMount(u32 user_id, char* dir_name, u32 mount_mode, - OrbisSaveDataMountResult* mount_result) { - const auto& mount_dir = Common::FS::GetUserPath(Common::FS::PathType::SaveDataDir) / - std::to_string(user_id) / game_serial / dir_name; - auto* mnt = Common::Singleton::Instance(); - switch (mount_mode) { - case ORBIS_SAVE_DATA_MOUNT_MODE_RDONLY: - case ORBIS_SAVE_DATA_MOUNT_MODE_RDWR: - case ORBIS_SAVE_DATA_MOUNT_MODE_RDWR | ORBIS_SAVE_DATA_MOUNT_MODE_DESTRUCT_OFF: - case ORBIS_SAVE_DATA_MOUNT_MODE_RDONLY | ORBIS_SAVE_DATA_MOUNT_MODE_DESTRUCT_OFF: { - is_rw_mode = (mount_mode == ORBIS_SAVE_DATA_MOUNT_MODE_RDWR) ? true : false; - if (!std::filesystem::exists(mount_dir)) { - return ORBIS_SAVE_DATA_ERROR_NOT_FOUND; - } - mount_result->mount_status = 0; - g_mount_point.copy(mount_result->mount_point.data, 16); - mnt->Mount(mount_dir, mount_result->mount_point.data); - } break; - case ORBIS_SAVE_DATA_MOUNT_MODE_CREATE: - case ORBIS_SAVE_DATA_MOUNT_MODE_CREATE | ORBIS_SAVE_DATA_MOUNT_MODE_RDONLY: - case ORBIS_SAVE_DATA_MOUNT_MODE_CREATE | ORBIS_SAVE_DATA_MOUNT_MODE_RDWR: - case ORBIS_SAVE_DATA_MOUNT_MODE_CREATE | ORBIS_SAVE_DATA_MOUNT_MODE_RDWR | - ORBIS_SAVE_DATA_MOUNT_MODE_DESTRUCT_OFF: - case ORBIS_SAVE_DATA_MOUNT_MODE_CREATE | ORBIS_SAVE_DATA_MOUNT_MODE_RDWR | - ORBIS_SAVE_DATA_MOUNT_MODE_COPY_ICON: - case ORBIS_SAVE_DATA_MOUNT_MODE_CREATE | ORBIS_SAVE_DATA_MOUNT_MODE_COPY_ICON: - case ORBIS_SAVE_DATA_MOUNT_MODE_CREATE | ORBIS_SAVE_DATA_MOUNT_MODE_DESTRUCT_OFF | - ORBIS_SAVE_DATA_MOUNT_MODE_COPY_ICON: - case ORBIS_SAVE_DATA_MOUNT_MODE_CREATE | ORBIS_SAVE_DATA_MOUNT_MODE_RDWR | - ORBIS_SAVE_DATA_MOUNT_MODE_DESTRUCT_OFF | ORBIS_SAVE_DATA_MOUNT_MODE_COPY_ICON: { - if (std::filesystem::exists(mount_dir)) { - return ORBIS_SAVE_DATA_ERROR_EXISTS; - } - if (std::filesystem::create_directories(mount_dir)) { - g_mount_point.copy(mount_result->mount_point.data, 16); - mnt->Mount(mount_dir, mount_result->mount_point.data); - mount_result->mount_status = 1; - } - } break; - case ORBIS_SAVE_DATA_MOUNT_MODE_CREATE2: - case ORBIS_SAVE_DATA_MOUNT_MODE_CREATE2 | ORBIS_SAVE_DATA_MOUNT_MODE_RDWR: - case ORBIS_SAVE_DATA_MOUNT_MODE_CREATE2 | ORBIS_SAVE_DATA_MOUNT_MODE_COPY_ICON: - case ORBIS_SAVE_DATA_MOUNT_MODE_CREATE2 | ORBIS_SAVE_DATA_MOUNT_MODE_RDWR | - ORBIS_SAVE_DATA_MOUNT_MODE_COPY_ICON: - case ORBIS_SAVE_DATA_MOUNT_MODE_CREATE2 | ORBIS_SAVE_DATA_MOUNT_MODE_RDWR | - ORBIS_SAVE_DATA_MOUNT_MODE_DESTRUCT_OFF | ORBIS_SAVE_DATA_MOUNT_MODE_COPY_ICON: { - if (!std::filesystem::exists(mount_dir)) { - std::filesystem::create_directories(mount_dir); - } - g_mount_point.copy(mount_result->mount_point.data, 16); - mnt->Mount(mount_dir, mount_result->mount_point.data); - mount_result->mount_status = 1; - } break; - default: - UNREACHABLE_MSG("Unknown mount mode = {}", mount_mode); +Error PS4_SYSV_ABI sceSaveDataMount(const OrbisSaveDataMount* mount, + OrbisSaveDataMountResult* mount_result) { + if (!g_initialized) { + LOG_INFO(Lib_SaveData, "called without initialize"); + return setNotInitializedError(); } - mount_result->required_blocks = 0; + if (mount == nullptr && mount->dirName != nullptr) { + LOG_INFO(Lib_SaveData, "called with invalid parameter"); + return Error::PARAMETER; + } + LOG_DEBUG(Lib_SaveData, "called dirName: {}, mode: {:0b}, blocks: {}", + mount->dirName->data.to_view(), (int)mount->mountMode, mount->blocks); - return ORBIS_OK; + OrbisSaveDataMount2 mount_info{}; + mount_info.userId = mount->userId; + mount_info.dirName = mount->dirName; + mount_info.mountMode = mount->mountMode; + mount_info.blocks = mount->blocks; + return saveDataMount(&mount_info, mount_result); } -s32 PS4_SYSV_ABI sceSaveDataMount(const OrbisSaveDataMount* mount, - OrbisSaveDataMountResult* mount_result) { - if (mount == nullptr) { - return ORBIS_SAVE_DATA_ERROR_PARAMETER; +Error PS4_SYSV_ABI sceSaveDataMount2(const OrbisSaveDataMount2* mount, + OrbisSaveDataMountResult* mount_result) { + if (!g_initialized) { + LOG_INFO(Lib_SaveData, "called without initialize"); + return setNotInitializedError(); } - LOG_INFO(Lib_SaveData, "called: dirName = {}, mode = {}, blocks = {}", mount->dir_name->data, - mount->mount_mode, mount->blocks); - return saveDataMount(mount->user_id, (char*)mount->dir_name->data, mount->mount_mode, - mount_result); -} - -s32 PS4_SYSV_ABI sceSaveDataMount2(const OrbisSaveDataMount2* mount, - OrbisSaveDataMountResult* mount_result) { - if (mount == nullptr) { - return ORBIS_SAVE_DATA_ERROR_PARAMETER; + if (mount == nullptr && mount->dirName != nullptr) { + LOG_INFO(Lib_SaveData, "called with invalid parameter"); + return Error::PARAMETER; } - LOG_INFO(Lib_SaveData, "called: dirName = {}, mode = {}, blocks = {}", mount->dir_name->data, - mount->mount_mode, mount->blocks); - return saveDataMount(mount->user_id, (char*)mount->dir_name->data, mount->mount_mode, - mount_result); + LOG_DEBUG(Lib_SaveData, "called dirName: {}, mode: {:0b}, blocks: {}", + mount->dirName->data.to_view(), (int)mount->mountMode, mount->blocks); + return saveDataMount(mount, mount_result); } int PS4_SYSV_ABI sceSaveDataMount5() { @@ -637,9 +1304,44 @@ int PS4_SYSV_ABI sceSaveDataRegisterEventCallback() { return ORBIS_OK; } -int PS4_SYSV_ABI sceSaveDataRestoreBackupData() { - LOG_ERROR(Lib_SaveData, "(STUBBED) called"); - return ORBIS_OK; +Error PS4_SYSV_ABI sceSaveDataRestoreBackupData(const OrbisSaveDataRestoreBackupData* restore) { + if (!g_initialized) { + LOG_INFO(Lib_SaveData, "called without initialize"); + return setNotInitializedError(); + } + if (restore == nullptr || restore->dirName == nullptr) { + LOG_INFO(Lib_SaveData, "called with invalid parameter"); + return Error::PARAMETER; + } + + const std::string_view dir_name{restore->dirName->data}; + LOG_DEBUG(Lib_SaveData, "called dirName: {}", dir_name); + + std::string_view title{restore->titleId != nullptr ? std::string_view{restore->titleId->data} + : std::string_view{g_game_serial}}; + + const auto save_path = SaveInstance::MakeDirSavePath(restore->userId, title, dir_name); + + for (const auto& instance : g_mount_slots) { + if (instance.has_value() && instance->GetSavePath() == save_path) { + return Error::BUSY; + } + } + if (Backup::IsBackupExecutingFor(save_path)) { + return Error::BACKUP_BUSY; + } + + const auto backup_path = Backup::MakeBackupPath(save_path); + if (!fs::exists(backup_path)) { + return Error::NOT_FOUND; + } + + const bool ok = Backup::Restore(save_path); + if (!ok) { + return Error::INTERNAL; + } + + return Error::OK; } int PS4_SYSV_ABI sceSaveDataRestoreBackupDataForCdlg() { @@ -652,17 +1354,41 @@ int PS4_SYSV_ABI sceSaveDataRestoreLoadSaveDataMemory() { return ORBIS_OK; } -int PS4_SYSV_ABI sceSaveDataSaveIcon(const OrbisSaveDataMountPoint* mountPoint, - const OrbisSaveDataIcon* icon) { - auto* mnt = Common::Singleton::Instance(); - const auto mount_dir = mnt->GetHostPath(mountPoint->data); - LOG_INFO(Lib_SaveData, "called = {}", mount_dir.string()); - - if (icon != nullptr) { - Common::FS::IOFile file(mount_dir / "save_data.png", Common::FS::FileAccessMode::Write); - file.WriteRaw(icon->buf, icon->bufSize); +Error PS4_SYSV_ABI sceSaveDataSaveIcon(const OrbisSaveDataMountPoint* mountPoint, + const OrbisSaveDataIcon* icon) { + if (!g_initialized) { + LOG_INFO(Lib_SaveData, "called without initialize"); + return setNotInitializedError(); } - return ORBIS_OK; + if (mountPoint == nullptr || icon == nullptr || icon->buf == nullptr) { + LOG_INFO(Lib_SaveData, "called with invalid parameter"); + return Error::PARAMETER; + } + LOG_DEBUG(Lib_SaveData, "called"); + std::filesystem::path path; + const std::string_view mount_point_str{mountPoint->data}; + for (const auto& instance : g_mount_slots) { + if (instance.has_value() && instance->GetMountPoint() == mount_point_str) { + if (instance->IsReadOnly()) { + return Error::BAD_MOUNTED; + } + path = instance->GetIconPath(); + break; + } + } + if (path.empty()) { + return Error::NOT_MOUNTED; + } + + try { + const Common::FS::IOFile file(path, Common::FS::FileAccessMode::Write); + file.WriteRaw(icon->buf, std::min(icon->bufSize, icon->dataSize)); + } catch (const fs::filesystem_error& e) { + LOG_ERROR(Lib_SaveData, "Failed to load icon: {}", e.what()); + return Error::INTERNAL; + } + + return Error::OK; } int PS4_SYSV_ABI sceSaveDataSetAutoUploadSetting() { @@ -675,50 +1401,59 @@ int PS4_SYSV_ABI sceSaveDataSetEventInfo() { return ORBIS_OK; } -int PS4_SYSV_ABI sceSaveDataSetParam(const OrbisSaveDataMountPoint* mountPoint, - OrbisSaveDataParamType paramType, const void* paramBuf, - size_t paramBufSize) { - if (paramBuf == nullptr) - return ORBIS_SAVE_DATA_ERROR_PARAMETER; - - auto* mnt = Common::Singleton::Instance(); - const auto mount_dir = mnt->GetHostPath(mountPoint->data) / "param.txt"; - OrbisSaveDataParam params; - if (std::filesystem::exists(mount_dir)) { - Common::FS::IOFile file(mount_dir, Common::FS::FileAccessMode::Read); - file.ReadRaw(¶ms, sizeof(OrbisSaveDataParam)); +Error PS4_SYSV_ABI sceSaveDataSetParam(const OrbisSaveDataMountPoint* mountPoint, + OrbisSaveDataParamType paramType, const void* paramBuf, + size_t paramBufSize) { + if (!g_initialized) { + LOG_INFO(Lib_SaveData, "called without initialize"); + return setNotInitializedError(); + } + if (paramType > OrbisSaveDataParamType::USER_PARAM || mountPoint == nullptr || + paramBuf == nullptr) { + LOG_INFO(Lib_SaveData, "called with invalid parameter"); + return Error::PARAMETER; + } + LOG_DEBUG(Lib_SaveData, "called: paramType = {}", magic_enum::enum_name(paramType)); + PSF* param_sfo = nullptr; + const std::string_view mount_point_str{mountPoint->data}; + for (auto& instance : g_mount_slots) { + if (instance.has_value() && instance->GetMountPoint() == mount_point_str) { + param_sfo = &instance->GetParamSFO(); + break; + } + } + if (param_sfo == nullptr) { + return Error::NOT_MOUNTED; } - - LOG_INFO(Lib_SaveData, "called"); switch (paramType) { - case ORBIS_SAVE_DATA_PARAM_TYPE_ALL: { - memcpy(¶ms, paramBuf, sizeof(OrbisSaveDataParam)); + case OrbisSaveDataParamType::ALL: { + const auto param = static_cast(paramBuf); + ASSERT(paramBufSize == sizeof(OrbisSaveDataParam)); + param->ToSFO(*param_sfo); + return Error::OK; } break; - case ORBIS_SAVE_DATA_PARAM_TYPE_TITLE: { - strncpy(params.title, static_cast(paramBuf), paramBufSize); + case OrbisSaveDataParamType::TITLE: { + const auto value = static_cast(paramBuf); + param_sfo->AddString(std::string{SaveParams::MAINTITLE}, {value}, true); } break; - case ORBIS_SAVE_DATA_PARAM_TYPE_SUB_TITLE: { - strncpy(params.subTitle, static_cast(paramBuf), paramBufSize); + case OrbisSaveDataParamType::SUB_TITLE: { + const auto value = static_cast(paramBuf); + param_sfo->AddString(std::string{SaveParams::SUBTITLE}, {value}, true); } break; - case ORBIS_SAVE_DATA_PARAM_TYPE_DETAIL: { - strncpy(params.detail, static_cast(paramBuf), paramBufSize); + case OrbisSaveDataParamType::DETAIL: { + const auto value = static_cast(paramBuf); + param_sfo->AddString(std::string{SaveParams::DETAIL}, {value}, true); } break; - case ORBIS_SAVE_DATA_PARAM_TYPE_USER_PARAM: { - params.userParam = *(static_cast(paramBuf)); + case OrbisSaveDataParamType::USER_PARAM: { + const auto value = static_cast(paramBuf); + param_sfo->AddInteger(std::string{SaveParams::SAVEDATA_LIST_PARAM}, *value, true); } break; - case ORBIS_SAVE_DATA_PARAM_TYPE_MTIME: { - params.mtime = *(static_cast(paramBuf)); - } break; - default: { - UNREACHABLE_MSG("Unknown Param = {}", paramType); - } + default: + UNREACHABLE(); } - Common::FS::IOFile file(mount_dir, Common::FS::FileAccessMode::Write); - file.WriteRaw(¶ms, sizeof(OrbisSaveDataParam)); - - return ORBIS_OK; + return Error::OK; } int PS4_SYSV_ABI sceSaveDataSetSaveDataLibraryUser() { @@ -726,98 +1461,134 @@ int PS4_SYSV_ABI sceSaveDataSetSaveDataLibraryUser() { return ORBIS_OK; } -int PS4_SYSV_ABI sceSaveDataSetSaveDataMemory(const u32 userId, const void* buf, - const size_t bufSize, const int64_t offset) { - LOG_INFO(Lib_SaveData, "called"); - const auto& mount_dir = Common::FS::GetUserPath(Common::FS::PathType::SaveDataDir) / - std::to_string(userId) / game_serial / "sdmemory/save_mem1.sav"; - - Common::FS::IOFile file(mount_dir, Common::FS::FileAccessMode::Write); - file.Seek(offset); - file.WriteRaw(buf, bufSize); - - return ORBIS_OK; +Error PS4_SYSV_ABI sceSaveDataSetSaveDataMemory(OrbisUserServiceUserId userId, void* buf, + size_t bufSize, int64_t offset) { + LOG_DEBUG(Lib_SaveData, "Redirecting to sceSaveDataSetSaveDataMemory2"); + OrbisSaveDataMemoryData data{}; + data.buf = buf; + data.bufSize = bufSize; + data.offset = offset; + OrbisSaveDataMemorySet2 setParam{}; + setParam.userId = userId; + setParam.data = &data; + setParam.param = nullptr; + setParam.icon = nullptr; + return sceSaveDataSetSaveDataMemory2(&setParam); } -int PS4_SYSV_ABI sceSaveDataSetSaveDataMemory2(const OrbisSaveDataMemorySet2* setParam) { - LOG_INFO(Lib_SaveData, "called: dataNum = {}, slotId= {}", setParam->dataNum, setParam->slotId); - const auto& mount_dir = Common::FS::GetUserPath(Common::FS::PathType::SaveDataDir) / - std::to_string(setParam->userId) / game_serial / "sdmemory"; - if (setParam->data != nullptr) { - Common::FS::IOFile file(mount_dir / "save_mem2.sav", Common::FS::FileAccessMode::Write); - if (!file.IsOpen()) - return -1; - file.Seek(setParam->data->offset); - file.WriteRaw(setParam->data->buf, setParam->data->bufSize); +Error PS4_SYSV_ABI sceSaveDataSetSaveDataMemory2(const OrbisSaveDataMemorySet2* setParam) { + if (!g_initialized) { + LOG_INFO(Lib_SaveData, "called without initialize"); + return setNotInitializedError(); + } + if (setParam == nullptr) { + LOG_INFO(Lib_SaveData, "called with invalid parameter"); + return Error::PARAMETER; + } + if (!SaveMemory::IsSaveMemoryInitialized()) { + LOG_INFO(Lib_SaveData, "called without save memory initialized"); + return Error::MEMORY_NOT_READY; + } + if (SaveMemory::IsSaving()) { + LOG_TRACE(Lib_SaveData, "called while saving"); + return Error::BUSY_FOR_SAVING; + } + LOG_DEBUG(Lib_SaveData, "called"); + auto data = setParam->data; + if (data != nullptr) { + SaveMemory::WriteMemory(data->buf, data->bufSize, data->offset); + } + auto param = setParam->param; + if (param != nullptr) { + param->ToSFO(SaveMemory::GetParamSFO()); + SaveMemory::SaveSFO(); + } + auto icon = setParam->icon; + if (icon != nullptr) { + SaveMemory::WriteIcon(icon->buf, icon->bufSize); } - if (setParam->param != nullptr) { - Common::FS::IOFile file(mount_dir / "param.txt", Common::FS::FileAccessMode::Write); - file.WriteRaw((void*)setParam->param, sizeof(OrbisSaveDataParam)); - } - - if (setParam->icon != nullptr) { - Common::FS::IOFile file(mount_dir / "save_icon.png", Common::FS::FileAccessMode::Write); - file.WriteRaw(setParam->icon->buf, setParam->icon->bufSize); - } - - return ORBIS_OK; + SaveMemory::TriggerSaveWithoutEvent(); + return Error::OK; } -int PS4_SYSV_ABI sceSaveDataSetupSaveDataMemory(u32 userId, size_t memorySize, - OrbisSaveDataParam* param) { - - LOG_INFO(Lib_SaveData, "called:userId = {}, memorySize = {}", userId, memorySize); - - if (param == nullptr) { - return ORBIS_SAVE_DATA_ERROR_PARAMETER; +Error PS4_SYSV_ABI sceSaveDataSetupSaveDataMemory(OrbisUserServiceUserId userId, size_t memorySize, + OrbisSaveDataParam* param) { + LOG_DEBUG(Lib_SaveData, "called: userId = {}, memorySize = {}", userId, memorySize); + OrbisSaveDataMemorySetup2 setupParam{}; + setupParam.userId = userId; + setupParam.memorySize = memorySize; + setupParam.initParam = nullptr; + setupParam.initIcon = nullptr; + OrbisSaveDataMemorySetupResult result{}; + const auto res = sceSaveDataSetupSaveDataMemory2(&setupParam, &result); + if (res != Error::OK) { + return res; } - - const auto& mount_dir = Common::FS::GetUserPath(Common::FS::PathType::SaveDataDir) / - std::to_string(userId) / game_serial / "sdmemory"; - - if (!std::filesystem::exists(mount_dir)) { - std::filesystem::create_directories(mount_dir); + if (param != nullptr) { + OrbisSaveDataMemorySet2 setParam{}; + setParam.userId = userId; + setParam.data = nullptr; + setParam.param = param; + setParam.icon = nullptr; + sceSaveDataSetSaveDataMemory2(&setParam); } - - // NOTE: Reminder that games can pass params: - // memset(param, 0, sizeof(param_t)); - // strncpy(param->title, "Beach Buggy Racing", 127); - - std::vector buf(memorySize); - Common::FS::IOFile::WriteBytes(mount_dir / "save_mem1.sav", buf); - return ORBIS_OK; + return Error::OK; } -int PS4_SYSV_ABI sceSaveDataSetupSaveDataMemory2(const OrbisSaveDataMemorySetup2* setupParam, - OrbisSaveDataMemorySetupResult* result) { +Error PS4_SYSV_ABI sceSaveDataSetupSaveDataMemory2(const OrbisSaveDataMemorySetup2* setupParam, + OrbisSaveDataMemorySetupResult* result) { + if (!g_initialized) { + LOG_INFO(Lib_SaveData, "called without initialize"); + return setNotInitializedError(); + } if (setupParam == nullptr) { - return ORBIS_SAVE_DATA_ERROR_PARAMETER; + LOG_INFO(Lib_SaveData, "called with invalid parameter"); + return Error::PARAMETER; } - LOG_INFO(Lib_SaveData, "called"); - // if (setupParam->option == 1) { // check this later. - const auto& mount_dir = Common::FS::GetUserPath(Common::FS::PathType::SaveDataDir) / - std::to_string(setupParam->userId) / game_serial / "sdmemory"; - if (std::filesystem::exists(mount_dir) && - std::filesystem::exists(mount_dir / "save_mem2.sav")) { - Common::FS::IOFile file(mount_dir / "save_mem2.sav", Common::FS::FileAccessMode::Read); - if (!file.IsOpen()) - return -1; - // Bunny - CUSA07988 has a null result, having null result is checked and valid. - if (result != nullptr) - result->existedMemorySize = file.GetSize(); // Assign the saved data size. - // do not return ORBIS_SAVE_DATA_ERROR_EXISTS, as it will not trigger - // sceSaveDataGetSaveDataMemory2. - } else { - std::filesystem::create_directories(mount_dir); - std::vector buf(setupParam->memorySize); // check if > 0x1000000 (16.77mb) or x2? - Common::FS::IOFile::WriteBytes(mount_dir / "save_mem2.sav", buf); - std::vector paramBuf(sizeof(OrbisSaveDataParam)); - Common::FS::IOFile::WriteBytes(mount_dir / "param.txt", paramBuf); - std::vector iconBuf(setupParam->iconMemorySize); - Common::FS::IOFile::WriteBytes(mount_dir / "save_icon.png", iconBuf); + LOG_DEBUG(Lib_SaveData, "called"); + + SaveMemory::SetDirectories(setupParam->userId, g_game_serial); + + const auto& save_path = SaveMemory::GetSavePath(); + for (const auto& instance : g_mount_slots) { + if (instance.has_value() && instance->GetSavePath() == save_path) { + return Error::BUSY; + } } - return ORBIS_OK; + + try { + size_t existed_size = SaveMemory::CreateSaveMemory(setupParam->memorySize); + if (existed_size == 0) { // Just created + if (g_fw_ver >= ElfInfo::FW_45 && setupParam->initParam != nullptr) { + auto& sfo = SaveMemory::GetParamSFO(); + setupParam->initParam->ToSFO(sfo); + } + SaveMemory::SaveSFO(); + + auto init_icon = setupParam->initIcon; + if (g_fw_ver >= ElfInfo::FW_45 && init_icon != nullptr) { + SaveMemory::SetIcon(init_icon->buf, init_icon->bufSize); + } else { + SaveMemory::SetIcon(nullptr, 0); + } + } + if (g_fw_ver >= ElfInfo::FW_45 && result != nullptr) { + result->existedMemorySize = existed_size; + } + } catch (const fs::filesystem_error& e) { + LOG_ERROR(Lib_SaveData, "Failed to create/load save memory: {}", e.what()); + + const MsgDialog::MsgDialogState dialog{MsgDialog::MsgDialogState::UserState{ + .type = MsgDialog::ButtonType::OK, + .msg = "Failed to create or load save memory:\n" + std::string{e.what()}, + }}; + MsgDialog::ShowMsgDialog(dialog); + + return Error::INTERNAL; + } + + return Error::OK; } int PS4_SYSV_ABI sceSaveDataShutdownStart() { @@ -835,14 +1606,44 @@ int PS4_SYSV_ABI sceSaveDataSyncCloudList() { return ORBIS_OK; } -int PS4_SYSV_ABI sceSaveDataSyncSaveDataMemory(OrbisSaveDataMemorySync* syncParam) { - LOG_ERROR(Lib_SaveData, "(STUBBED) called: option = {}", syncParam->option); - return ORBIS_OK; +Error PS4_SYSV_ABI sceSaveDataSyncSaveDataMemory(OrbisSaveDataMemorySync* syncParam) { + if (!g_initialized) { + LOG_INFO(Lib_SaveData, "called without initialize"); + return setNotInitializedError(); + } + if (syncParam == nullptr) { + LOG_INFO(Lib_SaveData, "called with invalid parameter"); + return Error::PARAMETER; + } + if (!SaveMemory::IsSaveMemoryInitialized()) { + LOG_INFO(Lib_SaveData, "called without save memory initialized"); + return Error::MEMORY_NOT_READY; + } + LOG_DEBUG(Lib_SaveData, "called"); + bool ok = SaveMemory::TriggerSave(); + if (!ok) { + return Error::BUSY_FOR_SAVING; + } + return Error::OK; } -int PS4_SYSV_ABI sceSaveDataTerminate() { - LOG_ERROR(Lib_SaveData, "(STUBBED) called"); - return ORBIS_OK; +Error PS4_SYSV_ABI sceSaveDataTerminate() { + LOG_DEBUG(Lib_SaveData, "called"); + if (!g_initialized) { + return setNotInitializedError(); + } + for (auto& instance : g_mount_slots) { + if (instance.has_value()) { + if (g_fw_ver >= ElfInfo::FW_40) { + return Error::BUSY; + } + instance->Umount(); + instance.reset(); + } + } + g_initialized = false; + Backup::StopThread(); + return Error::OK; } int PS4_SYSV_ABI sceSaveDataTransferringMount() { @@ -850,19 +1651,9 @@ int PS4_SYSV_ABI sceSaveDataTransferringMount() { return ORBIS_OK; } -s32 PS4_SYSV_ABI sceSaveDataUmount(const OrbisSaveDataMountPoint* mountPoint) { - LOG_INFO(Lib_SaveData, "mountPoint = {}", mountPoint->data); - if (std::string_view(mountPoint->data).empty()) { - return ORBIS_SAVE_DATA_ERROR_NOT_MOUNTED; - } - const auto& mount_dir = Common::FS::GetUserPath(Common::FS::PathType::SaveDataDir) / - std::to_string(1) / game_serial / mountPoint->data; - auto* mnt = Common::Singleton::Instance(); - const auto& guest_path = mnt->GetHostPath(mountPoint->data); - if (guest_path.empty()) - return ORBIS_SAVE_DATA_ERROR_NOT_MOUNTED; - mnt->Unmount(mount_dir, mountPoint->data); - return ORBIS_OK; +Error PS4_SYSV_ABI sceSaveDataUmount(const OrbisSaveDataMountPoint* mountPoint) { + LOG_DEBUG(Lib_SaveData, "called"); + return Umount(mountPoint); } int PS4_SYSV_ABI sceSaveDataUmountSys() { @@ -870,37 +1661,9 @@ int PS4_SYSV_ABI sceSaveDataUmountSys() { return ORBIS_OK; } -int PS4_SYSV_ABI sceSaveDataUmountWithBackup(const OrbisSaveDataMountPoint* mountPoint) { - LOG_INFO(Lib_SaveData, "called mount = {}, is_rw_mode = {}", std::string(mountPoint->data), - is_rw_mode); - auto* mnt = Common::Singleton::Instance(); - const auto mount_dir = mnt->GetHostPath(mountPoint->data); - if (!std::filesystem::exists(mount_dir)) { - return ORBIS_SAVE_DATA_ERROR_NOT_FOUND; - } - // leave disabled for now. and just unmount. - - /* if (is_rw_mode) { // backup is done only when mount mode is ReadWrite. - auto backup_path = mount_dir; - std::string save_data_dir = (mount_dir.string() + "_backup"); - backup_path.replace_filename(save_data_dir); - - std::filesystem::create_directories(backup_path); - - for (const auto& entry : std::filesystem::recursive_directory_iterator(mount_dir)) { - const auto& path = entry.path(); - if (std::filesystem::is_regular_file(path)) { - std::filesystem::copy(path, save_data_dir, - std::filesystem::copy_options::overwrite_existing); - } - } - }*/ - const auto& guest_path = mnt->GetHostPath(mountPoint->data); - if (guest_path.empty()) - return ORBIS_SAVE_DATA_ERROR_NOT_MOUNTED; - - mnt->Unmount(mount_dir, mountPoint->data); - return ORBIS_OK; +Error PS4_SYSV_ABI sceSaveDataUmountWithBackup(const OrbisSaveDataMountPoint* mountPoint) { + LOG_DEBUG(Lib_SaveData, "called"); + return Umount(mountPoint, true); } int PS4_SYSV_ABI sceSaveDataUnregisterEventCallback() { diff --git a/src/core/libraries/save_data/savedata.h b/src/core/libraries/save_data/savedata.h index 9b3cf900f..13b3dd59e 100644 --- a/src/core/libraries/save_data/savedata.h +++ b/src/core/libraries/save_data/savedata.h @@ -3,259 +3,81 @@ #pragma once +#include "common/cstring.h" #include "common/types.h" namespace Core::Loader { class SymbolsResolver; } +class PSF; + namespace Libraries::SaveData { -constexpr int ORBIS_SAVE_DATA_DIRNAME_DATA_MAXSIZE = - 32; // Maximum size for a save data directory name -constexpr int ORBIS_SAVE_DATA_MOUNT_POINT_DATA_MAXSIZE = 16; // Maximum size for a mount point name +constexpr size_t OrbisSaveDataTitleMaxsize = 128; // Maximum title name size +constexpr size_t OrbisSaveDataSubtitleMaxsize = 128; // Maximum subtitle name size +constexpr size_t OrbisSaveDataDetailMaxsize = 1024; // Maximum detail name size + +enum class Error : u32; +enum class OrbisSaveDataParamType : u32; + +using OrbisUserServiceUserId = s32; + +// Maximum size for a title ID (4 uppercase letters + 5 digits) +constexpr int OrbisSaveDataTitleIdDataSize = 10; +// Maximum save directory name size +constexpr int OrbisSaveDataDirnameDataMaxsize = 32; + +struct OrbisSaveDataTitleId { + Common::CString data; + std::array _pad; +}; struct OrbisSaveDataDirName { - char data[ORBIS_SAVE_DATA_DIRNAME_DATA_MAXSIZE]; + Common::CString data; }; -struct OrbisSaveDataMount2 { - s32 user_id; - s32 unk1; - const OrbisSaveDataDirName* dir_name; - u64 blocks; - u32 mount_mode; - u8 reserved[32]; - s32 unk2; -}; - -struct OrbisSaveDataMountPoint { - char data[ORBIS_SAVE_DATA_MOUNT_POINT_DATA_MAXSIZE]; -}; - -struct OrbisSaveDataMountResult { - OrbisSaveDataMountPoint mount_point; - u64 required_blocks; - u32 unused; - u32 mount_status; - u8 reserved[28]; - s32 unk1; -}; - -constexpr int ORBIS_SAVE_DATA_TITLE_ID_DATA_SIZE = 10; -struct OrbisSaveDataTitleId { - char data[ORBIS_SAVE_DATA_TITLE_ID_DATA_SIZE]; - char padding[6]; -}; - -constexpr int ORBIS_SAVE_DATA_FINGERPRINT_DATA_SIZE = 65; -struct OrbisSaveDataFingerprint { - char data[ORBIS_SAVE_DATA_FINGERPRINT_DATA_SIZE]; - char padding[15]; -}; - -struct OrbisSaveDataMount { - s32 user_id; - s32 pad; - const OrbisSaveDataTitleId* titleId; - const OrbisSaveDataDirName* dir_name; - const OrbisSaveDataFingerprint* fingerprint; - u64 blocks; - u32 mount_mode; - u8 reserved[32]; -}; - -typedef u32 OrbisSaveDataParamType; - -constexpr int ORBIS_SAVE_DATA_TITLE_MAXSIZE = 128; -constexpr int ORBIS_SAVE_DATA_SUBTITLE_MAXSIZE = 128; -constexpr int ORBIS_SAVE_DATA_DETAIL_MAXSIZE = 1024; struct OrbisSaveDataParam { - char title[ORBIS_SAVE_DATA_TITLE_MAXSIZE]; - char subTitle[ORBIS_SAVE_DATA_SUBTITLE_MAXSIZE]; - char detail[ORBIS_SAVE_DATA_DETAIL_MAXSIZE]; + Common::CString title; + Common::CString subTitle; + Common::CString detail; u32 userParam; int : 32; time_t mtime; - u8 reserved[32]; + std::array _reserved; + + void FromSFO(const PSF& sfo); + + void ToSFO(PSF& sfo) const; }; -struct OrbisSaveDataIcon { - void* buf; - size_t bufSize; - size_t dataSize; - u8 reserved[32]; -}; - -typedef u32 OrbisSaveDataSaveDataMemoryOption; -#define ORBIS_SAVE_DATA_MEMORY_OPTION_NONE (0x00000000) -#define ORBIS_SAVE_DATA_MEMORY_OPTION_SET_PARAM (0x00000001 << 0) -#define ORBIS_SAVE_DATA_MEMORY_OPTION_DOUBLE_BUFFER (0x00000001 << 1) - -struct OrbisSaveDataMemorySetup2 { - OrbisSaveDataSaveDataMemoryOption option; - s32 userId; - size_t memorySize; - size_t iconMemorySize; - const OrbisSaveDataParam* initParam; - const OrbisSaveDataIcon* initIcon; - u32 slotId; - u8 reserved[20]; -}; - -struct OrbisSaveDataMemorySetupResult { - size_t existedMemorySize; - u8 reserved[16]; -}; - -typedef u32 OrbisSaveDataEventType; -#define SCE_SAVE_DATA_EVENT_TYPE_INVALID (0) -#define SCE_SAVE_DATA_EVENT_TYPE_UMOUNT_BACKUP_END (1) -#define SCE_SAVE_DATA_EVENT_TYPE_BACKUP_END (2) -#define SCE_SAVE_DATA_EVENT_TYPE_SAVE_DATA_MEMORY_SYNC_END (3) - -struct OrbisSaveDataEvent { - OrbisSaveDataEventType type; - s32 errorCode; - s32 userId; - u8 padding[4]; - OrbisSaveDataTitleId titleId; - OrbisSaveDataDirName dirName; - u8 reserved[40]; -}; - -struct OrbisSaveDataMemoryData { - void* buf; - size_t bufSize; - off_t offset; - u8 reserved[40]; -}; - -struct OrbisSaveDataMemoryGet2 { - s32 userId; - u8 padding[4]; - OrbisSaveDataMemoryData* data; - OrbisSaveDataParam* param; - OrbisSaveDataIcon* icon; - u32 slotId; - u8 reserved[28]; -}; - -struct OrbisSaveDataMemorySet2 { - s32 userId; - u8 padding[4]; - const OrbisSaveDataMemoryData* data; - const OrbisSaveDataParam* param; - const OrbisSaveDataIcon* icon; - u32 dataNum; - u8 slotId; - u8 reserved[24]; -}; - -struct OrbisSaveDataCheckBackupData { - s32 userId; - int : 32; - const OrbisSaveDataTitleId* titleId; - const OrbisSaveDataDirName* dirName; - OrbisSaveDataParam* param; - OrbisSaveDataIcon* icon; - u8 reserved[32]; -}; - -struct OrbisSaveDataMountInfo { - u64 blocks; - u64 freeBlocks; - u8 reserved[32]; -}; - -#define ORBIS_SAVE_DATA_BLOCK_SIZE (32768) -#define ORBIS_SAVE_DATA_BLOCKS_MIN2 (96) -#define ORBIS_SAVE_DATA_BLOCKS_MAX (32768) - -// savedataMount2 mountModes (ORed values) -constexpr int ORBIS_SAVE_DATA_MOUNT_MODE_RDONLY = 1; -constexpr int ORBIS_SAVE_DATA_MOUNT_MODE_RDWR = 2; -constexpr int ORBIS_SAVE_DATA_MOUNT_MODE_CREATE = 4; -constexpr int ORBIS_SAVE_DATA_MOUNT_MODE_DESTRUCT_OFF = 8; -constexpr int ORBIS_SAVE_DATA_MOUNT_MODE_COPY_ICON = 16; -constexpr int ORBIS_SAVE_DATA_MOUNT_MODE_CREATE2 = 32; -typedef struct _OrbisSaveDataEventParam OrbisSaveDataEventParam; - -typedef u32 OrbisSaveDataSortKey; -#define ORBIS_SAVE_DATA_SORT_KEY_DIRNAME (0) -#define ORBIS_SAVE_DATA_SORT_KEY_USER_PARAM (1) -#define ORBIS_SAVE_DATA_SORT_KEY_BLOCKS (2) -#define ORBIS_SAVE_DATA_SORT_KEY_MTIME (3) -#define ORBIS_SAVE_DATA_SORT_KEY_FREE_BLOCKS (5) - -typedef u32 OrbisSaveDataSortOrder; -#define ORBIS_SAVE_DATA_SORT_ORDER_ASCENT (0) -#define ORBIS_SAVE_DATA_SORT_ORDER_DESCENT (1) - -struct OrbisSaveDataDirNameSearchCond { - s32 userId; - int : 32; - const OrbisSaveDataTitleId* titleId; - const OrbisSaveDataDirName* dirName; - OrbisSaveDataSortKey key; - OrbisSaveDataSortOrder order; - u8 reserved[32]; -}; - -struct OrbisSaveDataSearchInfo { - u64 blocks; - u64 freeBlocks; - u8 reserved[32]; -}; - -struct OrbisSaveDataDirNameSearchResult { - u32 hitNum; - int : 32; - OrbisSaveDataDirName* dirNames; - u32 dirNamesNum; - u32 setNum; - OrbisSaveDataParam* params; - OrbisSaveDataSearchInfo* infos; - u8 reserved[12]; - int : 32; -}; - -struct OrbisSaveDataDelete { - s32 userId; - int : 32; - const OrbisSaveDataTitleId* titleId; - const OrbisSaveDataDirName* dirName; - u32 unused; - u8 reserved[32]; - int : 32; -}; - -typedef u32 OrbisSaveDataMemorySyncOption; - -#define SCE_SAVE_DATA_MEMORY_SYNC_OPTION_NONE (0x00000000) -#define SCE_SAVE_DATA_MEMORY_SYNC_OPTION_BLOCKING (0x00000001 << 0) - -struct OrbisSaveDataMemorySync { - s32 userId; - u32 slotId; - OrbisSaveDataMemorySyncOption option; - u8 reserved[28]; -}; - -constexpr int ORBIS_SAVE_DATA_PARAM_TYPE_ALL = 0; -constexpr int ORBIS_SAVE_DATA_PARAM_TYPE_TITLE = 1; -constexpr int ORBIS_SAVE_DATA_PARAM_TYPE_SUB_TITLE = 2; -constexpr int ORBIS_SAVE_DATA_PARAM_TYPE_DETAIL = 3; -constexpr int ORBIS_SAVE_DATA_PARAM_TYPE_USER_PARAM = 4; -constexpr int ORBIS_SAVE_DATA_PARAM_TYPE_MTIME = 5; +struct OrbisSaveDataBackup; +struct OrbisSaveDataCheckBackupData; +struct OrbisSaveDataDelete; +struct OrbisSaveDataDirNameSearchCond; +struct OrbisSaveDataDirNameSearchResult; +struct OrbisSaveDataEvent; +struct OrbisSaveDataEventParam; +struct OrbisSaveDataIcon; +struct OrbisSaveDataMemoryGet2; +struct OrbisSaveDataMemorySet2; +struct OrbisSaveDataMemorySetup2; +struct OrbisSaveDataMemorySetupResult; +struct OrbisSaveDataMemorySync; +struct OrbisSaveDataMount2; +struct OrbisSaveDataMount; +struct OrbisSaveDataMountInfo; +struct OrbisSaveDataMountPoint; +struct OrbisSaveDataMountResult; +struct OrbisSaveDataRestoreBackupData; int PS4_SYSV_ABI sceSaveDataAbort(); -int PS4_SYSV_ABI sceSaveDataBackup(); +Error PS4_SYSV_ABI sceSaveDataBackup(const OrbisSaveDataBackup* backup); int PS4_SYSV_ABI sceSaveDataBindPsnAccount(); int PS4_SYSV_ABI sceSaveDataBindPsnAccountForSystemBackup(); int PS4_SYSV_ABI sceSaveDataChangeDatabase(); int PS4_SYSV_ABI sceSaveDataChangeInternal(); -int PS4_SYSV_ABI sceSaveDataCheckBackupData(const OrbisSaveDataCheckBackupData* check); +Error PS4_SYSV_ABI sceSaveDataCheckBackupData(const OrbisSaveDataCheckBackupData* check); int PS4_SYSV_ABI sceSaveDataCheckBackupDataForCdlg(); int PS4_SYSV_ABI sceSaveDataCheckBackupDataInternal(); int PS4_SYSV_ABI sceSaveDataCheckCloudData(); @@ -263,7 +85,7 @@ int PS4_SYSV_ABI sceSaveDataCheckIpmiIfSize(); int PS4_SYSV_ABI sceSaveDataCheckSaveDataBroken(); int PS4_SYSV_ABI sceSaveDataCheckSaveDataVersion(); int PS4_SYSV_ABI sceSaveDataCheckSaveDataVersionLatest(); -int PS4_SYSV_ABI sceSaveDataClearProgress(); +Error PS4_SYSV_ABI sceSaveDataClearProgress(); int PS4_SYSV_ABI sceSaveDataCopy5(); int PS4_SYSV_ABI sceSaveDataCreateUploadData(); int PS4_SYSV_ABI sceSaveDataDebug(); @@ -273,13 +95,13 @@ int PS4_SYSV_ABI sceSaveDataDebugCreateSaveDataRoot(); int PS4_SYSV_ABI sceSaveDataDebugGetThreadId(); int PS4_SYSV_ABI sceSaveDataDebugRemoveSaveDataRoot(); int PS4_SYSV_ABI sceSaveDataDebugTarget(); -int PS4_SYSV_ABI sceSaveDataDelete(const OrbisSaveDataDelete* del); +Error PS4_SYSV_ABI sceSaveDataDelete(const OrbisSaveDataDelete* del); int PS4_SYSV_ABI sceSaveDataDelete5(); int PS4_SYSV_ABI sceSaveDataDeleteAllUser(); int PS4_SYSV_ABI sceSaveDataDeleteCloudData(); int PS4_SYSV_ABI sceSaveDataDeleteUser(); -int PS4_SYSV_ABI sceSaveDataDirNameSearch(const OrbisSaveDataDirNameSearchCond* cond, - OrbisSaveDataDirNameSearchResult* result); +Error PS4_SYSV_ABI sceSaveDataDirNameSearch(const OrbisSaveDataDirNameSearchCond* cond, + OrbisSaveDataDirNameSearchResult* result); int PS4_SYSV_ABI sceSaveDataDirNameSearchInternal(); int PS4_SYSV_ABI sceSaveDataDownload(); int PS4_SYSV_ABI sceSaveDataGetAllSize(); @@ -292,70 +114,70 @@ int PS4_SYSV_ABI sceSaveDataGetClientThreadPriority(); int PS4_SYSV_ABI sceSaveDataGetCloudQuotaInfo(); int PS4_SYSV_ABI sceSaveDataGetDataBaseFilePath(); int PS4_SYSV_ABI sceSaveDataGetEventInfo(); -int PS4_SYSV_ABI sceSaveDataGetEventResult(const OrbisSaveDataEventParam* eventParam, - OrbisSaveDataEvent* event); +Error PS4_SYSV_ABI sceSaveDataGetEventResult(const OrbisSaveDataEventParam* eventParam, + OrbisSaveDataEvent* event); int PS4_SYSV_ABI sceSaveDataGetFormat(); int PS4_SYSV_ABI sceSaveDataGetMountedSaveDataCount(); -int PS4_SYSV_ABI sceSaveDataGetMountInfo(const OrbisSaveDataMountPoint* mountPoint, - OrbisSaveDataMountInfo* info); -int PS4_SYSV_ABI sceSaveDataGetParam(const OrbisSaveDataMountPoint* mountPoint, - const OrbisSaveDataParamType paramType, void* paramBuf, - const size_t paramBufSize, size_t* gotSize); -int PS4_SYSV_ABI sceSaveDataGetProgress(); +Error PS4_SYSV_ABI sceSaveDataGetMountInfo(const OrbisSaveDataMountPoint* mountPoint, + OrbisSaveDataMountInfo* info); +Error PS4_SYSV_ABI sceSaveDataGetParam(const OrbisSaveDataMountPoint* mountPoint, + OrbisSaveDataParamType paramType, void* paramBuf, + size_t paramBufSize, size_t* gotSize); +Error PS4_SYSV_ABI sceSaveDataGetProgress(float* progress); int PS4_SYSV_ABI sceSaveDataGetSaveDataCount(); -int PS4_SYSV_ABI sceSaveDataGetSaveDataMemory(const u32 userId, void* buf, const size_t bufSize, - const int64_t offset); -int PS4_SYSV_ABI sceSaveDataGetSaveDataMemory2(OrbisSaveDataMemoryGet2* getParam); +Error PS4_SYSV_ABI sceSaveDataGetSaveDataMemory(OrbisUserServiceUserId userId, void* buf, + size_t bufSize, int64_t offset); +Error PS4_SYSV_ABI sceSaveDataGetSaveDataMemory2(OrbisSaveDataMemoryGet2* getParam); int PS4_SYSV_ABI sceSaveDataGetSaveDataRootDir(); int PS4_SYSV_ABI sceSaveDataGetSaveDataRootPath(); int PS4_SYSV_ABI sceSaveDataGetSaveDataRootUsbPath(); int PS4_SYSV_ABI sceSaveDataGetSavePoint(); int PS4_SYSV_ABI sceSaveDataGetUpdatedDataCount(); -int PS4_SYSV_ABI sceSaveDataInitialize(); -int PS4_SYSV_ABI sceSaveDataInitialize2(); -int PS4_SYSV_ABI sceSaveDataInitialize3(); +Error PS4_SYSV_ABI sceSaveDataInitialize(void*); +Error PS4_SYSV_ABI sceSaveDataInitialize2(void*); +Error PS4_SYSV_ABI sceSaveDataInitialize3(void*); int PS4_SYSV_ABI sceSaveDataInitializeForCdlg(); int PS4_SYSV_ABI sceSaveDataIsDeletingUsbDb(); int PS4_SYSV_ABI sceSaveDataIsMounted(); -int PS4_SYSV_ABI sceSaveDataLoadIcon(const OrbisSaveDataMountPoint* mountPoint, - OrbisSaveDataIcon* icon); -int PS4_SYSV_ABI sceSaveDataMount(const OrbisSaveDataMount* mount, - OrbisSaveDataMountResult* mount_result); -s32 PS4_SYSV_ABI sceSaveDataMount2(const OrbisSaveDataMount2* mount, - OrbisSaveDataMountResult* mount_result); +Error PS4_SYSV_ABI sceSaveDataLoadIcon(const OrbisSaveDataMountPoint* mountPoint, + OrbisSaveDataIcon* icon); +Error PS4_SYSV_ABI sceSaveDataMount(const OrbisSaveDataMount* mount, + OrbisSaveDataMountResult* mount_result); +Error PS4_SYSV_ABI sceSaveDataMount2(const OrbisSaveDataMount2* mount, + OrbisSaveDataMountResult* mount_result); int PS4_SYSV_ABI sceSaveDataMount5(); int PS4_SYSV_ABI sceSaveDataMountInternal(); int PS4_SYSV_ABI sceSaveDataMountSys(); int PS4_SYSV_ABI sceSaveDataPromote5(); int PS4_SYSV_ABI sceSaveDataRebuildDatabase(); int PS4_SYSV_ABI sceSaveDataRegisterEventCallback(); -int PS4_SYSV_ABI sceSaveDataRestoreBackupData(); +Error PS4_SYSV_ABI sceSaveDataRestoreBackupData(const OrbisSaveDataRestoreBackupData* restore); int PS4_SYSV_ABI sceSaveDataRestoreBackupDataForCdlg(); int PS4_SYSV_ABI sceSaveDataRestoreLoadSaveDataMemory(); -int PS4_SYSV_ABI sceSaveDataSaveIcon(const OrbisSaveDataMountPoint* mountPoint, - const OrbisSaveDataIcon* icon); +Error PS4_SYSV_ABI sceSaveDataSaveIcon(const OrbisSaveDataMountPoint* mountPoint, + const OrbisSaveDataIcon* icon); int PS4_SYSV_ABI sceSaveDataSetAutoUploadSetting(); int PS4_SYSV_ABI sceSaveDataSetEventInfo(); -int PS4_SYSV_ABI sceSaveDataSetParam(const OrbisSaveDataMountPoint* mountPoint, - OrbisSaveDataParamType paramType, const void* paramBuf, - size_t paramBufSize); +Error PS4_SYSV_ABI sceSaveDataSetParam(const OrbisSaveDataMountPoint* mountPoint, + OrbisSaveDataParamType paramType, const void* paramBuf, + size_t paramBufSize); int PS4_SYSV_ABI sceSaveDataSetSaveDataLibraryUser(); -int PS4_SYSV_ABI sceSaveDataSetSaveDataMemory(const u32 userId, const void* buf, - const size_t bufSize, const int64_t offset); -int PS4_SYSV_ABI sceSaveDataSetSaveDataMemory2(const OrbisSaveDataMemorySet2* setParam); -int PS4_SYSV_ABI sceSaveDataSetupSaveDataMemory(u32 userId, size_t memorySize, - OrbisSaveDataParam* param); -int PS4_SYSV_ABI sceSaveDataSetupSaveDataMemory2(const OrbisSaveDataMemorySetup2* setupParam, - OrbisSaveDataMemorySetupResult* result); +Error PS4_SYSV_ABI sceSaveDataSetSaveDataMemory(OrbisUserServiceUserId userId, void* buf, + size_t bufSize, int64_t offset); +Error PS4_SYSV_ABI sceSaveDataSetSaveDataMemory2(const OrbisSaveDataMemorySet2* setParam); +Error PS4_SYSV_ABI sceSaveDataSetupSaveDataMemory(OrbisUserServiceUserId userId, size_t memorySize, + OrbisSaveDataParam* param); +Error PS4_SYSV_ABI sceSaveDataSetupSaveDataMemory2(const OrbisSaveDataMemorySetup2* setupParam, + OrbisSaveDataMemorySetupResult* result); int PS4_SYSV_ABI sceSaveDataShutdownStart(); int PS4_SYSV_ABI sceSaveDataSupportedFakeBrokenStatus(); int PS4_SYSV_ABI sceSaveDataSyncCloudList(); -int PS4_SYSV_ABI sceSaveDataSyncSaveDataMemory(OrbisSaveDataMemorySync* syncParam); -int PS4_SYSV_ABI sceSaveDataTerminate(); +Error PS4_SYSV_ABI sceSaveDataSyncSaveDataMemory(OrbisSaveDataMemorySync* syncParam); +Error PS4_SYSV_ABI sceSaveDataTerminate(); int PS4_SYSV_ABI sceSaveDataTransferringMount(); -int PS4_SYSV_ABI sceSaveDataUmount(const OrbisSaveDataMountPoint* mountPoint); +Error PS4_SYSV_ABI sceSaveDataUmount(const OrbisSaveDataMountPoint* mountPoint); int PS4_SYSV_ABI sceSaveDataUmountSys(); -int PS4_SYSV_ABI sceSaveDataUmountWithBackup(const OrbisSaveDataMountPoint* mountPoint); +Error PS4_SYSV_ABI sceSaveDataUmountWithBackup(const OrbisSaveDataMountPoint* mountPoint); int PS4_SYSV_ABI sceSaveDataUnregisterEventCallback(); int PS4_SYSV_ABI sceSaveDataUpload(); int PS4_SYSV_ABI Func_02E4C4D201716422(); diff --git a/src/core/libraries/system/msgdialog.cpp b/src/core/libraries/system/msgdialog.cpp index 94c122d9b..7d924e4ad 100644 --- a/src/core/libraries/system/msgdialog.cpp +++ b/src/core/libraries/system/msgdialog.cpp @@ -39,11 +39,6 @@ Error PS4_SYSV_ABI sceMsgDialogGetResult(DialogResult* result) { if (result == nullptr) { return Error::ARG_NULL; } - for (const auto v : result->reserved) { - if (v != 0) { - return Error::PARAM_INVALID; - } - } *result = g_result; return Error::OK; } diff --git a/src/core/libraries/system/msgdialog_ui.cpp b/src/core/libraries/system/msgdialog_ui.cpp index 63b3390c9..15d6f4dbd 100644 --- a/src/core/libraries/system/msgdialog_ui.cpp +++ b/src/core/libraries/system/msgdialog_ui.cpp @@ -1,6 +1,8 @@ // SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later +#include + #include #include "common/assert.h" #include "imgui/imgui_std.h" @@ -31,18 +33,6 @@ struct { }; static_assert(std::size(user_button_texts) == static_cast(ButtonType::TWO_BUTTONS) + 1); -static void DrawCenteredText(const char* text) { - const auto ws = GetWindowSize(); - const auto text_size = CalcTextSize(text, nullptr, false, ws.x - 40.0f); - PushTextWrapPos(ws.x - 30.0f); - SetCursorPos({ - (ws.x - text_size.x) / 2.0f, - (ws.y - text_size.y) / 2.0f - 50.0f, - }); - Text("%s", text); - PopTextWrapPos(); -} - MsgDialogState::MsgDialogState(const OrbisParam& param) { this->mode = param.mode; switch (mode) { @@ -81,11 +71,29 @@ MsgDialogState::MsgDialogState(const OrbisParam& param) { } } +MsgDialogState::MsgDialogState(UserState mode) { + this->mode = MsgDialogMode::USER_MSG; + this->state = mode; +} + +MsgDialogState::MsgDialogState(ProgressState mode) { + this->mode = MsgDialogMode::PROGRESS_BAR; + this->state = mode; +} + +MsgDialogState::MsgDialogState(SystemState mode) { + this->mode = MsgDialogMode::SYSTEM_MSG; + this->state = mode; +} + void MsgDialogUi::DrawUser() { const auto& [button_type, msg, btn_param1, btn_param2] = state->GetState(); const auto ws = GetWindowSize(); - DrawCenteredText(msg.c_str()); + if (!msg.empty()) { + DrawCenteredText(&msg.front(), &msg.back() + 1, + GetContentRegionAvail() - ImVec2{0.0f, 15.0f + BUTTON_SIZE.y}); + } ASSERT(button_type <= ButtonType::TWO_BUTTONS); auto [count, text1, text2] = user_button_texts[static_cast(button_type)]; if (count == 0xFF) { // TWO_BUTTONS -> User defined message @@ -115,7 +123,7 @@ void MsgDialogUi::DrawUser() { break; } } - if (first_render && !focus_first) { + if ((first_render || IsKeyPressed(ImGuiKey_GamepadFaceRight)) && !focus_first) { SetItemCurrentNavFocus(); } PopID(); @@ -125,7 +133,7 @@ void MsgDialogUi::DrawUser() { if (Button(text1, BUTTON_SIZE)) { Finish(ButtonId::BUTTON1); } - if (first_render && focus_first) { + if ((first_render || IsKeyPressed(ImGuiKey_GamepadFaceRight)) && focus_first) { SetItemCurrentNavFocus(); } PopID(); @@ -249,11 +257,13 @@ void MsgDialogUi::Draw() { CentralizeWindow(); SetNextWindowSize(window_size); - SetNextWindowFocus(); SetNextWindowCollapsed(false); + if (first_render || !io.NavActive) { + SetNextWindowFocus(); + } KeepNavHighlight(); - // Hack to allow every dialog to have a unique window - if (Begin("Message Dialog##MessageDialog", nullptr, ImGuiWindowFlags_NoSavedSettings)) { + if (Begin("Message Dialog##MessageDialog", nullptr, + ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoSavedSettings)) { switch (state->GetMode()) { case MsgDialogMode::USER_MSG: DrawUser(); @@ -269,4 +279,16 @@ void MsgDialogUi::Draw() { End(); first_render = false; -} \ No newline at end of file +} + +DialogResult Libraries::MsgDialog::ShowMsgDialog(MsgDialogState state, bool block) { + DialogResult result{}; + Status status = Status::RUNNING; + MsgDialogUi dialog(&state, &status, &result); + if (block) { + while (status == Status::RUNNING) { + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + } + } + return result; +} diff --git a/src/core/libraries/system/msgdialog_ui.h b/src/core/libraries/system/msgdialog_ui.h index 845abdc43..d24ec067c 100644 --- a/src/core/libraries/system/msgdialog_ui.h +++ b/src/core/libraries/system/msgdialog_ui.h @@ -3,6 +3,7 @@ #pragma once +#include #include #include "common/fixed_value.h" @@ -129,6 +130,11 @@ private: public: explicit MsgDialogState(const OrbisParam& param); + + explicit MsgDialogState(UserState mode); + explicit MsgDialogState(ProgressState mode); + explicit MsgDialogState(SystemState mode); + MsgDialogState() = default; [[nodiscard]] OrbisUserServiceUserId GetUserId() const { @@ -165,13 +171,11 @@ public: void Finish(ButtonId buttonId, CommonDialog::Result r = CommonDialog::Result::OK); - void SetProgressBarValue(u32 value, bool increment); - void Draw() override; - - bool ShouldGrabGamepad() override { - return true; - } }; +// Utility function to show a message dialog +// !!! This function can block !!! +DialogResult ShowMsgDialog(MsgDialogState state, bool block = true); + }; // namespace Libraries::MsgDialog \ No newline at end of file diff --git a/src/core/libraries/system/savedatadialog.cpp b/src/core/libraries/system/savedatadialog.cpp deleted file mode 100644 index 5aad480d0..000000000 --- a/src/core/libraries/system/savedatadialog.cpp +++ /dev/null @@ -1,84 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#include "common/logging/log.h" -#include "core/libraries/error_codes.h" -#include "core/libraries/libs.h" -#include "core/libraries/system/savedatadialog.h" - -namespace Libraries::SaveDataDialog { - -int PS4_SYSV_ABI sceSaveDataDialogClose() { - LOG_ERROR(Lib_SaveDataDialog, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceSaveDataDialogGetResult() { - LOG_ERROR(Lib_SaveDataDialog, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceSaveDataDialogGetStatus() { - LOG_ERROR(Lib_SaveDataDialog, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceSaveDataDialogInitialize() { - LOG_ERROR(Lib_SaveDataDialog, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceSaveDataDialogIsReadyToDisplay() { - LOG_ERROR(Lib_SaveDataDialog, "(STUBBED) called"); - return 1; -} - -int PS4_SYSV_ABI sceSaveDataDialogOpen() { - LOG_ERROR(Lib_SaveDataDialog, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceSaveDataDialogProgressBarInc() { - LOG_ERROR(Lib_SaveDataDialog, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceSaveDataDialogProgressBarSetValue() { - LOG_ERROR(Lib_SaveDataDialog, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceSaveDataDialogTerminate() { - LOG_ERROR(Lib_SaveDataDialog, "(STUBBED) called"); - return ORBIS_OK; -} - -int PS4_SYSV_ABI sceSaveDataDialogUpdateStatus() { - LOG_ERROR(Lib_SaveDataDialog, "(STUBBED) called"); - return 3; // SCE_COMMON_DIALOG_STATUS_FINISHED -} - -void RegisterlibSceSaveDataDialog(Core::Loader::SymbolsResolver* sym) { - LIB_FUNCTION("fH46Lag88XY", "libSceSaveDataDialog", 1, "libSceSaveDataDialog", 1, 1, - sceSaveDataDialogClose); - LIB_FUNCTION("yEiJ-qqr6Cg", "libSceSaveDataDialog", 1, "libSceSaveDataDialog", 1, 1, - sceSaveDataDialogGetResult); - LIB_FUNCTION("ERKzksauAJA", "libSceSaveDataDialog", 1, "libSceSaveDataDialog", 1, 1, - sceSaveDataDialogGetStatus); - LIB_FUNCTION("s9e3+YpRnzw", "libSceSaveDataDialog", 1, "libSceSaveDataDialog", 1, 1, - sceSaveDataDialogInitialize); - LIB_FUNCTION("en7gNVnh878", "libSceSaveDataDialog", 1, "libSceSaveDataDialog", 1, 1, - sceSaveDataDialogIsReadyToDisplay); - LIB_FUNCTION("4tPhsP6FpDI", "libSceSaveDataDialog", 1, "libSceSaveDataDialog", 1, 1, - sceSaveDataDialogOpen); - LIB_FUNCTION("V-uEeFKARJU", "libSceSaveDataDialog", 1, "libSceSaveDataDialog", 1, 1, - sceSaveDataDialogProgressBarInc); - LIB_FUNCTION("hay1CfTmLyA", "libSceSaveDataDialog", 1, "libSceSaveDataDialog", 1, 1, - sceSaveDataDialogProgressBarSetValue); - LIB_FUNCTION("YuH2FA7azqQ", "libSceSaveDataDialog", 1, "libSceSaveDataDialog", 1, 1, - sceSaveDataDialogTerminate); - LIB_FUNCTION("KK3Bdg1RWK0", "libSceSaveDataDialog", 1, "libSceSaveDataDialog", 1, 1, - sceSaveDataDialogUpdateStatus); -}; - -} // namespace Libraries::SaveDataDialog diff --git a/src/core/libraries/system/savedatadialog.h b/src/core/libraries/system/savedatadialog.h deleted file mode 100644 index e8fe7c75f..000000000 --- a/src/core/libraries/system/savedatadialog.h +++ /dev/null @@ -1,26 +0,0 @@ -// 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::SaveDataDialog { - -int PS4_SYSV_ABI sceSaveDataDialogClose(); -int PS4_SYSV_ABI sceSaveDataDialogGetResult(); -int PS4_SYSV_ABI sceSaveDataDialogGetStatus(); -int PS4_SYSV_ABI sceSaveDataDialogInitialize(); -int PS4_SYSV_ABI sceSaveDataDialogIsReadyToDisplay(); -int PS4_SYSV_ABI sceSaveDataDialogOpen(); -int PS4_SYSV_ABI sceSaveDataDialogProgressBarInc(); -int PS4_SYSV_ABI sceSaveDataDialogProgressBarSetValue(); -int PS4_SYSV_ABI sceSaveDataDialogTerminate(); -int PS4_SYSV_ABI sceSaveDataDialogUpdateStatus(); - -void RegisterlibSceSaveDataDialog(Core::Loader::SymbolsResolver* sym); -} // namespace Libraries::SaveDataDialog diff --git a/src/core/libraries/system/systemservice.cpp b/src/core/libraries/system/systemservice.cpp index d99ec7c7c..8002e2bfc 100644 --- a/src/core/libraries/system/systemservice.cpp +++ b/src/core/libraries/system/systemservice.cpp @@ -1717,7 +1717,7 @@ int PS4_SYSV_ABI sceSystemServiceGetAppType() { s32 PS4_SYSV_ABI sceSystemServiceGetDisplaySafeAreaInfo(OrbisSystemServiceDisplaySafeAreaInfo* info) { - LOG_INFO(Lib_SystemService, "called"); + LOG_DEBUG(Lib_SystemService, "called"); if (info == nullptr) { LOG_ERROR(Lib_SystemService, "OrbisSystemServiceDisplaySafeAreaInfo is null"); return ORBIS_SYSTEM_SERVICE_ERROR_PARAMETER; diff --git a/src/core/libraries/videoout/driver.cpp b/src/core/libraries/videoout/driver.cpp index 8fcdd118b..fa7577907 100644 --- a/src/core/libraries/videoout/driver.cpp +++ b/src/core/libraries/videoout/driver.cpp @@ -1,6 +1,7 @@ // SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later +#include #include #include "common/assert.h" @@ -160,9 +161,7 @@ int VideoOutDriver::UnregisterBuffers(VideoOutPort* port, s32 attributeIndex) { return ORBIS_OK; } -std::chrono::microseconds VideoOutDriver::Flip(const Request& req) { - const auto start = std::chrono::high_resolution_clock::now(); - +void VideoOutDriver::Flip(const Request& req) { // Whatever the game is rendering show splash if it is active if (!renderer->ShowSplash(req.frame)) { // Present the frame. @@ -198,9 +197,6 @@ std::chrono::microseconds VideoOutDriver::Flip(const Request& req) { port->buffer_labels[req.index] = 0; port->SignalVoLabel(); } - - const auto end = std::chrono::high_resolution_clock::now(); - return std::chrono::duration_cast(end - start); } void VideoOutDriver::DrawBlankFrame() { @@ -261,8 +257,13 @@ void VideoOutDriver::SubmitFlipInternal(VideoOutPort* port, s32 index, s64 flip_ } void VideoOutDriver::PresentThread(std::stop_token token) { - static constexpr std::chrono::milliseconds VblankPeriod{16}; + static constexpr std::chrono::nanoseconds VblankPeriod{16666667}; + const auto vblank_period = VblankPeriod / Config::vblankDiv(); + Common::SetCurrentThreadName("PresentThread"); + Common::SetCurrentThreadRealtime(vblank_period); + + Common::AccurateTimer timer{vblank_period}; const auto receive_request = [this] -> Request { std::scoped_lock lk{mutex}; @@ -274,23 +275,20 @@ void VideoOutDriver::PresentThread(std::stop_token token) { return {}; }; - auto vblank_period = VblankPeriod / Config::vblankDiv(); auto delay = std::chrono::microseconds{0}; while (!token.stop_requested()) { - // Sleep for most of the vblank duration. - std::this_thread::sleep_for(vblank_period - delay); + timer.Start(); // Check if it's time to take a request. auto& vblank_status = main_port.vblank_status; if (vblank_status.count % (main_port.flip_rate + 1) == 0) { const auto request = receive_request(); if (!request) { - delay = std::chrono::microseconds{0}; if (!main_port.is_open) { DrawBlankFrame(); } } else { - delay = Flip(request); + Flip(request); FRAME_END; } } @@ -311,6 +309,8 @@ void VideoOutDriver::PresentThread(std::stop_token token) { Kernel::SceKernelEvent::Filter::VideoOut, nullptr); } } + + timer.End(); } } diff --git a/src/core/libraries/videoout/driver.h b/src/core/libraries/videoout/driver.h index 141294bfd..2e478b9ee 100644 --- a/src/core/libraries/videoout/driver.h +++ b/src/core/libraries/videoout/driver.h @@ -101,7 +101,7 @@ private: } }; - std::chrono::microseconds Flip(const Request& req); + void Flip(const Request& req); void DrawBlankFrame(); // Used when there is no flip request to keep ImGui up to date void SubmitFlipInternal(VideoOutPort* port, s32 index, s64 flip_arg, bool is_eop = false); void PresentThread(std::stop_token token); diff --git a/src/emulator.cpp b/src/emulator.cpp index cc9cbbd98..4a2e38ff8 100644 --- a/src/emulator.cpp +++ b/src/emulator.cpp @@ -10,6 +10,8 @@ #ifdef ENABLE_QT_GUI #include "common/memory_patcher.h" #endif +#include "common/assert.h" +#include "common/elf_info.h" #include "common/ntapi.h" #include "common/path_util.h" #include "common/polyfill_thread.h" @@ -42,9 +44,10 @@ Emulator::Emulator() { const auto config_dir = Common::FS::GetUserPath(Common::FS::PathType::UserDir); Config::load(config_dir / "config.toml"); - // Initialize NT API functions + // Initialize NT API functions and set high priority #ifdef _WIN32 Common::NtApi::Initialize(); + SetPriorityClass(GetCurrentProcess(), ABOVE_NORMAL_PRIORITY_CLASS); #endif // Start logger. @@ -89,17 +92,24 @@ void Emulator::Run(const std::filesystem::path& file) { // Certain games may use /hostapp as well such as CUSA001100 mnt->Mount(file.parent_path(), "/hostapp"); + auto& game_info = Common::ElfInfo::Instance(); + // Loading param.sfo file if exists std::string id; std::string title; std::string app_version; + u32 fw_version; + std::filesystem::path sce_sys_folder = file.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") { auto* param_sfo = Common::Singleton::Instance(); - param_sfo->open(sce_sys_folder.string() + "/param.sfo", {}); - id = std::string(param_sfo->GetString("CONTENT_ID"), 7, 9); + const bool success = param_sfo->Open(sce_sys_folder / "param.sfo"); + ASSERT_MSG(success, "Failed to open param.sfo"); + const auto content_id = param_sfo->GetString("CONTENT_ID"); + ASSERT_MSG(content_id.has_value(), "Failed to get CONTENT_ID"); + id = std::string(*content_id, 7, 9); Libraries::NpTrophy::game_serial = id; const auto trophyDir = Common::FS::GetUserPath(Common::FS::PathType::MetaDataDir) / id / "TrophyFiles"; @@ -112,10 +122,10 @@ void Emulator::Run(const std::filesystem::path& file) { #ifdef ENABLE_QT_GUI MemoryPatcher::g_game_serial = id; #endif - title = param_sfo->GetString("TITLE"); + title = param_sfo->GetString("TITLE").value_or("Unknown title"); LOG_INFO(Loader, "Game id: {} Title: {}", id, title); - u32 fw_version = param_sfo->GetInteger("SYSTEM_VER"); - app_version = param_sfo->GetString("APP_VER"); + fw_version = param_sfo->GetInteger("SYSTEM_VER").value_or(0x4700000); + app_version = param_sfo->GetString("APP_VER").value_or("Unknown version"); LOG_INFO(Loader, "Fw: {:#x} App Version: {}", fw_version, app_version); } else if (entry.path().filename() == "playgo-chunk.dat") { auto* playgo = Common::Singleton::Instance(); @@ -136,13 +146,20 @@ void Emulator::Run(const std::filesystem::path& file) { } } + game_info.initialized = true; + game_info.game_serial = id; + game_info.title = title; + game_info.app_ver = app_version; + game_info.firmware_ver = fw_version & 0xFFF00000; + game_info.raw_firmware_ver = fw_version; + std::string game_title = fmt::format("{} - {} <{}>", id, title, app_version); std::string window_title = ""; if (Common::isRelease) { window_title = fmt::format("shadPS4 v{} | {}", Common::VERSION, game_title); } else { - window_title = - fmt::format("shadPS4 v{} {} | {}", Common::VERSION, Common::g_scm_desc, game_title); + window_title = fmt::format("shadPS4 v{} {} {} | {}", Common::VERSION, Common::g_scm_branch, + Common::g_scm_desc, game_title); } window = std::make_unique( Config::getScreenWidth(), Config::getScreenHeight(), controller, window_title); diff --git a/src/imgui/imgui_config.h b/src/imgui/imgui_config.h index 4602382ed..2094d56bc 100644 --- a/src/imgui/imgui_config.h +++ b/src/imgui/imgui_config.h @@ -26,4 +26,7 @@ extern void assert_fail_debug_msg(const char* msg); #define IMGUI_DEFINE_MATH_OPERATORS #define IM_VEC2_CLASS_EXTRA \ - constexpr ImVec2(float _v) : x(_v), y(_v) {} \ No newline at end of file + 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 diff --git a/src/imgui/imgui_layer.h b/src/imgui/imgui_layer.h index a2ec7fd24..a6c7e2a4a 100644 --- a/src/imgui/imgui_layer.h +++ b/src/imgui/imgui_layer.h @@ -12,10 +12,6 @@ public: static void RemoveLayer(Layer* layer); virtual void Draw() = 0; - - virtual bool ShouldGrabGamepad() { - return false; - } }; } // namespace ImGui \ No newline at end of file diff --git a/src/imgui/imgui_std.h b/src/imgui/imgui_std.h index 6d97cc11b..ec1e2f79d 100644 --- a/src/imgui/imgui_std.h +++ b/src/imgui/imgui_std.h @@ -3,12 +3,25 @@ #pragma once +#include #include #include "imgui_internal.h" +#define IM_COL32_GRAY(x) IM_COL32(x, x, x, 0xFF) + namespace ImGui { +namespace Easing { + +inline float FastInFastOutCubic(float x) { + constexpr float c4 = 1.587401f; // 4^(1/3) + constexpr float c05 = 0.7937f; // 0.5^(1/3) + return std::pow(c4 * x - c05, 3.0f) + 0.5f; +} + +} // namespace Easing + inline void CentralizeWindow() { const auto display_size = GetIO().DisplaySize; SetNextWindowPos(display_size / 2.0f, ImGuiCond_Always, {0.5f}); @@ -18,10 +31,39 @@ inline void KeepNavHighlight() { GetCurrentContext()->NavDisableHighlight = false; } -inline void SetItemCurrentNavFocus() { +inline void SetItemCurrentNavFocus(const ImGuiID id = -1) { const auto ctx = GetCurrentContext(); - SetFocusID(ctx->LastItemData.ID, ctx->CurrentWindow); + SetFocusID(id == -1 ? ctx->LastItemData.ID : id, ctx->CurrentWindow); ctx->NavInitResult.Clear(); + ctx->NavDisableHighlight = false; +} + +inline void DrawPrettyBackground() { + const double time = GetTime() / 1.5f; + const float x = ((float)std::cos(time) + 1.0f) / 2.0f; + const float d = Easing::FastInFastOutCubic(x); + u8 top_left = ImLerp(0x13, 0x05, d); + u8 top_right = ImLerp(0x00, 0x07, d); + u8 bottom_right = ImLerp(0x03, 0x27, d); + u8 bottom_left = ImLerp(0x05, 0x00, d); + + auto& window = *GetCurrentWindowRead(); + auto inner_pos = window.DC.CursorPos - window.WindowPadding; + auto inner_size = GetContentRegionAvail() + window.WindowPadding * 2.0f; + GetWindowDrawList()->AddRectFilledMultiColor( + inner_pos, inner_pos + inner_size, IM_COL32_GRAY(top_left), IM_COL32_GRAY(top_right), + IM_COL32_GRAY(bottom_right), IM_COL32_GRAY(bottom_left)); +} + +static void DrawCenteredText(const char* text, const char* text_end = nullptr, + ImVec2 content = GetContentRegionAvail()) { + auto pos = GetCursorPos(); + const auto text_size = CalcTextSize(text, text_end, false, content.x - 40.0f); + PushTextWrapPos(content.x); + SetCursorPos(pos + (content - text_size) / 2.0f); + TextEx(text, text_end, ImGuiTextFlags_NoWidthForLargeClippedText); + PopTextWrapPos(); + SetCursorPos(pos + content); } } // namespace ImGui diff --git a/src/imgui/imgui_texture.h b/src/imgui/imgui_texture.h new file mode 100644 index 000000000..1a38066d0 --- /dev/null +++ b/src/imgui/imgui_texture.h @@ -0,0 +1,45 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include + +namespace ImGui { + +namespace Core::TextureManager { +struct Inner; +} // namespace Core::TextureManager + +class RefCountedTexture { + Core::TextureManager::Inner* inner; + + explicit RefCountedTexture(Core::TextureManager::Inner* inner); + +public: + struct Image { + ImTextureID im_id; + u32 width; + u32 height; + }; + + static RefCountedTexture DecodePngTexture(std::vector data); + + static RefCountedTexture DecodePngFile(std::filesystem::path path); + + RefCountedTexture(); + + RefCountedTexture(const RefCountedTexture& other); + RefCountedTexture(RefCountedTexture&& other) noexcept; + RefCountedTexture& operator=(const RefCountedTexture& other); + RefCountedTexture& operator=(RefCountedTexture&& other) noexcept; + + virtual ~RefCountedTexture(); + + [[nodiscard]] Image GetTexture() const; + + explicit(false) operator bool() const; +}; + +}; // namespace ImGui \ No newline at end of file diff --git a/src/imgui/layer/video_info.cpp b/src/imgui/layer/video_info.cpp index 2a60926fa..55cfaf895 100644 --- a/src/imgui/layer/video_info.cpp +++ b/src/imgui/layer/video_info.cpp @@ -2,16 +2,121 @@ // SPDX-License-Identifier: GPL-2.0-or-later #include + +#include "common/config.h" +#include "common/types.h" +#include "imgui_internal.h" #include "video_info.h" -void ImGui::Layers::VideoInfo::Draw() { - const ImGuiIO& io = GetIO(); +using namespace ImGui; - m_show = IsKeyPressed(ImGuiKey_F10, false) ^ m_show; +struct FrameInfo { + u32 num; + float delta; +}; - if (m_show) { - if (Begin("Video Info")) { - Text("Frame time: %.3f ms (%.1f FPS)", 1000.0f / io.Framerate, io.Framerate); +static bool show = false; +static bool show_advanced = false; + +static u32 current_frame = 0; +constexpr float TARGET_FPS = 60.0f; +constexpr u32 FRAME_BUFFER_SIZE = 1024; +constexpr float BAR_WIDTH_MULT = 1.4f; +constexpr float BAR_HEIGHT_MULT = 1.25f; +constexpr float FRAME_GRAPH_PADDING_Y = 3.0f; +static std::array frame_list; +static float frame_graph_height = 50.0f; + +static void DrawSimple() { + const auto io = GetIO(); + Text("Frame time: %.3f ms (%.1f FPS)", 1000.0f / io.Framerate, io.Framerate); +} + +static void DrawAdvanced() { + const auto& ctx = *GetCurrentContext(); + const auto& io = ctx.IO; + const auto& window = *ctx.CurrentWindow; + auto& draw_list = *window.DrawList; + + Text("Frame time: %.3f ms (%.1f FPS)", io.DeltaTime * 1000.0f, io.Framerate); + + SeparatorText("Frame graph"); + const float full_width = GetContentRegionAvail().x; + { // Frame graph - inspired by + // https://asawicki.info/news_1758_an_idea_for_visualization_of_frame_times + auto pos = GetCursorScreenPos(); + const ImVec2 size{full_width, frame_graph_height + FRAME_GRAPH_PADDING_Y * 2.0f}; + ItemSize(size); + if (!ItemAdd({pos, pos + size}, GetID("FrameGraph"))) { + return; + } + + float target_dt = 1.0f / (TARGET_FPS * (float)Config::vblankDiv()); + float cur_pos_x = pos.x + full_width; + pos.y += FRAME_GRAPH_PADDING_Y; + const float final_pos_y = pos.y + frame_graph_height; + + draw_list.AddRectFilled({pos.x, pos.y - FRAME_GRAPH_PADDING_Y}, + {pos.x + full_width, final_pos_y + FRAME_GRAPH_PADDING_Y}, + IM_COL32(0x33, 0x33, 0x33, 0xFF)); + draw_list.PushClipRect({pos.x, pos.y}, {pos.x + full_width, final_pos_y}, true); + for (u32 i = 0; i < FRAME_BUFFER_SIZE; ++i) { + const auto& frame_info = frame_list[(current_frame - i) % FRAME_BUFFER_SIZE]; + const float dt_factor = target_dt / frame_info.delta; + + const float width = std::ceil(BAR_WIDTH_MULT / dt_factor); + const float height = + std::min(std::log2(BAR_HEIGHT_MULT / dt_factor) / 3.0f, 1.0f) * frame_graph_height; + + ImU32 color; + if (dt_factor >= 0.95f) { // BLUE + color = IM_COL32(0x33, 0x33, 0xFF, 0xFF); + } else if (dt_factor >= 0.5f) { // GREEN <> YELLOW + float t = 1.0f - (dt_factor - 0.5f) * 2.0f; + int r = (int)(0xFF * t); + color = IM_COL32(r, 0xFF, 0, 0xFF); + } else { // YELLOW <> RED + float t = dt_factor * 2.0f; + int g = (int)(0xFF * t); + color = IM_COL32(0xFF, g, 0, 0xFF); + } + draw_list.AddRectFilled({cur_pos_x - width, final_pos_y - height}, + {cur_pos_x, final_pos_y}, color); + cur_pos_x -= width; + if (cur_pos_x < width) { + break; + } + } + draw_list.PopClipRect(); + } +} + +void Layers::VideoInfo::Draw() { + const auto io = GetIO(); + + const FrameInfo frame_info{ + .num = ++current_frame, + .delta = io.DeltaTime, + }; + frame_list[current_frame % FRAME_BUFFER_SIZE] = frame_info; + + if (IsKeyPressed(ImGuiKey_F10, false)) { + const bool changed_ctrl = io.KeyCtrl != show_advanced; + show_advanced = io.KeyCtrl; + show = changed_ctrl || !show; + } + + if (show) { + if (show_advanced) { + if (Begin("Video debug info", &show, 0)) { + DrawAdvanced(); + } + } else { + if (Begin("Video Info", nullptr, + ImGuiWindowFlags_NoNav | ImGuiWindowFlags_NoDecoration | + ImGuiWindowFlags_AlwaysAutoResize)) { + DrawSimple(); + } } End(); } diff --git a/src/imgui/layer/video_info.h b/src/imgui/layer/video_info.h index 8eec972a8..8a8af554e 100644 --- a/src/imgui/layer/video_info.h +++ b/src/imgui/layer/video_info.h @@ -11,7 +11,6 @@ class RendererVulkan; namespace ImGui::Layers { class VideoInfo : public Layer { - bool m_show = false; ::Vulkan::RendererVulkan* renderer{}; public: diff --git a/src/imgui/renderer/CMakeLists.txt b/src/imgui/renderer/CMakeLists.txt new file mode 100644 index 000000000..b5f51ef62 --- /dev/null +++ b/src/imgui/renderer/CMakeLists.txt @@ -0,0 +1,32 @@ +# SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +# SPDX-License-Identifier: GPL-2.0-or-later + +project(ImGui_Resources) + +add_executable(Dear_ImGui_FontEmbed ${CMAKE_SOURCE_DIR}/externals/dear_imgui/misc/fonts/binary_to_compressed_c.cpp) + +set(FONT_LIST + NotoSansJP-Regular.ttf +) + +set(OutputList "") +FOREACH (FONT_FILE ${FONT_LIST}) + string(REGEX REPLACE "-" "_" fontname ${FONT_FILE}) + string(TOLOWER ${fontname} fontname) + string(REGEX REPLACE ".ttf" "" fontname_cpp ${fontname}) + set(fontname_cpp "imgui_font_${fontname_cpp}") + + MESSAGE(STATUS "Embedding font ${FONT_FILE}") + set(OUTPUT "generated_fonts/imgui_fonts/${fontname}") + add_custom_command( + OUTPUT "${OUTPUT}.g.cpp" + COMMAND ${CMAKE_COMMAND} -E make_directory "generated_fonts/imgui_fonts" + COMMAND $ -nostatic "${CMAKE_CURRENT_SOURCE_DIR}/fonts/${FONT_FILE}" ${fontname_cpp} > "${OUTPUT}.g.cpp" + DEPENDS Dear_ImGui_FontEmbed "fonts/${FONT_FILE}" + USES_TERMINAL + ) + list(APPEND OutputList "${OUTPUT}.g.cpp") +ENDFOREACH () + +add_library(ImGui_Resources STATIC ${OutputList}) +set(IMGUI_RESOURCES_INCLUDE ${CMAKE_CURRENT_BINARY_DIR}/generated_fonts PARENT_SCOPE) diff --git a/src/imgui/renderer/fonts/NotoSansJP-Regular.ttf b/src/imgui/renderer/fonts/NotoSansJP-Regular.ttf new file mode 100644 index 000000000..b2dad730d Binary files /dev/null and b/src/imgui/renderer/fonts/NotoSansJP-Regular.ttf differ diff --git a/src/imgui/renderer/imgui_core.cpp b/src/imgui/renderer/imgui_core.cpp index 1c6313972..b972d99d0 100644 --- a/src/imgui/renderer/imgui_core.cpp +++ b/src/imgui/renderer/imgui_core.cpp @@ -3,15 +3,20 @@ #include #include + #include "common/config.h" #include "common/path_util.h" #include "imgui/imgui_layer.h" #include "imgui_core.h" #include "imgui_impl_sdl3.h" #include "imgui_impl_vulkan.h" +#include "imgui_internal.h" #include "sdl_window.h" +#include "texture_manager.h" #include "video_core/renderer_vulkan/renderer_vulkan.h" +#include "imgui_fonts/notosansjp_regular.ttf.g.cpp" + static void CheckVkResult(const vk::Result err) { LOG_ERROR(ImGui, "Vulkan error {}", vk::to_string(err)); } @@ -48,6 +53,22 @@ void Initialize(const ::Vulkan::Instance& instance, const Frontend::WindowSDL& w io.DisplaySize = ImVec2((float)window.getWidth(), (float)window.getHeight()); io.IniFilename = SDL_strdup(config_path.string().c_str()); io.LogFilename = SDL_strdup(log_path.string().c_str()); + + ImFontGlyphRangesBuilder rb{}; + rb.AddRanges(io.Fonts->GetGlyphRangesDefault()); + rb.AddRanges(io.Fonts->GetGlyphRangesGreek()); + rb.AddRanges(io.Fonts->GetGlyphRangesKorean()); + rb.AddRanges(io.Fonts->GetGlyphRangesJapanese()); + rb.AddRanges(io.Fonts->GetGlyphRangesCyrillic()); + ImVector ranges{}; + rb.BuildRanges(&ranges); + ImFontConfig font_cfg{}; + font_cfg.OversampleH = 2; + font_cfg.OversampleV = 1; + io.Fonts->AddFontFromMemoryCompressedTTF(imgui_font_notosansjp_regular_compressed_data, + imgui_font_notosansjp_regular_compressed_size, 16.0f, + &font_cfg, ranges.Data); + StyleColorsDark(); Sdl::Init(window.GetSdlWindow()); @@ -68,6 +89,8 @@ void Initialize(const ::Vulkan::Instance& instance, const Frontend::WindowSDL& w .check_vk_result_fn = &CheckVkResult, }; Vulkan::Init(vk_info); + + TextureManager::StartWorker(); } void OnResize() { @@ -77,6 +100,8 @@ void OnResize() { void Shutdown(const vk::Device& device) { device.waitIdle(); + TextureManager::StopWorker(); + const ImGuiIO& io = GetIO(); const auto ini_filename = (void*)io.IniFilename; const auto log_filename = (void*)io.LogFilename; @@ -92,24 +117,19 @@ void Shutdown(const vk::Device& device) { bool ProcessEvent(SDL_Event* event) { Sdl::ProcessEvent(event); switch (event->type) { + // Don't block release/up events case SDL_EVENT_MOUSE_MOTION: case SDL_EVENT_MOUSE_WHEEL: case SDL_EVENT_MOUSE_BUTTON_DOWN: - case SDL_EVENT_MOUSE_BUTTON_UP: return GetIO().WantCaptureMouse; case SDL_EVENT_TEXT_INPUT: case SDL_EVENT_KEY_DOWN: - case SDL_EVENT_KEY_UP: return GetIO().WantCaptureKeyboard; case SDL_EVENT_GAMEPAD_BUTTON_DOWN: - case SDL_EVENT_GAMEPAD_BUTTON_UP: case SDL_EVENT_GAMEPAD_AXIS_MOTION: - case SDL_EVENT_GAMEPAD_ADDED: - case SDL_EVENT_GAMEPAD_REMOVED: case SDL_EVENT_GAMEPAD_TOUCHPAD_DOWN: - case SDL_EVENT_GAMEPAD_TOUCHPAD_UP: case SDL_EVENT_GAMEPAD_TOUCHPAD_MOTION: - return (GetIO().BackendFlags & ImGuiBackendFlags_HasGamepad) != 0; + return GetIO().NavActive; default: return false; } @@ -130,21 +150,11 @@ void NewFrame() { } } - Vulkan::NewFrame(); Sdl::NewFrame(); ImGui::NewFrame(); - bool capture_gamepad = false; for (auto* layer : layers) { layer->Draw(); - if (layer->ShouldGrabGamepad()) { - capture_gamepad = true; - } - } - if (capture_gamepad) { - GetIO().BackendFlags |= ImGuiBackendFlags_HasGamepad; - } else { - GetIO().BackendFlags &= ~ImGuiBackendFlags_HasGamepad; } } diff --git a/src/imgui/renderer/imgui_impl_sdl3.cpp b/src/imgui/renderer/imgui_impl_sdl3.cpp index 2a7d801e4..bb194bff7 100644 --- a/src/imgui/renderer/imgui_impl_sdl3.cpp +++ b/src/imgui/renderer/imgui_impl_sdl3.cpp @@ -703,8 +703,8 @@ static void UpdateGamepads() { const int thumb_dead_zone = 8000; // SDL_gamepad.h suggests using this value. UpdateGamepadButton(bd, io, ImGuiKey_GamepadStart, SDL_GAMEPAD_BUTTON_START); UpdateGamepadButton(bd, io, ImGuiKey_GamepadBack, SDL_GAMEPAD_BUTTON_BACK); - UpdateGamepadButton(bd, io, ImGuiKey_GamepadFaceLeft, - SDL_GAMEPAD_BUTTON_WEST); // Xbox X, PS Square + /*UpdateGamepadButton(bd, io, ImGuiKey_GamepadFaceLeft, + SDL_GAMEPAD_BUTTON_WEST); // Xbox X, PS Square*/ // Disable to avoid menu toggle UpdateGamepadButton(bd, io, ImGuiKey_GamepadFaceRight, SDL_GAMEPAD_BUTTON_EAST); // Xbox B, PS Circle UpdateGamepadButton(bd, io, ImGuiKey_GamepadFaceUp, diff --git a/src/imgui/renderer/imgui_impl_vulkan.cpp b/src/imgui/renderer/imgui_impl_vulkan.cpp index 2c1c135f7..cf8c5ea4e 100644 --- a/src/imgui/renderer/imgui_impl_vulkan.cpp +++ b/src/imgui/renderer/imgui_impl_vulkan.cpp @@ -4,6 +4,8 @@ // Based on imgui_impl_vulkan.cpp from Dear ImGui repository #include +#include + #include #include "imgui_impl_vulkan.h" @@ -47,13 +49,15 @@ struct VkData { vk::ShaderModule shader_module_vert{}; vk::ShaderModule shader_module_frag{}; + std::mutex command_pool_mutex; + vk::CommandPool command_pool{}; + vk::Sampler simple_sampler{}; + // Font data - vk::Sampler font_sampler{}; vk::DeviceMemory font_memory{}; vk::Image font_image{}; vk::ImageView font_view{}; vk::DescriptorSet font_descriptor_set{}; - vk::CommandPool font_command_pool{}; vk::CommandBuffer font_command_buffer{}; // Render buffers @@ -222,12 +226,53 @@ static inline vk::DeviceSize AlignBufferSize(vk::DeviceSize size, vk::DeviceSize return (size + alignment - 1) & ~(alignment - 1); } -// Register a texture -vk::DescriptorSet AddTexture(vk::Sampler sampler, vk::ImageView image_view, - vk::ImageLayout image_layout) { +void UploadTextureData::Upload() { VkData* bd = GetBackendData(); const InitInfo& v = bd->init_info; + vk::SubmitInfo submit_info{ + .commandBufferCount = 1, + .pCommandBuffers = &command_buffer, + }; + CheckVkErr(v.queue.submit({submit_info})); + CheckVkErr(v.queue.waitIdle()); + + v.device.destroyBuffer(upload_buffer, v.allocator); + v.device.freeMemory(upload_buffer_memory, v.allocator); + { + std::unique_lock lk(bd->command_pool_mutex); + v.device.freeCommandBuffers(bd->command_pool, {command_buffer}); + } + upload_buffer = VK_NULL_HANDLE; + upload_buffer_memory = VK_NULL_HANDLE; +} + +void UploadTextureData::Destroy() { + VkData* bd = GetBackendData(); + const InitInfo& v = bd->init_info; + + CheckVkErr(v.device.waitIdle()); + RemoveTexture(descriptor_set); + descriptor_set = VK_NULL_HANDLE; + + v.device.destroyImageView(image_view, v.allocator); + image_view = VK_NULL_HANDLE; + v.device.destroyImage(image, v.allocator); + image = VK_NULL_HANDLE; + v.device.freeMemory(image_memory, v.allocator); + image_memory = VK_NULL_HANDLE; +} + +// Register a texture +vk::DescriptorSet AddTexture(vk::ImageView image_view, vk::ImageLayout image_layout, + vk::Sampler sampler) { + VkData* bd = GetBackendData(); + const InitInfo& v = bd->init_info; + + if (sampler == VK_NULL_HANDLE) { + sampler = bd->simple_sampler; + } + // Create Descriptor Set: vk::DescriptorSet descriptor_set; { @@ -262,6 +307,166 @@ vk::DescriptorSet AddTexture(vk::Sampler sampler, vk::ImageView image_view, } return descriptor_set; } +UploadTextureData UploadTexture(const void* data, vk::Format format, u32 width, u32 height, + size_t size) { + ImGuiIO& io = GetIO(); + VkData* bd = GetBackendData(); + const InitInfo& v = bd->init_info; + + UploadTextureData info{}; + { + std::unique_lock lk(bd->command_pool_mutex); + info.command_buffer = + CheckVkResult(v.device.allocateCommandBuffers(vk::CommandBufferAllocateInfo{ + .commandPool = bd->command_pool, + .commandBufferCount = 1, + })) + .front(); + CheckVkErr(info.command_buffer.begin(vk::CommandBufferBeginInfo{ + .flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit, + })); + } + + // Create Image + { + vk::ImageCreateInfo image_info{ + .imageType = vk::ImageType::e2D, + .format = format, + .extent{ + .width = width, + .height = height, + .depth = 1, + }, + .mipLevels = 1, + .arrayLayers = 1, + .samples = vk::SampleCountFlagBits::e1, + .tiling = vk::ImageTiling::eOptimal, + .usage = vk::ImageUsageFlagBits::eTransferDst | vk::ImageUsageFlagBits::eSampled, + .sharingMode = vk::SharingMode::eExclusive, + .initialLayout = vk::ImageLayout::eUndefined, + }; + info.image = CheckVkResult(v.device.createImage(image_info, v.allocator)); + auto req = v.device.getImageMemoryRequirements(info.image); + vk::MemoryAllocateInfo alloc_info{ + .allocationSize = IM_MAX(v.min_allocation_size, req.size), + .memoryTypeIndex = + FindMemoryType(vk::MemoryPropertyFlagBits::eDeviceLocal, req.memoryTypeBits), + }; + info.image_memory = CheckVkResult(v.device.allocateMemory(alloc_info, v.allocator)); + CheckVkErr(v.device.bindImageMemory(info.image, info.image_memory, 0)); + } + + // Create Image View + { + vk::ImageViewCreateInfo view_info{ + .image = info.image, + .viewType = vk::ImageViewType::e2D, + .format = format, + .subresourceRange{ + .aspectMask = vk::ImageAspectFlagBits::eColor, + .levelCount = 1, + .layerCount = 1, + }, + }; + info.image_view = CheckVkResult(v.device.createImageView(view_info, v.allocator)); + } + + // Create descriptor set (ImTextureID) + info.descriptor_set = AddTexture(info.image_view, vk::ImageLayout::eShaderReadOnlyOptimal); + + // Create Upload Buffer + { + vk::BufferCreateInfo buffer_info{ + .size = size, + .usage = vk::BufferUsageFlagBits::eTransferSrc, + .sharingMode = vk::SharingMode::eExclusive, + }; + info.upload_buffer = CheckVkResult(v.device.createBuffer(buffer_info, v.allocator)); + auto req = v.device.getBufferMemoryRequirements(info.upload_buffer); + auto alignemtn = IM_MAX(bd->buffer_memory_alignment, req.alignment); + vk::MemoryAllocateInfo alloc_info{ + .allocationSize = IM_MAX(v.min_allocation_size, req.size), + .memoryTypeIndex = + FindMemoryType(vk::MemoryPropertyFlagBits::eHostVisible, req.memoryTypeBits), + }; + info.upload_buffer_memory = CheckVkResult(v.device.allocateMemory(alloc_info, v.allocator)); + CheckVkErr(v.device.bindBufferMemory(info.upload_buffer, info.upload_buffer_memory, 0)); + } + + // Upload to Buffer + { + char* map = (char*)CheckVkResult(v.device.mapMemory(info.upload_buffer_memory, 0, size)); + memcpy(map, data, size); + vk::MappedMemoryRange range[1]{ + { + .memory = info.upload_buffer_memory, + .size = size, + }, + }; + CheckVkErr(v.device.flushMappedMemoryRanges(range)); + v.device.unmapMemory(info.upload_buffer_memory); + } + + // Copy to Image + { + vk::ImageMemoryBarrier copy_barrier[1]{ + { + .sType = vk::StructureType::eImageMemoryBarrier, + .dstAccessMask = vk::AccessFlagBits::eTransferWrite, + .oldLayout = vk::ImageLayout::eUndefined, + .newLayout = vk::ImageLayout::eTransferDstOptimal, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = info.image, + .subresourceRange{ + .aspectMask = vk::ImageAspectFlagBits::eColor, + .levelCount = 1, + .layerCount = 1, + }, + }, + }; + info.command_buffer.pipelineBarrier(vk::PipelineStageFlagBits::eHost, + vk::PipelineStageFlagBits::eTransfer, {}, {}, {}, + {copy_barrier}); + + vk::BufferImageCopy region{ + .imageSubresource{ + .aspectMask = vk::ImageAspectFlagBits::eColor, + .layerCount = 1, + }, + .imageExtent{ + .width = width, + .height = height, + .depth = 1, + }, + }; + info.command_buffer.copyBufferToImage(info.upload_buffer, info.image, + vk::ImageLayout::eTransferDstOptimal, {region}); + + vk::ImageMemoryBarrier use_barrier[1]{{ + .sType = vk::StructureType::eImageMemoryBarrier, + .srcAccessMask = vk::AccessFlagBits::eTransferWrite, + .dstAccessMask = vk::AccessFlagBits::eShaderRead, + .oldLayout = vk::ImageLayout::eTransferDstOptimal, + .newLayout = vk::ImageLayout::eShaderReadOnlyOptimal, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = info.image, + .subresourceRange{ + .aspectMask = vk::ImageAspectFlagBits::eColor, + .levelCount = 1, + .layerCount = 1, + }, + }}; + info.command_buffer.pipelineBarrier(vk::PipelineStageFlagBits::eTransfer, + vk::PipelineStageFlagBits::eFragmentShader, {}, {}, {}, + {use_barrier}); + } + + CheckVkErr(info.command_buffer.end()); + + return info; +} void RemoveTexture(vk::DescriptorSet descriptor_set) { VkData* bd = GetBackendData(); @@ -517,27 +722,20 @@ static bool CreateFontsTexture() { DestroyFontsTexture(); } - // Create command pool/buffer - if (bd->font_command_pool == VK_NULL_HANDLE) { - vk::CommandPoolCreateInfo info{ - .sType = vk::StructureType::eCommandPoolCreateInfo, - .flags = vk::CommandPoolCreateFlags{}, - .queueFamilyIndex = v.queue_family, - }; - bd->font_command_pool = CheckVkResult(v.device.createCommandPool(info, v.allocator)); - } + // Create command buffer if (bd->font_command_buffer == VK_NULL_HANDLE) { vk::CommandBufferAllocateInfo info{ .sType = vk::StructureType::eCommandBufferAllocateInfo, - .commandPool = bd->font_command_pool, + .commandPool = bd->command_pool, .commandBufferCount = 1, }; + std::unique_lock lk(bd->command_pool_mutex); bd->font_command_buffer = CheckVkResult(v.device.allocateCommandBuffers(info)).front(); } // Start command buffer { - CheckVkErr(v.device.resetCommandPool(bd->font_command_pool, vk::CommandPoolResetFlags{})); + CheckVkErr(bd->font_command_buffer.reset()); vk::CommandBufferBeginInfo begin_info{}; begin_info.sType = vk::StructureType::eCommandBufferBeginInfo; begin_info.flags |= vk::CommandBufferUsageFlagBits::eOneTimeSubmit; @@ -597,8 +795,7 @@ static bool CreateFontsTexture() { } // Create the Descriptor Set: - bd->font_descriptor_set = - AddTexture(bd->font_sampler, bd->font_view, vk::ImageLayout::eShaderReadOnlyOptimal); + bd->font_descriptor_set = AddTexture(bd->font_view, vk::ImageLayout::eShaderReadOnlyOptimal); // Create the Upload Buffer: vk::DeviceMemory upload_buffer_memory{}; @@ -956,25 +1153,6 @@ bool CreateDeviceObjects() { bd->descriptor_pool = CheckVkResult(v.device.createDescriptorPool(pool_info)); } - if (!bd->font_sampler) { - // Bilinear sampling is required by default. Set 'io.Fonts->Flags |= - // ImFontAtlasFlags_NoBakedLines' or 'style.AntiAliasedLinesUseTex = false' to allow - // point/nearest sampling. - vk::SamplerCreateInfo info{ - .sType = vk::StructureType::eSamplerCreateInfo, - .magFilter = vk::Filter::eLinear, - .minFilter = vk::Filter::eLinear, - .mipmapMode = vk::SamplerMipmapMode::eLinear, - .addressModeU = vk::SamplerAddressMode::eRepeat, - .addressModeV = vk::SamplerAddressMode::eRepeat, - .addressModeW = vk::SamplerAddressMode::eRepeat, - .maxAnisotropy = 1.0f, - .minLod = -1000, - .maxLod = 1000, - }; - bd->font_sampler = CheckVkResult(v.device.createSampler(info, v.allocator)); - } - if (!bd->descriptor_set_layout) { vk::DescriptorSetLayoutBinding binding[1]{ { @@ -1016,6 +1194,35 @@ bool CreateDeviceObjects() { CreatePipeline(v.device, v.allocator, v.pipeline_cache, nullptr, &bd->pipeline, v.subpass); + if (bd->command_pool == VK_NULL_HANDLE) { + vk::CommandPoolCreateInfo info{ + .sType = vk::StructureType::eCommandPoolCreateInfo, + .flags = vk::CommandPoolCreateFlagBits::eResetCommandBuffer, + .queueFamilyIndex = v.queue_family, + }; + std::unique_lock lk(bd->command_pool_mutex); + bd->command_pool = CheckVkResult(v.device.createCommandPool(info, v.allocator)); + } + + if (!bd->simple_sampler) { + // Bilinear sampling is required by default. Set 'io.Fonts->Flags |= + // ImFontAtlasFlags_NoBakedLines' or 'style.AntiAliasedLinesUseTex = false' to allow + // point/nearest sampling. + vk::SamplerCreateInfo info{ + .sType = vk::StructureType::eSamplerCreateInfo, + .magFilter = vk::Filter::eLinear, + .minFilter = vk::Filter::eLinear, + .mipmapMode = vk::SamplerMipmapMode::eLinear, + .addressModeU = vk::SamplerAddressMode::eRepeat, + .addressModeV = vk::SamplerAddressMode::eRepeat, + .addressModeW = vk::SamplerAddressMode::eRepeat, + .maxAnisotropy = 1.0f, + .minLod = -1000, + .maxLod = 1000, + }; + bd->simple_sampler = CheckVkResult(v.device.createSampler(info, v.allocator)); + } + return true; } @@ -1026,12 +1233,14 @@ void ImGuiImplVulkanDestroyDeviceObjects() { DestroyFontsTexture(); if (bd->font_command_buffer) { - v.device.freeCommandBuffers(bd->font_command_pool, {bd->font_command_buffer}); + std::unique_lock lk(bd->command_pool_mutex); + v.device.freeCommandBuffers(bd->command_pool, {bd->font_command_buffer}); bd->font_command_buffer = VK_NULL_HANDLE; } - if (bd->font_command_pool) { - v.device.destroyCommandPool(bd->font_command_pool, v.allocator); - bd->font_command_pool = VK_NULL_HANDLE; + if (bd->command_pool) { + std::unique_lock lk(bd->command_pool_mutex); + v.device.destroyCommandPool(bd->command_pool, v.allocator); + bd->command_pool = VK_NULL_HANDLE; } if (bd->shader_module_vert) { v.device.destroyShaderModule(bd->shader_module_vert, v.allocator); @@ -1041,9 +1250,9 @@ void ImGuiImplVulkanDestroyDeviceObjects() { v.device.destroyShaderModule(bd->shader_module_frag, v.allocator); bd->shader_module_frag = VK_NULL_HANDLE; } - if (bd->font_sampler) { - v.device.destroySampler(bd->font_sampler, v.allocator); - bd->font_sampler = VK_NULL_HANDLE; + if (bd->simple_sampler) { + v.device.destroySampler(bd->simple_sampler, v.allocator); + bd->simple_sampler = VK_NULL_HANDLE; } if (bd->descriptor_set_layout) { v.device.destroyDescriptorSetLayout(bd->descriptor_set_layout, v.allocator); @@ -1095,13 +1304,4 @@ void Shutdown() { IM_DELETE(bd); } -void NewFrame() { - VkData* bd = GetBackendData(); - IM_ASSERT(bd != nullptr && - "Context or backend not initialized! Did you call ImGuiImplVulkanInit()?"); - - if (!bd->font_descriptor_set) - CreateFontsTexture(); -} - } // namespace ImGui::Vulkan diff --git a/src/imgui/renderer/imgui_impl_vulkan.h b/src/imgui/renderer/imgui_impl_vulkan.h index e68b8723f..ca76fda6d 100644 --- a/src/imgui/renderer/imgui_impl_vulkan.h +++ b/src/imgui/renderer/imgui_impl_vulkan.h @@ -6,6 +6,7 @@ #pragma once #define VULKAN_HPP_NO_EXCEPTIONS +#include "common/types.h" #include "video_core/renderer_vulkan/vk_common.h" struct ImDrawData; @@ -29,14 +30,33 @@ struct InitInfo { void (*check_vk_result_fn)(vk::Result err); }; -vk::DescriptorSet AddTexture(vk::Sampler sampler, vk::ImageView image_view, - vk::ImageLayout image_layout); +// Prepare all resources needed for uploading textures +// Caller should clean up the returned data. +struct UploadTextureData { + vk::Image image; + vk::ImageView image_view; + vk::DescriptorSet descriptor_set; + vk::DeviceMemory image_memory; + + vk::CommandBuffer command_buffer; // Submit to the queue + vk::Buffer upload_buffer; + vk::DeviceMemory upload_buffer_memory; + + void Upload(); + + void Destroy(); +}; + +vk::DescriptorSet AddTexture(vk::ImageView image_view, vk::ImageLayout image_layout, + vk::Sampler sampler = VK_NULL_HANDLE); + +UploadTextureData UploadTexture(const void* data, vk::Format format, u32 width, u32 height, + size_t size); void RemoveTexture(vk::DescriptorSet descriptor_set); bool Init(InitInfo info); void Shutdown(); -void NewFrame(); void RenderDrawData(ImDrawData& draw_data, vk::CommandBuffer command_buffer, vk::Pipeline pipeline = VK_NULL_HANDLE); diff --git a/src/imgui/renderer/texture_manager.cpp b/src/imgui/renderer/texture_manager.cpp new file mode 100644 index 000000000..ba4a05d01 --- /dev/null +++ b/src/imgui/renderer/texture_manager.cpp @@ -0,0 +1,243 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include +#include + +#include + +#include "common/assert.h" +#include "common/io_file.h" +#include "common/polyfill_thread.h" +#include "imgui_impl_vulkan.h" +#include "texture_manager.h" + +namespace ImGui { + +namespace Core::TextureManager { +struct Inner { + std::atomic_int count = 0; + ImTextureID texture_id = nullptr; + u32 width = 0; + u32 height = 0; + + Vulkan::UploadTextureData upload_data; + + ~Inner(); +}; +} // namespace Core::TextureManager + +using namespace Core::TextureManager; + +RefCountedTexture::RefCountedTexture(Inner* inner) : inner(inner) { + ++inner->count; +} + +RefCountedTexture RefCountedTexture::DecodePngTexture(std::vector data) { + const auto core = new Inner; + Core::TextureManager::DecodePngTexture(std::move(data), core); + return RefCountedTexture(core); +} + +RefCountedTexture RefCountedTexture::DecodePngFile(std::filesystem::path path) { + const auto core = new Inner; + Core::TextureManager::DecodePngFile(std::move(path), core); + return RefCountedTexture(core); +} + +RefCountedTexture::RefCountedTexture() : inner(nullptr) {} + +RefCountedTexture::RefCountedTexture(const RefCountedTexture& other) : inner(other.inner) { + if (inner != nullptr) { + ++inner->count; + } +} + +RefCountedTexture::RefCountedTexture(RefCountedTexture&& other) noexcept : inner(other.inner) { + other.inner = nullptr; +} + +RefCountedTexture& RefCountedTexture::operator=(const RefCountedTexture& other) { + if (this == &other) + return *this; + inner = other.inner; + if (inner != nullptr) { + ++inner->count; + } + return *this; +} + +RefCountedTexture& RefCountedTexture::operator=(RefCountedTexture&& other) noexcept { + if (this == &other) + return *this; + std::swap(inner, other.inner); + return *this; +} + +RefCountedTexture::~RefCountedTexture() { + if (inner != nullptr) { + if (inner->count.fetch_sub(1) == 1) { + delete inner; + } + } +} +RefCountedTexture::Image RefCountedTexture::GetTexture() const { + if (inner == nullptr) { + return {}; + } + return Image{ + .im_id = inner->texture_id, + .width = inner->width, + .height = inner->height, + }; +} +RefCountedTexture::operator bool() const { + return inner != nullptr && inner->texture_id != nullptr; +} + +struct Job { + Inner* core; + std::vector data; + std::filesystem::path path; +}; + +struct UploadJob { + Inner* core = nullptr; + Vulkan::UploadTextureData data; + int tick = 0; // Used to skip the first frame when destroying to await the current frame to draw +}; + +static bool g_is_worker_running = false; +static std::jthread g_worker_thread; +static std::condition_variable g_worker_cv; + +static std::mutex g_job_list_mtx; +static std::deque g_job_list; + +static std::mutex g_upload_mtx; +static std::deque g_upload_list; + +namespace Core::TextureManager { + +Inner::~Inner() { + if (upload_data.descriptor_set != nullptr) { + std::unique_lock lk{g_upload_mtx}; + g_upload_list.emplace_back(UploadJob{ + .data = this->upload_data, + .tick = 2, + }); + } +} + +void WorkerLoop() { + std::mutex mtx; + while (g_is_worker_running) { + std::unique_lock lk{mtx}; + g_worker_cv.wait(lk); + if (!g_is_worker_running) { + break; + } + while (true) { + g_job_list_mtx.lock(); + if (g_job_list.empty()) { + g_job_list_mtx.unlock(); + break; + } + auto [core, png_raw, path] = std::move(g_job_list.front()); + g_job_list.pop_front(); + g_job_list_mtx.unlock(); + + if (!path.empty()) { // Decode PNG from file + Common::FS::IOFile file(path, Common::FS::FileAccessMode::Read); + if (!file.IsOpen()) { + LOG_ERROR(ImGui, "Failed to open PNG file: {}", path.string()); + continue; + } + png_raw.resize(file.GetSize()); + file.Seek(0); + file.ReadRaw(png_raw.data(), png_raw.size()); + file.Close(); + } + + int width, height; + const stbi_uc* pixels = + stbi_load_from_memory(png_raw.data(), png_raw.size(), &width, &height, nullptr, 4); + + auto texture = Vulkan::UploadTexture(pixels, vk::Format::eR8G8B8A8Unorm, width, height, + width * height * 4 * sizeof(stbi_uc)); + + core->upload_data = texture; + core->width = width; + core->height = height; + + std::unique_lock upload_lk{g_upload_mtx}; + g_upload_list.emplace_back(UploadJob{ + .core = core, + }); + } + } +} + +void StartWorker() { + ASSERT(!g_is_worker_running); + g_worker_thread = std::jthread(WorkerLoop); + g_is_worker_running = true; +} + +void StopWorker() { + ASSERT(g_is_worker_running); + g_is_worker_running = false; + g_worker_cv.notify_one(); +} + +void DecodePngTexture(std::vector data, Inner* core) { + ++core->count; + Job job{ + .core = core, + .data = std::move(data), + }; + std::unique_lock lk{g_job_list_mtx}; + g_job_list.push_back(std::move(job)); + g_worker_cv.notify_one(); +} + +void DecodePngFile(std::filesystem::path path, Inner* core) { + ++core->count; + Job job{ + .core = core, + .path = std::move(path), + }; + std::unique_lock lk{g_job_list_mtx}; + g_job_list.push_back(std::move(job)); + g_worker_cv.notify_one(); +} + +void Submit() { + UploadJob upload; + { + std::unique_lock lk{g_upload_mtx}; + if (g_upload_list.empty()) { + return; + } + // Upload one texture at a time to avoid slow down + upload = g_upload_list.front(); + g_upload_list.pop_front(); + if (upload.tick > 0) { + --upload.tick; + g_upload_list.emplace_back(upload); + return; + } + } + if (upload.core != nullptr) { + upload.core->upload_data.Upload(); + upload.core->texture_id = upload.core->upload_data.descriptor_set; + if (upload.core->count.fetch_sub(1) == 1) { + delete upload.core; + } + } else { + upload.data.Destroy(); + } +} +} // namespace Core::TextureManager + +} // namespace ImGui \ No newline at end of file diff --git a/src/imgui/renderer/texture_manager.h b/src/imgui/renderer/texture_manager.h new file mode 100644 index 000000000..4fa7b9924 --- /dev/null +++ b/src/imgui/renderer/texture_manager.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/types.h" +#include "imgui/imgui_texture.h" + +namespace vk { +class CommandBuffer; +} + +namespace ImGui::Core::TextureManager { + +struct Inner; + +void StartWorker(); + +void StopWorker(); + +void DecodePngTexture(std::vector data, Inner* core); + +void DecodePngFile(std::filesystem::path path, Inner* core); + +void Submit(); + +}; // namespace ImGui::Core::TextureManager \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index cc64d9f72..cea92be07 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -10,6 +10,11 @@ int main(int argc, char* argv[]) { fmt::print("Usage: {} \n", argv[0]); return -1; } + // check if eboot file exists + if (!std::filesystem::exists(argv[1])) { + fmt::print("Eboot.bin file not found\n"); + return -1; + } for (int i = 0; i < argc; i++) { std::string curArg = argv[i]; diff --git a/src/qt_gui/game_info.h b/src/qt_gui/game_info.h index 6032e1c3a..a4bcd20ee 100644 --- a/src/qt_gui/game_info.h +++ b/src/qt_gui/game_info.h @@ -27,20 +27,36 @@ public: game.path = filePath; PSF psf; - if (psf.open(game.path + "/sce_sys/param.sfo", {})) { + if (psf.Open(std::filesystem::path(game.path) / "sce_sys" / "param.sfo")) { game.icon_path = game.path + "/sce_sys/icon0.png"; QString iconpath = QString::fromStdString(game.icon_path); game.icon = QImage(iconpath); game.pic_path = game.path + "/sce_sys/pic1.png"; - game.name = psf.GetString("TITLE"); - game.serial = psf.GetString("TITLE_ID"); - game.region = GameListUtils::GetRegion(psf.GetString("CONTENT_ID").at(0)).toStdString(); - u32 fw_int = psf.GetInteger("SYSTEM_VER"); - QString fw = QString::number(fw_int, 16); - QString fw_ = fw.length() > 7 ? QString::number(fw_int, 16).left(3).insert(2, '.') - : fw.left(3).insert(1, '.'); - game.fw = (fw_int == 0) ? "0.00" : fw_.toStdString(); - game.version = psf.GetString("APP_VER"); + if (const auto title = psf.GetString("TITLE"); title.has_value()) { + game.name = *title; + } + if (const auto title_id = psf.GetString("TITLE_ID"); title_id.has_value()) { + game.serial = *title_id; + } + if (const auto content_id = psf.GetString("CONTENT_ID"); + content_id.has_value() && !content_id->empty()) { + game.region = GameListUtils::GetRegion(content_id->at(0)).toStdString(); + } + if (const auto fw_int_opt = psf.GetInteger("SYSTEM_VER"); fw_int_opt.has_value()) { + auto fw_int = *fw_int_opt; + if (fw_int == 0) { + game.fw = "0.00"; + } else { + QString fw = QString::number(fw_int, 16); + QString fw_ = fw.length() > 7 + ? QString::number(fw_int, 16).left(3).insert(2, '.') + : fw.left(3).insert(1, '.'); + game.fw = fw_.toStdString(); + } + } + if (auto app_ver = psf.GetString("APP_VER"); app_ver.has_value()) { + game.version = *app_ver; + } } return game; } diff --git a/src/qt_gui/gui_context_menus.h b/src/qt_gui/gui_context_menus.h index fb1994bb0..a2f7f28ff 100644 --- a/src/qt_gui/gui_context_menus.h +++ b/src/qt_gui/gui_context_menus.h @@ -80,8 +80,8 @@ public: if (selected == &openSfoViewer) { PSF psf; - if (psf.open(m_games[itemID].path + "/sce_sys/param.sfo", {})) { - int rows = psf.map_strings.size() + psf.map_integers.size(); + if (psf.Open(std::filesystem::path(m_games[itemID].path) / "sce_sys" / "param.sfo")) { + int rows = psf.GetEntries().size(); QTableWidget* tableWidget = new QTableWidget(rows, 2); tableWidget->setAttribute(Qt::WA_DeleteOnClose); connect(widget->parent(), &QWidget::destroyed, tableWidget, @@ -90,23 +90,45 @@ public: tableWidget->verticalHeader()->setVisible(false); // Hide vertical header int row = 0; - for (const auto& pair : psf.map_strings) { + for (const auto& entry : psf.GetEntries()) { QTableWidgetItem* keyItem = - new QTableWidgetItem(QString::fromStdString(pair.first)); - QTableWidgetItem* valueItem = - new QTableWidgetItem(QString::fromStdString(pair.second)); - - tableWidget->setItem(row, 0, keyItem); - tableWidget->setItem(row, 1, valueItem); - keyItem->setFlags(keyItem->flags() & ~Qt::ItemIsEditable); - valueItem->setFlags(valueItem->flags() & ~Qt::ItemIsEditable); - row++; - } - for (const auto& pair : psf.map_integers) { - QTableWidgetItem* keyItem = - new QTableWidgetItem(QString::fromStdString(pair.first)); - QTableWidgetItem* valueItem = new QTableWidgetItem( - QString("0x").append(QString::number(pair.second, 16))); + new QTableWidgetItem(QString::fromStdString(entry.key)); + QTableWidgetItem* valueItem; + switch (entry.param_fmt) { + case PSFEntryFmt::Binary: { + const auto bin = psf.GetBinary(entry.key); + if (!bin.has_value()) { + valueItem = new QTableWidgetItem(QString("Unknown")); + } else { + std::string text; + text.reserve(bin->size() * 2); + for (const auto& c : *bin) { + static constexpr char hex[] = "0123456789ABCDEF"; + text.push_back(hex[c >> 4 & 0xF]); + text.push_back(hex[c & 0xF]); + } + valueItem = new QTableWidgetItem(QString::fromStdString(text)); + } + } break; + case PSFEntryFmt::Text: { + auto text = psf.GetString(entry.key); + if (!text.has_value()) { + valueItem = new QTableWidgetItem(QString("Unknown")); + } else { + valueItem = + new QTableWidgetItem(QString::fromStdString(std::string{*text})); + } + } break; + case PSFEntryFmt::Integer: { + auto integer = psf.GetInteger(entry.key); + if (!integer.has_value()) { + valueItem = new QTableWidgetItem(QString("Unknown")); + } else { + valueItem = + new QTableWidgetItem(QString("0x") + QString::number(*integer, 16)); + } + } break; + } tableWidget->setItem(row, 0, keyItem); tableWidget->setItem(row, 1, valueItem); diff --git a/src/qt_gui/main_window.cpp b/src/qt_gui/main_window.cpp index e5b502c58..c60360665 100644 --- a/src/qt_gui/main_window.cpp +++ b/src/qt_gui/main_window.cpp @@ -509,6 +509,10 @@ void MainWindow::StartGame() { if (gamePath != "") { AddRecentFiles(gamePath); Core::Emulator emulator; + if (!std::filesystem::exists(gamePath.toUtf8().constData())) { + QMessageBox::critical(nullptr, tr("Run Game"), QString(tr("Eboot.bin file not found"))); + return; + } emulator.Run(gamePath.toUtf8().constData()); } } @@ -610,6 +614,11 @@ void MainWindow::BootGame() { path = std::filesystem::path(fileNames[0].toStdWString()); #endif Core::Emulator emulator; + if (!std::filesystem::exists(path)) { + QMessageBox::critical(nullptr, tr("Run Game"), + QString(tr("Eboot.bin file not found"))); + return; + } emulator.Run(path); } } @@ -627,9 +636,19 @@ void MainWindow::InstallDragDropPkg(std::filesystem::path file, int pkgNum, int QMessageBox msgBox; msgBox.setWindowTitle(tr("PKG Extraction")); - psf.open("", pkg.sfo); + if (!psf.Open(pkg.sfo)) { + QMessageBox::critical(this, tr("PKG ERROR"), + "Could not read SFO. Check log for details"); + return; + } - std::string content_id = psf.GetString("CONTENT_ID"); + std::string content_id; + if (auto value = psf.GetString("CONTENT_ID"); value.has_value()) { + content_id = std::string{*value}; + } else { + QMessageBox::critical(this, tr("PKG ERROR"), "PSF file there is no CONTENT_ID"); + return; + } std::string entitlement_label = Common::SplitString(content_id, '-')[2]; auto addon_extract_path = Common::FS::GetUserPath(Common::FS::PathType::AddonsDir) / @@ -638,9 +657,21 @@ void MainWindow::InstallDragDropPkg(std::filesystem::path file, int pkgNum, int auto category = psf.GetString("CATEGORY"); if (pkgType.contains("PATCH")) { - QString pkg_app_version = QString::fromStdString(psf.GetString("APP_VER")); - psf.open(extract_path.string() + "/sce_sys/param.sfo", {}); - QString game_app_version = QString::fromStdString(psf.GetString("APP_VER")); + QString pkg_app_version; + if (auto app_ver = psf.GetString("APP_VER"); app_ver.has_value()) { + pkg_app_version = QString::fromStdString(std::string{*app_ver}); + } else { + QMessageBox::critical(this, tr("PKG ERROR"), "PSF file there is no APP_VER"); + return; + } + psf.Open(extract_path / "sce_sys" / "param.sfo"); + 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}); + } else { + QMessageBox::critical(this, tr("PKG ERROR"), "PSF file there is no APP_VER"); + return; + } double appD = game_app_version.toDouble(); double pkgD = pkg_app_version.toDouble(); if (pkgD == appD) { @@ -915,6 +946,10 @@ void MainWindow::CreateRecentGameActions() { QString gamePath = action->text(); AddRecentFiles(gamePath); // Update the list. Core::Emulator emulator; + if (!std::filesystem::exists(gamePath.toUtf8().constData())) { + QMessageBox::critical(nullptr, tr("Run Game"), QString(tr("Eboot.bin file not found"))); + return; + } emulator.Run(gamePath.toUtf8().constData()); }); } diff --git a/src/qt_gui/pkg_viewer.cpp b/src/qt_gui/pkg_viewer.cpp index 49005c720..8f20f6929 100644 --- a/src/qt_gui/pkg_viewer.cpp +++ b/src/qt_gui/pkg_viewer.cpp @@ -109,13 +109,17 @@ void PKGViewer::ProcessPKGInfo() { path = std::filesystem::path(m_pkg_list[i].toStdWString()); #endif package.Open(path); - psf.open("", package.sfo); - QString title_name = QString::fromStdString(psf.GetString("TITLE")); - QString title_id = QString::fromStdString(psf.GetString("TITLE_ID")); - QString app_type = game_list_util.GetAppType(psf.GetInteger("APP_TYPE")); - QString app_version = QString::fromStdString(psf.GetString("APP_VER")); - QString title_category = QString::fromStdString(psf.GetString("CATEGORY")); - QString pkg_size = game_list_util.FormatSize(package.GetPkgHeader().pkg_size); + psf.Open(package.sfo); + QString title_name = + QString::fromStdString(std::string{psf.GetString("TITLE").value_or("Unknown")}); + QString title_id = + QString::fromStdString(std::string{psf.GetString("TITLE_ID").value_or("Unknown")}); + QString app_type = GameListUtils::GetAppType(psf.GetInteger("APP_TYPE").value_or(0)); + QString app_version = + QString::fromStdString(std::string{psf.GetString("APP_VER").value_or("Unknown")}); + QString title_category = + QString::fromStdString(std::string{psf.GetString("CATEGORY").value_or("Unknown")}); + QString pkg_size = GameListUtils::FormatSize(package.GetPkgHeader().pkg_size); pkg_content_flag = package.GetPkgHeader().pkg_content_flags; QString flagss = ""; for (const auto& flag : package.flagNames) { @@ -126,11 +130,17 @@ void PKGViewer::ProcessPKGInfo() { } } - u32 fw_int = psf.GetInteger("SYSTEM_VER"); - QString fw = QString::number(fw_int, 16); - QString fw_ = fw.length() > 7 ? QString::number(fw_int, 16).left(3).insert(2, '.') + QString fw_ = "Unknown"; + if (const auto fw_int_opt = psf.GetInteger("SYSTEM_VER"); fw_int_opt.has_value()) { + const u32 fw_int = *fw_int_opt; + if (fw_int == 0) { + fw_ = "0.00"; + } else { + QString fw = QString::number(fw_int, 16); + fw_ = fw.length() > 7 ? QString::number(fw_int, 16).left(3).insert(2, '.') : fw.left(3).insert(1, '.'); - fw_ = (fw_int == 0) ? "0.00" : fw_; + } + } char region = package.GetPkgHeader().pkg_content_id[0]; QString pkg_info = ""; if (title_category == "gd" && !flagss.contains("PATCH")) { diff --git a/src/qt_gui/pkg_viewer.h b/src/qt_gui/pkg_viewer.h index 9598328a0..265a03b92 100644 --- a/src/qt_gui/pkg_viewer.h +++ b/src/qt_gui/pkg_viewer.h @@ -33,7 +33,6 @@ private: PKGHeader pkgheader; PKGEntry entry; PSFHeader header; - PSFEntry psfentry; char pkgTitleID[9]; std::vector pkg; u64 pkgSize = 0; diff --git a/src/qt_gui/settings_dialog.cpp b/src/qt_gui/settings_dialog.cpp index 4206c4463..720c68b78 100644 --- a/src/qt_gui/settings_dialog.cpp +++ b/src/qt_gui/settings_dialog.cpp @@ -4,6 +4,8 @@ #include #include +#include "common/logging/backend.h" +#include "common/logging/filter.h" #include "main_window.h" #include "settings_dialog.h" #include "ui_settings_dialog.h" @@ -78,6 +80,11 @@ SettingsDialog::SettingsDialog(std::span physical_devices, QWidge Config::setDefaultValues(); LoadValuesFromConfig(); } + if (Common::Log::IsActive()) { + Common::Log::Filter filter; + filter.ParseFilterString(Config::getLogFilter()); + Common::Log::SetGlobalFilter(filter); + } }); ui->buttonBox->button(QDialogButtonBox::Save)->setText(tr("Save")); diff --git a/src/qt_gui/translations/es_ES.ts b/src/qt_gui/translations/es_ES.ts index e1bc91809..8690b2e88 100644 --- a/src/qt_gui/translations/es_ES.ts +++ b/src/qt_gui/translations/es_ES.ts @@ -21,7 +21,7 @@ This software should not be used to play games you have not legally obtained. - Este software no debe utilizarse para jugar juegos que no hayas obtenido legalmente. + Este software no debe utilizarse para jugar juegos que hayas obtenido ilegalmente. @@ -118,7 +118,7 @@ Copy Serial - Copiar serial + Copiar número de serie @@ -296,7 +296,7 @@ Settings - Configuraciones + Configuración @@ -341,7 +341,7 @@ toolBar - barra de herramientas + Barra de herramientas @@ -365,7 +365,7 @@ Settings - Configuraciones + Configuración @@ -538,7 +538,7 @@ All Patches available for all games have been downloaded. - Todos los parches disponibles para todos los juegos han sido descargados. + Todos los parches disponibles han sido descargados para todos los juegos. @@ -648,7 +648,7 @@ File doesn't appear to be a valid PKG file - El archivo no parece ser un archivo PKG válido + El archivo parece no ser un archivo PKG válido @@ -671,7 +671,7 @@ Serial: - Serie: + Número de serie: @@ -711,7 +711,7 @@ You can delete the cheats you don't want after downloading them. - Puedes eliminar los trucos que no quieras después de descargarlos. + Puedes eliminar los trucos que no quieras una vez descargados. @@ -741,7 +741,7 @@ Patches - Parche + Parches @@ -761,7 +761,7 @@ No patch file found for the current serial. - No se encontró ningún archivo de parche para la serie actual. + No se encontró ningún archivo de parche para el número de serie actual. @@ -937,7 +937,7 @@ Icon - Ícono + Icono @@ -947,7 +947,7 @@ Serial - Serie + Numero de serie diff --git a/src/shader_recompiler/backend/bindings.h b/src/shader_recompiler/backend/bindings.h index 1b53c74eb..510b0c0ec 100644 --- a/src/shader_recompiler/backend/bindings.h +++ b/src/shader_recompiler/backend/bindings.h @@ -9,10 +9,10 @@ namespace Shader::Backend { struct Bindings { u32 unified{}; - u32 uniform_buffer{}; - u32 storage_buffer{}; - u32 texture{}; - u32 image{}; + u32 buffer{}; + u32 user_data{}; + + auto operator<=>(const Bindings&) const = default; }; } // namespace Shader::Backend diff --git a/src/shader_recompiler/backend/spirv/emit_spirv.cpp b/src/shader_recompiler/backend/spirv/emit_spirv.cpp index e671a37eb..8aa292b1c 100644 --- a/src/shader_recompiler/backend/spirv/emit_spirv.cpp +++ b/src/shader_recompiler/backend/spirv/emit_spirv.cpp @@ -265,7 +265,7 @@ void PatchPhiNodes(const IR::Program& program, EmitContext& ctx) { } // Anonymous namespace std::vector EmitSPIRV(const Profile& profile, const RuntimeInfo& runtime_info, - const IR::Program& program, u32& binding) { + const IR::Program& program, Bindings& binding) { EmitContext ctx{profile, runtime_info, program.info, binding}; const Id main{DefineMain(ctx, program)}; DefineEntryPoint(program, ctx, main); diff --git a/src/shader_recompiler/backend/spirv/emit_spirv.h b/src/shader_recompiler/backend/spirv/emit_spirv.h index aada0ff67..5b8da4496 100644 --- a/src/shader_recompiler/backend/spirv/emit_spirv.h +++ b/src/shader_recompiler/backend/spirv/emit_spirv.h @@ -4,12 +4,13 @@ #pragma once #include +#include "shader_recompiler/backend/bindings.h" #include "shader_recompiler/ir/program.h" #include "shader_recompiler/profile.h" namespace Shader::Backend::SPIRV { [[nodiscard]] std::vector EmitSPIRV(const Profile& profile, const RuntimeInfo& runtime_info, - const IR::Program& program, u32& binding); + const IR::Program& program, Bindings& binding); } // namespace Shader::Backend::SPIRV diff --git a/src/shader_recompiler/backend/spirv/emit_spirv_context_get_set.cpp b/src/shader_recompiler/backend/spirv/emit_spirv_context_get_set.cpp index 5fed9b4db..92279c5fb 100644 --- a/src/shader_recompiler/backend/spirv/emit_spirv_context_get_set.cpp +++ b/src/shader_recompiler/backend/spirv/emit_spirv_context_get_set.cpp @@ -86,7 +86,14 @@ Id OutputAttrPointer(EmitContext& ctx, IR::Attribute attr, u32 element) { } // Anonymous namespace Id EmitGetUserData(EmitContext& ctx, IR::ScalarReg reg) { - return ctx.ConstU32(ctx.info.user_data[static_cast(reg)]); + const u32 index = ctx.binding.user_data + ctx.info.ud_mask.Index(reg); + const u32 half = PushData::UdRegsIndex + (index >> 2); + const Id ud_ptr{ctx.OpAccessChain(ctx.TypePointer(spv::StorageClass::PushConstant, ctx.U32[1]), + ctx.push_data_block, ctx.ConstU32(half), + ctx.ConstU32(index & 3))}; + const Id ud_reg{ctx.OpLoad(ctx.U32[1], ud_ptr)}; + ctx.Name(ud_reg, fmt::format("ud_{}", u32(reg))); + return ud_reg; } void EmitGetThreadBitScalarReg(EmitContext& ctx) { diff --git a/src/shader_recompiler/backend/spirv/emit_spirv_image.cpp b/src/shader_recompiler/backend/spirv/emit_spirv_image.cpp index 530f381d7..50d9cc8cb 100644 --- a/src/shader_recompiler/backend/spirv/emit_spirv_image.cpp +++ b/src/shader_recompiler/backend/spirv/emit_spirv_image.cpp @@ -157,17 +157,20 @@ Id EmitImageFetch(EmitContext& ctx, IR::Inst* inst, u32 handle, Id coords, const ImageOperands operands; operands.AddOffset(ctx, offset); operands.Add(spv::ImageOperandsMask::Lod, lod); - return ctx.OpBitcast( - ctx.F32[4], ctx.OpImageFetch(result_type, image, coords, operands.mask, operands.operands)); + const Id texel = + texture.is_storage + ? ctx.OpImageRead(result_type, image, coords, operands.mask, operands.operands) + : ctx.OpImageFetch(result_type, image, coords, operands.mask, operands.operands); + return ctx.OpBitcast(ctx.F32[4], texel); } -Id EmitImageQueryDimensions(EmitContext& ctx, IR::Inst* inst, u32 handle, Id lod, bool skip_mips) { +Id EmitImageQueryDimensions(EmitContext& ctx, IR::Inst* inst, u32 handle, Id lod, bool has_mips) { const auto& texture = ctx.images[handle & 0xFFFF]; const Id image = ctx.OpLoad(texture.image_type, texture.id); const auto type = ctx.info.images[handle & 0xFFFF].type; const Id zero = ctx.u32_zero_value; - const auto mips{[&] { return skip_mips ? zero : ctx.OpImageQueryLevels(ctx.U32[1], image); }}; - const bool uses_lod{type != AmdGpu::ImageType::Color2DMsaa}; + const auto mips{[&] { return has_mips ? ctx.OpImageQueryLevels(ctx.U32[1], image) : zero; }}; + const bool uses_lod{type != AmdGpu::ImageType::Color2DMsaa && !texture.is_storage}; const auto query{[&](Id type) { return uses_lod ? ctx.OpImageQuerySizeLod(type, image, lod) : ctx.OpImageQuerySize(type, image); @@ -178,6 +181,7 @@ Id EmitImageQueryDimensions(EmitContext& ctx, IR::Inst* inst, u32 handle, Id lod case AmdGpu::ImageType::Color1DArray: case AmdGpu::ImageType::Color2D: case AmdGpu::ImageType::Cube: + case AmdGpu::ImageType::Color2DMsaa: return ctx.OpCompositeConstruct(ctx.U32[4], query(ctx.U32[2]), zero, mips()); case AmdGpu::ImageType::Color2DArray: case AmdGpu::ImageType::Color3D: diff --git a/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp b/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp index 2f1f7aa75..dd780622f 100644 --- a/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp +++ b/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp @@ -42,7 +42,7 @@ void Name(EmitContext& ctx, Id object, std::string_view format_str, Args&&... ar } // Anonymous namespace EmitContext::EmitContext(const Profile& profile_, const RuntimeInfo& runtime_info_, - const Info& info_, u32& binding_) + const Info& info_, Bindings& binding_) : Sirit::Module(profile_.supported_spirv), info{info_}, runtime_info{runtime_info_}, profile{profile_}, stage{info.stage}, binding{binding_} { AddCapability(spv::Capability::Shader); @@ -173,7 +173,7 @@ EmitContext::SpirvAttribute EmitContext::GetAttributeInfo(AmdGpu::NumberFormat f } void EmitContext::DefineBufferOffsets() { - for (auto& buffer : buffers) { + for (BufferDefinition& buffer : buffers) { const u32 binding = buffer.binding; const u32 half = PushData::BufOffsetIndex + (binding >> 4); const u32 comp = (binding & 0xf) >> 2; @@ -182,9 +182,11 @@ void EmitContext::DefineBufferOffsets() { push_data_block, ConstU32(half), ConstU32(comp))}; const Id value{OpLoad(U32[1], ptr)}; buffer.offset = OpBitFieldUExtract(U32[1], value, ConstU32(offset), ConstU32(8U)); + Name(buffer.offset, fmt::format("buf{}_off", binding)); buffer.offset_dwords = OpShiftRightLogical(U32[1], buffer.offset, ConstU32(2U)); + Name(buffer.offset_dwords, fmt::format("buf{}_dword_off", binding)); } - for (auto& tex_buffer : texture_buffers) { + for (TextureBufferDefinition& tex_buffer : texture_buffers) { const u32 binding = tex_buffer.binding; const u32 half = PushData::BufOffsetIndex + (binding >> 4); const u32 comp = (binding & 0xf) >> 2; @@ -192,7 +194,8 @@ void EmitContext::DefineBufferOffsets() { const Id ptr{OpAccessChain(TypePointer(spv::StorageClass::PushConstant, U32[1]), push_data_block, ConstU32(half), ConstU32(comp))}; const Id value{OpLoad(U32[1], ptr)}; - tex_buffer.coord_offset = OpBitFieldUExtract(U32[1], value, ConstU32(offset), ConstU32(8U)); + tex_buffer.coord_offset = OpBitFieldUExtract(U32[1], value, ConstU32(offset), ConstU32(6U)); + Name(tex_buffer.coord_offset, fmt::format("texbuf{}_off", binding)); } } @@ -330,18 +333,25 @@ void EmitContext::DefineOutputs() { void EmitContext::DefinePushDataBlock() { // Create push constants block for instance steps rates - const Id struct_type{Name(TypeStruct(U32[1], U32[1], U32[4], U32[4], U32[4]), "AuxData")}; + const Id struct_type{Name( + TypeStruct(U32[1], U32[1], U32[4], U32[4], U32[4], U32[4], U32[4], U32[4]), "AuxData")}; Decorate(struct_type, spv::Decoration::Block); MemberName(struct_type, 0, "sr0"); MemberName(struct_type, 1, "sr1"); MemberName(struct_type, 2, "buf_offsets0"); MemberName(struct_type, 3, "buf_offsets1"); - MemberName(struct_type, 4, "buf_offsets2"); + MemberName(struct_type, 4, "ud_regs0"); + MemberName(struct_type, 5, "ud_regs1"); + MemberName(struct_type, 6, "ud_regs2"); + MemberName(struct_type, 7, "ud_regs3"); MemberDecorate(struct_type, 0, spv::Decoration::Offset, 0U); MemberDecorate(struct_type, 1, spv::Decoration::Offset, 4U); MemberDecorate(struct_type, 2, spv::Decoration::Offset, 8U); MemberDecorate(struct_type, 3, spv::Decoration::Offset, 24U); MemberDecorate(struct_type, 4, spv::Decoration::Offset, 40U); + MemberDecorate(struct_type, 5, spv::Decoration::Offset, 56U); + MemberDecorate(struct_type, 6, spv::Decoration::Offset, 72U); + MemberDecorate(struct_type, 7, spv::Decoration::Offset, 88U); push_data_block = DefineVar(struct_type, spv::StorageClass::PushConstant); Name(push_data_block, "push_data"); interfaces.push_back(push_data_block); @@ -379,7 +389,7 @@ void EmitContext::DefineBuffers() { const Id struct_pointer_type{TypePointer(storage_class, struct_type)}; const Id pointer_type = TypePointer(storage_class, data_type); const Id id{AddGlobalVariable(struct_pointer_type, storage_class)}; - Decorate(id, spv::Decoration::Binding, binding); + Decorate(id, spv::Decoration::Binding, binding.unified++); Decorate(id, spv::Decoration::DescriptorSet, 0U); if (is_storage && !desc.is_written) { Decorate(id, spv::Decoration::NonWritable); @@ -388,7 +398,7 @@ void EmitContext::DefineBuffers() { buffers.push_back({ .id = id, - .binding = binding++, + .binding = binding.buffer++, .data_types = data_types, .pointer_type = pointer_type, }); @@ -406,12 +416,12 @@ void EmitContext::DefineTextureBuffers() { sampled, spv::ImageFormat::Unknown)}; const Id pointer_type{TypePointer(spv::StorageClass::UniformConstant, image_type)}; const Id id{AddGlobalVariable(pointer_type, spv::StorageClass::UniformConstant)}; - Decorate(id, spv::Decoration::Binding, binding); + Decorate(id, spv::Decoration::Binding, binding.unified++); Decorate(id, spv::Decoration::DescriptorSet, 0U); Name(id, fmt::format("{}_{}", desc.is_written ? "imgbuf" : "texbuf", desc.sgpr_base)); texture_buffers.push_back({ .id = id, - .binding = binding++, + .binding = binding.buffer++, .image_type = image_type, .result_type = sampled_type[4], .is_integer = is_integer, @@ -507,10 +517,13 @@ Id ImageType(EmitContext& ctx, const ImageResource& desc, Id sampled_type) { return ctx.TypeImage(sampled_type, spv::Dim::Dim2D, false, false, false, sampled, format); case AmdGpu::ImageType::Color2DArray: return ctx.TypeImage(sampled_type, spv::Dim::Dim2D, false, true, false, sampled, format); + case AmdGpu::ImageType::Color2DMsaa: + return ctx.TypeImage(sampled_type, spv::Dim::Dim2D, false, false, true, sampled, format); case AmdGpu::ImageType::Color3D: return ctx.TypeImage(sampled_type, spv::Dim::Dim3D, false, false, false, sampled, format); case AmdGpu::ImageType::Cube: - return ctx.TypeImage(sampled_type, spv::Dim::Cube, false, false, false, sampled, format); + return ctx.TypeImage(sampled_type, spv::Dim::Cube, false, desc.is_array, false, sampled, + format); default: break; } @@ -524,7 +537,7 @@ void EmitContext::DefineImagesAndSamplers() { const Id image_type{ImageType(*this, image_desc, sampled_type)}; const Id pointer_type{TypePointer(spv::StorageClass::UniformConstant, image_type)}; const Id id{AddGlobalVariable(pointer_type, spv::StorageClass::UniformConstant)}; - Decorate(id, spv::Decoration::Binding, binding); + Decorate(id, spv::Decoration::Binding, binding.unified++); Decorate(id, spv::Decoration::DescriptorSet, 0U); Name(id, fmt::format("{}_{}{}_{:02x}", stage, "img", image_desc.sgpr_base, image_desc.dword_offset)); @@ -534,9 +547,9 @@ void EmitContext::DefineImagesAndSamplers() { .sampled_type = image_desc.is_storage ? sampled_type : TypeSampledImage(image_type), .pointer_type = pointer_type, .image_type = image_type, + .is_storage = image_desc.is_storage, }); interfaces.push_back(id); - ++binding; } if (std::ranges::any_of(info.images, &ImageResource::is_atomic)) { image_u32 = TypePointer(spv::StorageClass::Image, U32[1]); @@ -548,13 +561,12 @@ void EmitContext::DefineImagesAndSamplers() { sampler_pointer_type = TypePointer(spv::StorageClass::UniformConstant, sampler_type); for (const auto& samp_desc : info.samplers) { const Id id{AddGlobalVariable(sampler_pointer_type, spv::StorageClass::UniformConstant)}; - Decorate(id, spv::Decoration::Binding, binding); + Decorate(id, spv::Decoration::Binding, binding.unified++); Decorate(id, spv::Decoration::DescriptorSet, 0U); Name(id, fmt::format("{}_{}{}_{:02x}", stage, "samp", samp_desc.sgpr_base, samp_desc.dword_offset)); samplers.push_back(id); interfaces.push_back(id); - ++binding; } } diff --git a/src/shader_recompiler/backend/spirv/spirv_emit_context.h b/src/shader_recompiler/backend/spirv/spirv_emit_context.h index 0908b7f82..9029866b0 100644 --- a/src/shader_recompiler/backend/spirv/spirv_emit_context.h +++ b/src/shader_recompiler/backend/spirv/spirv_emit_context.h @@ -6,6 +6,7 @@ #include #include +#include "shader_recompiler/backend/bindings.h" #include "shader_recompiler/info.h" #include "shader_recompiler/ir/program.h" #include "shader_recompiler/profile.h" @@ -37,7 +38,7 @@ struct VectorIds { class EmitContext final : public Sirit::Module { public: explicit EmitContext(const Profile& profile, const RuntimeInfo& runtime_info, const Info& info, - u32& binding); + Bindings& binding); ~EmitContext(); Id Def(const IR::Value& value); @@ -200,6 +201,7 @@ public: Id sampled_type; Id pointer_type; Id image_type; + bool is_storage = false; }; struct BufferDefinition { @@ -216,11 +218,11 @@ public: u32 binding; Id image_type; Id result_type; - bool is_integer; - bool is_storage; + bool is_integer = false; + bool is_storage = false; }; - u32& binding; + Bindings& binding; boost::container::small_vector buffers; boost::container::small_vector texture_buffers; boost::container::small_vector images; diff --git a/src/shader_recompiler/frontend/control_flow_graph.cpp b/src/shader_recompiler/frontend/control_flow_graph.cpp index 276bd9db0..9d481d32c 100644 --- a/src/shader_recompiler/frontend/control_flow_graph.cpp +++ b/src/shader_recompiler/frontend/control_flow_graph.cpp @@ -23,7 +23,6 @@ struct Compare { static IR::Condition MakeCondition(const GcnInst& inst) { if (inst.IsCmpx()) { - ASSERT(inst.opcode == Opcode::V_CMPX_NE_U32); return IR::Condition::Execnz; } @@ -99,7 +98,7 @@ void CFG::EmitDivergenceLabels() { // with SAVEEXEC to mask the threads that didn't pass the condition // of initial branch. (inst.opcode == Opcode::S_ANDN2_B64 && inst.dst[0].field == OperandField::ExecLo) || - inst.opcode == Opcode::V_CMPX_NE_U32; + inst.IsCmpx(); }; const auto is_close_scope = [](const GcnInst& inst) { // Closing an EXEC scope can be either a branch instruction @@ -109,7 +108,7 @@ void CFG::EmitDivergenceLabels() { // Sometimes compiler might insert instructions between the SAVEEXEC and the branch. // Those instructions need to be wrapped in the condition as well so allow branch // as end scope instruction. - inst.opcode == Opcode::S_CBRANCH_EXECZ || + inst.opcode == Opcode::S_CBRANCH_EXECZ || inst.opcode == Opcode::S_ENDPGM || (inst.opcode == Opcode::S_ANDN2_B64 && inst.dst[0].field == OperandField::ExecLo); }; @@ -127,7 +126,8 @@ void CFG::EmitDivergenceLabels() { s32 curr_begin = -1; for (size_t index = GetIndex(start); index < end_index; index++) { const auto& inst = inst_list[index]; - if (is_close_scope(inst) && curr_begin != -1) { + const bool is_close = is_close_scope(inst); + if ((is_close || index == end_index - 1) && curr_begin != -1) { // If there are no instructions inside scope don't do anything. if (index - curr_begin == 1) { curr_begin = -1; @@ -138,8 +138,16 @@ void CFG::EmitDivergenceLabels() { const auto& save_inst = inst_list[curr_begin]; const Label label = index_to_pc[curr_begin] + save_inst.length; AddLabel(label); - // Add a label to the close scope instruction as well. - AddLabel(index_to_pc[index]); + // Add a label to the close scope instruction. + // There are 3 cases where we need to close a scope. + // * Close scope instruction inside the block + // * Close scope instruction at the end of the block (cbranch or endpgm) + // * Normal instruction at the end of the block + // For the last case we must NOT add a label as that would cause + // the instruction to be separated into its own basic block. + if (is_close) { + AddLabel(index_to_pc[index]); + } // Reset scope begin. curr_begin = -1; } @@ -194,7 +202,7 @@ void CFG::LinkBlocks() { const auto end_inst{block.end_inst}; // Handle divergence block inserted here. if (end_inst.opcode == Opcode::S_AND_SAVEEXEC_B64 || - end_inst.opcode == Opcode::S_ANDN2_B64 || end_inst.opcode == Opcode::V_CMPX_NE_U32) { + end_inst.opcode == Opcode::S_ANDN2_B64 || end_inst.IsCmpx()) { // Blocks are stored ordered by address in the set auto next_it = std::next(it); auto* target_block = &(*next_it); diff --git a/src/shader_recompiler/frontend/decode.cpp b/src/shader_recompiler/frontend/decode.cpp index 26a2c1a6c..6020f93bb 100644 --- a/src/shader_recompiler/frontend/decode.cpp +++ b/src/shader_recompiler/frontend/decode.cpp @@ -1032,6 +1032,7 @@ void GcnDecodeContext::decodeInstructionMIMG(uint64_t hexInstruction) { m_instruction.control.mimg = *reinterpret_cast(&hexInstruction); m_instruction.control.mimg.mod = getMimgModifier(m_instruction.opcode); + ASSERT(m_instruction.control.mimg.r128 == 0); } void GcnDecodeContext::decodeInstructionDS(uint64_t hexInstruction) { diff --git a/src/shader_recompiler/frontend/translate/data_share.cpp b/src/shader_recompiler/frontend/translate/data_share.cpp index 0c3d21a96..247ad65c2 100644 --- a/src/shader_recompiler/frontend/translate/data_share.cpp +++ b/src/shader_recompiler/frontend/translate/data_share.cpp @@ -7,53 +7,157 @@ namespace Shader::Gcn { void Translator::EmitDataShare(const GcnInst& inst) { switch (inst.opcode) { - case Opcode::DS_SWIZZLE_B32: - return DS_SWIZZLE_B32(inst); - case Opcode::DS_READ_B32: - return DS_READ(32, false, false, false, inst); - case Opcode::DS_READ2ST64_B32: - return DS_READ(32, false, true, true, inst); - case Opcode::DS_READ_B64: - return DS_READ(64, false, false, false, inst); - case Opcode::DS_READ2_B32: - return DS_READ(32, false, true, false, inst); - case Opcode::DS_READ2_B64: - return DS_READ(64, false, true, false, inst); - case Opcode::DS_WRITE_B32: - return DS_WRITE(32, false, false, false, inst); - case Opcode::DS_WRITE2ST64_B32: - return DS_WRITE(32, false, true, true, inst); - case Opcode::DS_WRITE_B64: - return DS_WRITE(64, false, false, false, inst); - case Opcode::DS_WRITE2_B32: - return DS_WRITE(32, false, true, false, inst); - case Opcode::DS_WRITE2_B64: - return DS_WRITE(64, false, true, false, inst); + // DS case Opcode::DS_ADD_U32: return DS_ADD_U32(inst, false); - case Opcode::DS_MIN_U32: - return DS_MIN_U32(inst, false, false); case Opcode::DS_MIN_I32: return DS_MIN_U32(inst, true, false); - case Opcode::DS_MAX_U32: - return DS_MAX_U32(inst, false, false); case Opcode::DS_MAX_I32: return DS_MAX_U32(inst, true, false); + case Opcode::DS_MIN_U32: + return DS_MIN_U32(inst, false, false); + case Opcode::DS_MAX_U32: + return DS_MAX_U32(inst, false, false); + case Opcode::DS_WRITE_B32: + return DS_WRITE(32, false, false, false, inst); + case Opcode::DS_WRITE2_B32: + return DS_WRITE(32, false, true, false, inst); + case Opcode::DS_WRITE2ST64_B32: + return DS_WRITE(32, false, true, true, inst); case Opcode::DS_ADD_RTN_U32: return DS_ADD_U32(inst, true); case Opcode::DS_MIN_RTN_U32: return DS_MIN_U32(inst, false, true); case Opcode::DS_MAX_RTN_U32: return DS_MAX_U32(inst, false, true); - case Opcode::DS_APPEND: - return DS_APPEND(inst); + case Opcode::DS_SWIZZLE_B32: + return DS_SWIZZLE_B32(inst); + case Opcode::DS_READ_B32: + return DS_READ(32, false, false, false, inst); + case Opcode::DS_READ2_B32: + return DS_READ(32, false, true, false, inst); + case Opcode::DS_READ2ST64_B32: + return DS_READ(32, false, true, true, inst); case Opcode::DS_CONSUME: return DS_CONSUME(inst); + case Opcode::DS_APPEND: + return DS_APPEND(inst); + case Opcode::DS_WRITE_B64: + return DS_WRITE(64, false, false, false, inst); + case Opcode::DS_WRITE2_B64: + return DS_WRITE(64, false, true, false, inst); + case Opcode::DS_READ_B64: + return DS_READ(64, false, false, false, inst); + case Opcode::DS_READ2_B64: + return DS_READ(64, false, true, false, inst); default: LogMissingOpcode(inst); } } +// SOPP + +void Translator::S_BARRIER() { + ir.Barrier(); +} + +// VOP2 + +void Translator::V_READFIRSTLANE_B32(const GcnInst& inst) { + const IR::ScalarReg dst{inst.dst[0].code}; + const IR::U32 value{GetSrc(inst.src[0])}; + + if (info.stage != Stage::Compute) { + SetDst(inst.dst[0], value); + } else { + SetDst(inst.dst[0], ir.ReadFirstLane(value)); + } +} + +void Translator::V_READLANE_B32(const GcnInst& inst) { + const IR::ScalarReg dst{inst.dst[0].code}; + const IR::U32 value{GetSrc(inst.src[0])}; + const IR::U32 lane{GetSrc(inst.src[1])}; + ir.SetScalarReg(dst, ir.ReadLane(value, lane)); +} + +void Translator::V_WRITELANE_B32(const GcnInst& inst) { + const IR::VectorReg dst{inst.dst[0].code}; + const IR::U32 value{GetSrc(inst.src[0])}; + const IR::U32 lane{GetSrc(inst.src[1])}; + const IR::U32 old_value{GetSrc(inst.dst[0])}; + ir.SetVectorReg(dst, ir.WriteLane(old_value, value, lane)); +} + +// DS + +void Translator::DS_ADD_U32(const GcnInst& inst, bool rtn) { + const IR::U32 addr{GetSrc(inst.src[0])}; + const IR::U32 data{GetSrc(inst.src[1])}; + const IR::U32 offset = ir.Imm32(u32(inst.control.ds.offset0)); + const IR::U32 addr_offset = ir.IAdd(addr, offset); + const IR::Value original_val = ir.SharedAtomicIAdd(addr_offset, data); + if (rtn) { + SetDst(inst.dst[0], IR::U32{original_val}); + } +} + +void Translator::DS_MIN_U32(const GcnInst& inst, bool is_signed, bool rtn) { + const IR::U32 addr{GetSrc(inst.src[0])}; + const IR::U32 data{GetSrc(inst.src[1])}; + const IR::U32 offset = ir.Imm32(u32(inst.control.ds.offset0)); + const IR::U32 addr_offset = ir.IAdd(addr, offset); + const IR::Value original_val = ir.SharedAtomicIMin(addr_offset, data, is_signed); + if (rtn) { + SetDst(inst.dst[0], IR::U32{original_val}); + } +} + +void Translator::DS_MAX_U32(const GcnInst& inst, bool is_signed, bool rtn) { + const IR::U32 addr{GetSrc(inst.src[0])}; + const IR::U32 data{GetSrc(inst.src[1])}; + const IR::U32 offset = ir.Imm32(u32(inst.control.ds.offset0)); + const IR::U32 addr_offset = ir.IAdd(addr, offset); + const IR::Value original_val = ir.SharedAtomicIMax(addr_offset, data, is_signed); + if (rtn) { + SetDst(inst.dst[0], IR::U32{original_val}); + } +} + +void Translator::DS_WRITE(int bit_size, bool is_signed, bool is_pair, bool stride64, + const GcnInst& inst) { + const IR::U32 addr{ir.GetVectorReg(IR::VectorReg(inst.src[0].code))}; + const IR::VectorReg data0{inst.src[1].code}; + const IR::VectorReg data1{inst.src[2].code}; + if (is_pair) { + const u32 adj = (bit_size == 32 ? 4 : 8) * (stride64 ? 64 : 1); + const IR::U32 addr0 = ir.IAdd(addr, ir.Imm32(u32(inst.control.ds.offset0 * adj))); + if (bit_size == 32) { + ir.WriteShared(32, ir.GetVectorReg(data0), addr0); + } else { + ir.WriteShared( + 64, ir.CompositeConstruct(ir.GetVectorReg(data0), ir.GetVectorReg(data0 + 1)), + addr0); + } + const IR::U32 addr1 = ir.IAdd(addr, ir.Imm32(u32(inst.control.ds.offset1 * adj))); + if (bit_size == 32) { + ir.WriteShared(32, ir.GetVectorReg(data1), addr1); + } else { + ir.WriteShared( + 64, ir.CompositeConstruct(ir.GetVectorReg(data1), ir.GetVectorReg(data1 + 1)), + addr1); + } + } else if (bit_size == 64) { + const IR::U32 addr0 = ir.IAdd(addr, ir.Imm32(u32(inst.control.ds.offset0))); + const IR::Value data = + ir.CompositeConstruct(ir.GetVectorReg(data0), ir.GetVectorReg(data0 + 1)); + ir.WriteShared(bit_size, data, addr0); + } else { + const IR::U32 addr0 = ir.IAdd(addr, ir.Imm32(u32(inst.control.ds.offset0))); + ir.WriteShared(bit_size, ir.GetVectorReg(data0), addr0); + } +} + void Translator::DS_SWIZZLE_B32(const GcnInst& inst) { const u8 offset0 = inst.control.ds.offset0; const u8 offset1 = inst.control.ds.offset1; @@ -102,103 +206,6 @@ void Translator::DS_READ(int bit_size, bool is_signed, bool is_pair, bool stride } } -void Translator::DS_WRITE(int bit_size, bool is_signed, bool is_pair, bool stride64, - const GcnInst& inst) { - const IR::U32 addr{ir.GetVectorReg(IR::VectorReg(inst.src[0].code))}; - const IR::VectorReg data0{inst.src[1].code}; - const IR::VectorReg data1{inst.src[2].code}; - if (is_pair) { - const u32 adj = (bit_size == 32 ? 4 : 8) * (stride64 ? 64 : 1); - const IR::U32 addr0 = ir.IAdd(addr, ir.Imm32(u32(inst.control.ds.offset0 * adj))); - if (bit_size == 32) { - ir.WriteShared(32, ir.GetVectorReg(data0), addr0); - } else { - ir.WriteShared( - 64, ir.CompositeConstruct(ir.GetVectorReg(data0), ir.GetVectorReg(data0 + 1)), - addr0); - } - const IR::U32 addr1 = ir.IAdd(addr, ir.Imm32(u32(inst.control.ds.offset1 * adj))); - if (bit_size == 32) { - ir.WriteShared(32, ir.GetVectorReg(data1), addr1); - } else { - ir.WriteShared( - 64, ir.CompositeConstruct(ir.GetVectorReg(data1), ir.GetVectorReg(data1 + 1)), - addr1); - } - } else if (bit_size == 64) { - const IR::U32 addr0 = ir.IAdd(addr, ir.Imm32(u32(inst.control.ds.offset0))); - const IR::Value data = - ir.CompositeConstruct(ir.GetVectorReg(data0), ir.GetVectorReg(data0 + 1)); - ir.WriteShared(bit_size, data, addr0); - } else { - const IR::U32 addr0 = ir.IAdd(addr, ir.Imm32(u32(inst.control.ds.offset0))); - ir.WriteShared(bit_size, ir.GetVectorReg(data0), addr0); - } -} - -void Translator::DS_ADD_U32(const GcnInst& inst, bool rtn) { - const IR::U32 addr{GetSrc(inst.src[0])}; - const IR::U32 data{GetSrc(inst.src[1])}; - const IR::U32 offset = ir.Imm32(u32(inst.control.ds.offset0)); - const IR::U32 addr_offset = ir.IAdd(addr, offset); - const IR::Value original_val = ir.SharedAtomicIAdd(addr_offset, data); - if (rtn) { - SetDst(inst.dst[0], IR::U32{original_val}); - } -} - -void Translator::DS_MIN_U32(const GcnInst& inst, bool is_signed, bool rtn) { - const IR::U32 addr{GetSrc(inst.src[0])}; - const IR::U32 data{GetSrc(inst.src[1])}; - const IR::U32 offset = ir.Imm32(u32(inst.control.ds.offset0)); - const IR::U32 addr_offset = ir.IAdd(addr, offset); - const IR::Value original_val = ir.SharedAtomicIMin(addr_offset, data, is_signed); - if (rtn) { - SetDst(inst.dst[0], IR::U32{original_val}); - } -} - -void Translator::DS_MAX_U32(const GcnInst& inst, bool is_signed, bool rtn) { - const IR::U32 addr{GetSrc(inst.src[0])}; - const IR::U32 data{GetSrc(inst.src[1])}; - const IR::U32 offset = ir.Imm32(u32(inst.control.ds.offset0)); - const IR::U32 addr_offset = ir.IAdd(addr, offset); - const IR::Value original_val = ir.SharedAtomicIMax(addr_offset, data, is_signed); - if (rtn) { - SetDst(inst.dst[0], IR::U32{original_val}); - } -} - -void Translator::S_BARRIER() { - ir.Barrier(); -} - -void Translator::V_READFIRSTLANE_B32(const GcnInst& inst) { - const IR::ScalarReg dst{inst.dst[0].code}; - const IR::U32 value{GetSrc(inst.src[0])}; - - if (info.stage != Stage::Compute) { - SetDst(inst.dst[0], value); - } else { - SetDst(inst.dst[0], ir.ReadFirstLane(value)); - } -} - -void Translator::V_READLANE_B32(const GcnInst& inst) { - const IR::ScalarReg dst{inst.dst[0].code}; - const IR::U32 value{GetSrc(inst.src[0])}; - const IR::U32 lane{GetSrc(inst.src[1])}; - ir.SetScalarReg(dst, ir.ReadLane(value, lane)); -} - -void Translator::V_WRITELANE_B32(const GcnInst& inst) { - const IR::VectorReg dst{inst.dst[0].code}; - const IR::U32 value{GetSrc(inst.src[0])}; - const IR::U32 lane{GetSrc(inst.src[1])}; - const IR::U32 old_value{GetSrc(inst.dst[0])}; - ir.SetVectorReg(dst, ir.WriteLane(old_value, value, lane)); -} - void Translator::DS_APPEND(const GcnInst& inst) { const u32 inst_offset = inst.control.ds.offset0; const IR::U32 gds_offset = ir.IAdd(ir.GetM0(), ir.Imm32(inst_offset)); diff --git a/src/shader_recompiler/frontend/translate/export.cpp b/src/shader_recompiler/frontend/translate/export.cpp index 18e830f7b..7d901822d 100644 --- a/src/shader_recompiler/frontend/translate/export.cpp +++ b/src/shader_recompiler/frontend/translate/export.cpp @@ -71,6 +71,9 @@ void Translator::EmitExport(const GcnInst& inst) { ir.SetAttribute(attrib, comp, swizzle(i)); } } + if (IR::IsMrt(attrib)) { + info.mrt_mask |= 1u << u8(attrib); + } } } // namespace Shader::Gcn diff --git a/src/shader_recompiler/frontend/translate/scalar_alu.cpp b/src/shader_recompiler/frontend/translate/scalar_alu.cpp index e246b5c51..1e572a97f 100644 --- a/src/shader_recompiler/frontend/translate/scalar_alu.cpp +++ b/src/shader_recompiler/frontend/translate/scalar_alu.cpp @@ -17,79 +17,83 @@ void Translator::EmitScalarAlu(const GcnInst& inst) { } default: switch (inst.opcode) { - case Opcode::S_MOV_B32: - return S_MOV(inst); - case Opcode::S_MUL_I32: - return S_MUL_I32(inst); - case Opcode::S_AND_SAVEEXEC_B64: - return S_AND_SAVEEXEC_B64(inst); - case Opcode::S_MOV_B64: - return S_MOV_B64(inst); - case Opcode::S_OR_B64: - return S_OR_B64(NegateMode::None, false, inst); - case Opcode::S_NOR_B64: - return S_OR_B64(NegateMode::Result, false, inst); - case Opcode::S_XOR_B64: - return S_OR_B64(NegateMode::None, true, inst); - case Opcode::S_XNOR_B64: - return S_OR_B64(NegateMode::Result, true, inst); - case Opcode::S_ORN2_B64: - return S_OR_B64(NegateMode::Src1, false, inst); - case Opcode::S_AND_B64: - return S_AND_B64(NegateMode::None, inst); - case Opcode::S_NAND_B64: - return S_AND_B64(NegateMode::Result, inst); - case Opcode::S_ANDN2_B64: - return S_AND_B64(NegateMode::Src1, inst); - case Opcode::S_NOT_B64: - return S_NOT_B64(inst); + // SOP2 + case Opcode::S_ADD_U32: + return S_ADD_U32(inst); + case Opcode::S_SUB_U32: + return S_SUB_U32(inst); case Opcode::S_ADD_I32: return S_ADD_I32(inst); - case Opcode::S_AND_B32: - return S_AND_B32(NegateMode::None, inst); - case Opcode::S_NAND_B32: - return S_AND_B32(NegateMode::Result, inst); - case Opcode::S_ANDN2_B32: - return S_AND_B32(NegateMode::Src1, inst); - case Opcode::S_ASHR_I32: - return S_ASHR_I32(inst); - case Opcode::S_OR_B32: - return S_OR_B32(inst); - case Opcode::S_XOR_B32: - return S_XOR_B32(inst); - case Opcode::S_LSHL_B32: - return S_LSHL_B32(inst); - case Opcode::S_LSHR_B32: - return S_LSHR_B32(inst); + case Opcode::S_SUB_I32: + return S_SUB_U32(inst); + case Opcode::S_ADDC_U32: + return S_ADDC_U32(inst); + case Opcode::S_MIN_I32: + return S_MIN_U32(true, inst); + case Opcode::S_MIN_U32: + return S_MIN_U32(false, inst); + case Opcode::S_MAX_I32: + return S_MAX_U32(true, inst); + case Opcode::S_MAX_U32: + return S_MAX_U32(false, inst); case Opcode::S_CSELECT_B32: return S_CSELECT_B32(inst); case Opcode::S_CSELECT_B64: return S_CSELECT_B64(inst); - case Opcode::S_BFE_U32: - return S_BFE_U32(inst); + case Opcode::S_AND_B32: + return S_AND_B32(NegateMode::None, inst); + case Opcode::S_AND_B64: + return S_AND_B64(NegateMode::None, inst); + case Opcode::S_OR_B32: + return S_OR_B32(inst); + case Opcode::S_OR_B64: + return S_OR_B64(NegateMode::None, false, inst); + case Opcode::S_XOR_B32: + return S_XOR_B32(inst); + case Opcode::S_XOR_B64: + return S_OR_B64(NegateMode::None, true, inst); + case Opcode::S_ANDN2_B32: + return S_AND_B32(NegateMode::Src1, inst); + case Opcode::S_ANDN2_B64: + return S_AND_B64(NegateMode::Src1, inst); + case Opcode::S_ORN2_B64: + return S_OR_B64(NegateMode::Src1, false, inst); + case Opcode::S_NAND_B32: + return S_AND_B32(NegateMode::Result, inst); + case Opcode::S_NAND_B64: + return S_AND_B64(NegateMode::Result, inst); + case Opcode::S_NOR_B64: + return S_OR_B64(NegateMode::Result, false, inst); + case Opcode::S_XNOR_B64: + return S_OR_B64(NegateMode::Result, true, inst); + case Opcode::S_LSHL_B32: + return S_LSHL_B32(inst); + case Opcode::S_LSHR_B32: + return S_LSHR_B32(inst); + case Opcode::S_ASHR_I32: + return S_ASHR_I32(inst); case Opcode::S_BFM_B32: return S_BFM_B32(inst); - case Opcode::S_BREV_B32: - return S_BREV_B32(inst); - case Opcode::S_ADD_U32: - return S_ADD_U32(inst); - case Opcode::S_ADDC_U32: - return S_ADDC_U32(inst); - case Opcode::S_SUB_U32: - case Opcode::S_SUB_I32: - return S_SUB_U32(inst); - case Opcode::S_MIN_U32: - return S_MIN_U32(false, inst); - case Opcode::S_MIN_I32: - return S_MIN_U32(true, inst); - case Opcode::S_MAX_U32: - return S_MAX_U32(false, inst); - case Opcode::S_MAX_I32: - return S_MAX_U32(true, inst); + case Opcode::S_MUL_I32: + return S_MUL_I32(inst); + case Opcode::S_BFE_U32: + return S_BFE_U32(inst); case Opcode::S_ABSDIFF_I32: return S_ABSDIFF_I32(inst); + + // SOP1 + case Opcode::S_MOV_B32: + return S_MOV(inst); + case Opcode::S_MOV_B64: + return S_MOV_B64(inst); + case Opcode::S_NOT_B64: + return S_NOT_B64(inst); case Opcode::S_WQM_B64: break; + case Opcode::S_BREV_B32: + return S_BREV_B32(inst); + case Opcode::S_AND_SAVEEXEC_B64: + return S_AND_SAVEEXEC_B64(inst); default: LogMissingOpcode(inst); } @@ -131,6 +135,7 @@ void Translator::EmitSOPC(const GcnInst& inst) { void Translator::EmitSOPK(const GcnInst& inst) { switch (inst.opcode) { + // SOPK case Opcode::S_MOVK_I32: return S_MOVK(inst); @@ -169,217 +174,22 @@ void Translator::EmitSOPK(const GcnInst& inst) { } } -void Translator::S_MOVK(const GcnInst& inst) { - const auto simm16 = inst.control.sopk.simm; - if (simm16 & (1 << 15)) { - // TODO: need to verify the case of imm sign extension - UNREACHABLE(); - } - SetDst(inst.dst[0], ir.Imm32(simm16)); +// SOP2 + +void Translator::S_ADD_U32(const GcnInst& inst) { + const IR::U32 src0{GetSrc(inst.src[0])}; + const IR::U32 src1{GetSrc(inst.src[1])}; + SetDst(inst.dst[0], ir.IAdd(src0, src1)); + // TODO: Carry out + ir.SetScc(ir.Imm1(false)); } -void Translator::S_ADDK_I32(const GcnInst& inst) { - const s32 simm16 = inst.control.sopk.simm; - SetDst(inst.dst[0], ir.IAdd(GetSrc(inst.dst[0]), ir.Imm32(simm16))); -} - -void Translator::S_MULK_I32(const GcnInst& inst) { - const s32 simm16 = inst.control.sopk.simm; - SetDst(inst.dst[0], ir.IMul(GetSrc(inst.dst[0]), ir.Imm32(simm16))); -} - -void Translator::S_MOV(const GcnInst& inst) { - SetDst(inst.dst[0], GetSrc(inst.src[0])); -} - -void Translator::S_MUL_I32(const GcnInst& inst) { - SetDst(inst.dst[0], ir.IMul(GetSrc(inst.src[0]), GetSrc(inst.src[1]))); -} - -void Translator::S_CMP(ConditionOp cond, bool is_signed, const GcnInst& inst) { - const IR::U32 lhs = GetSrc(inst.src[0]); - const IR::U32 rhs = GetSrc(inst.src[1]); - const IR::U1 result = [&] { - switch (cond) { - case ConditionOp::EQ: - return ir.IEqual(lhs, rhs); - case ConditionOp::LG: - return ir.INotEqual(lhs, rhs); - case ConditionOp::GT: - return ir.IGreaterThan(lhs, rhs, is_signed); - case ConditionOp::GE: - return ir.IGreaterThanEqual(lhs, rhs, is_signed); - case ConditionOp::LT: - return ir.ILessThan(lhs, rhs, is_signed); - case ConditionOp::LE: - return ir.ILessThanEqual(lhs, rhs, is_signed); - default: - UNREACHABLE(); - } - }(); - ir.SetScc(result); -} - -void Translator::S_CMPK(ConditionOp cond, bool is_signed, const GcnInst& inst) { - const s32 simm16 = inst.control.sopk.simm; - const IR::U32 lhs = GetSrc(inst.dst[0]); - const IR::U32 rhs = ir.Imm32(simm16); - const IR::U1 result = [&] { - switch (cond) { - case ConditionOp::EQ: - return ir.IEqual(lhs, rhs); - case ConditionOp::LG: - return ir.INotEqual(lhs, rhs); - case ConditionOp::GT: - return ir.IGreaterThan(lhs, rhs, is_signed); - case ConditionOp::GE: - return ir.IGreaterThanEqual(lhs, rhs, is_signed); - case ConditionOp::LT: - return ir.ILessThan(lhs, rhs, is_signed); - case ConditionOp::LE: - return ir.ILessThanEqual(lhs, rhs, is_signed); - default: - UNREACHABLE(); - } - }(); - ir.SetScc(result); -} - -void Translator::S_AND_SAVEEXEC_B64(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()}; - const IR::U1 src = [&] { - switch (inst.src[0].field) { - case OperandField::VccLo: - return ir.GetVcc(); - case OperandField::ScalarGPR: - return ir.GetThreadBitScalarReg(IR::ScalarReg(inst.src[0].code)); - default: - UNREACHABLE(); - } - }(); - - switch (inst.dst[0].field) { - case OperandField::ScalarGPR: - ir.SetThreadBitScalarReg(IR::ScalarReg(inst.dst[0].code), exec); - break; - case OperandField::VccLo: - ir.SetVcc(exec); - break; - default: - UNREACHABLE(); - } - - // Update EXEC. - const IR::U1 result = ir.LogicalAnd(exec, src); - ir.SetExec(result); - ir.SetScc(result); -} - -void Translator::S_MOV_B64(const GcnInst& inst) { - const IR::U1 src = [&] { - switch (inst.src[0].field) { - case OperandField::VccLo: - return ir.GetVcc(); - case OperandField::ExecLo: - return ir.GetExec(); - case OperandField::ScalarGPR: - return ir.GetThreadBitScalarReg(IR::ScalarReg(inst.src[0].code)); - case OperandField::ConstZero: - return ir.Imm1(false); - default: - UNREACHABLE(); - } - }(); - switch (inst.dst[0].field) { - case OperandField::ScalarGPR: - ir.SetThreadBitScalarReg(IR::ScalarReg(inst.dst[0].code), src); - break; - case OperandField::ExecLo: - ir.SetExec(src); - break; - case OperandField::VccLo: - ir.SetVcc(src); - break; - default: - UNREACHABLE(); - } -} - -void Translator::S_OR_B64(NegateMode negate, bool is_xor, const GcnInst& inst) { - const auto get_src = [&](const InstOperand& operand) { - switch (operand.field) { - case OperandField::ExecLo: - return ir.GetExec(); - case OperandField::VccLo: - return ir.GetVcc(); - case OperandField::ScalarGPR: - return ir.GetThreadBitScalarReg(IR::ScalarReg(operand.code)); - default: - UNREACHABLE(); - } - }; - - const IR::U1 src0{get_src(inst.src[0])}; - IR::U1 src1{get_src(inst.src[1])}; - if (negate == NegateMode::Src1) { - src1 = ir.LogicalNot(src1); - } - IR::U1 result = is_xor ? ir.LogicalXor(src0, src1) : ir.LogicalOr(src0, src1); - if (negate == NegateMode::Result) { - result = ir.LogicalNot(result); - } - ir.SetScc(result); - switch (inst.dst[0].field) { - case OperandField::VccLo: - ir.SetVcc(result); - break; - case OperandField::ScalarGPR: - ir.SetThreadBitScalarReg(IR::ScalarReg(inst.dst[0].code), result); - break; - default: - UNREACHABLE(); - } -} - -void Translator::S_AND_B64(NegateMode negate, const GcnInst& inst) { - const auto get_src = [&](const InstOperand& operand) { - switch (operand.field) { - case OperandField::VccLo: - return ir.GetVcc(); - case OperandField::ExecLo: - return ir.GetExec(); - case OperandField::ScalarGPR: - return ir.GetThreadBitScalarReg(IR::ScalarReg(operand.code)); - default: - UNREACHABLE(); - } - }; - const IR::U1 src0{get_src(inst.src[0])}; - IR::U1 src1{get_src(inst.src[1])}; - if (negate == NegateMode::Src1) { - src1 = ir.LogicalNot(src1); - } - IR::U1 result = ir.LogicalAnd(src0, src1); - if (negate == NegateMode::Result) { - result = ir.LogicalNot(result); - } - ir.SetScc(result); - switch (inst.dst[0].field) { - case OperandField::VccLo: - ir.SetVcc(result); - break; - case OperandField::ScalarGPR: - ir.SetThreadBitScalarReg(IR::ScalarReg(inst.dst[0].code), result); - break; - case OperandField::ExecLo: - ir.SetExec(result); - break; - default: - UNREACHABLE(); - } +void Translator::S_SUB_U32(const GcnInst& inst) { + const IR::U32 src0{GetSrc(inst.src[0])}; + const IR::U32 src1{GetSrc(inst.src[1])}; + SetDst(inst.dst[0], ir.ISub(src0, src1)); + // TODO: Carry out + ir.SetScc(ir.Imm1(false)); } void Translator::S_ADD_I32(const GcnInst& inst) { @@ -389,50 +199,27 @@ void Translator::S_ADD_I32(const GcnInst& inst) { // TODO: Overflow flag } -void Translator::S_AND_B32(NegateMode negate, const GcnInst& inst) { - const IR::U32 src0{GetSrc(inst.src[0])}; - IR::U32 src1{GetSrc(inst.src[1])}; - if (negate == NegateMode::Src1) { - src1 = ir.BitwiseNot(src1); - } - IR::U32 result{ir.BitwiseAnd(src0, src1)}; - if (negate == NegateMode::Result) { - result = ir.BitwiseNot(result); - } - SetDst(inst.dst[0], result); - ir.SetScc(ir.INotEqual(result, ir.Imm32(0))); -} - -void Translator::S_ASHR_I32(const GcnInst& inst) { +void Translator::S_ADDC_U32(const GcnInst& inst) { const IR::U32 src0{GetSrc(inst.src[0])}; const IR::U32 src1{GetSrc(inst.src[1])}; - const IR::U32 result{ir.ShiftRightArithmetic(src0, src1)}; - SetDst(inst.dst[0], result); - ir.SetScc(ir.INotEqual(result, ir.Imm32(0))); + const IR::U32 carry{ir.Select(ir.GetScc(), ir.Imm32(1U), ir.Imm32(0U))}; + SetDst(inst.dst[0], ir.IAdd(ir.IAdd(src0, src1), carry)); } -void Translator::S_OR_B32(const GcnInst& inst) { +void Translator::S_MIN_U32(bool is_signed, const GcnInst& inst) { const IR::U32 src0{GetSrc(inst.src[0])}; const IR::U32 src1{GetSrc(inst.src[1])}; - const IR::U32 result{ir.BitwiseOr(src0, src1)}; + const IR::U32 result = ir.IMin(src0, src1, is_signed); SetDst(inst.dst[0], result); - ir.SetScc(ir.INotEqual(result, ir.Imm32(0))); + ir.SetScc(ir.IEqual(result, src0)); } -void Translator::S_XOR_B32(const GcnInst& inst) { +void Translator::S_MAX_U32(bool is_signed, const GcnInst& inst) { const IR::U32 src0{GetSrc(inst.src[0])}; const IR::U32 src1{GetSrc(inst.src[1])}; - const IR::U32 result{ir.BitwiseXor(src0, src1)}; + const IR::U32 result = ir.IMax(src0, src1, is_signed); SetDst(inst.dst[0], result); - ir.SetScc(ir.INotEqual(result, ir.Imm32(0))); -} - -void Translator::S_LSHR_B32(const GcnInst& inst) { - const IR::U32 src0{GetSrc(inst.src[0])}; - const IR::U32 src1{GetSrc(inst.src[1])}; - const IR::U32 result{ir.ShiftRightLogical(src0, src1)}; - SetDst(inst.dst[0], result); - ir.SetScc(ir.INotEqual(result, ir.Imm32(0))); + ir.SetScc(ir.IEqual(result, src0)); } void Translator::S_CSELECT_B32(const GcnInst& inst) { @@ -471,12 +258,112 @@ void Translator::S_CSELECT_B64(const GcnInst& inst) { } } -void Translator::S_BFE_U32(const GcnInst& inst) { +void Translator::S_AND_B32(NegateMode negate, const GcnInst& inst) { + const IR::U32 src0{GetSrc(inst.src[0])}; + IR::U32 src1{GetSrc(inst.src[1])}; + if (negate == NegateMode::Src1) { + src1 = ir.BitwiseNot(src1); + } + IR::U32 result{ir.BitwiseAnd(src0, src1)}; + if (negate == NegateMode::Result) { + result = ir.BitwiseNot(result); + } + SetDst(inst.dst[0], result); + ir.SetScc(ir.INotEqual(result, ir.Imm32(0))); +} + +void Translator::S_AND_B64(NegateMode negate, const GcnInst& inst) { + const auto get_src = [&](const InstOperand& operand) { + switch (operand.field) { + case OperandField::VccLo: + return ir.GetVcc(); + case OperandField::ExecLo: + return ir.GetExec(); + case OperandField::ScalarGPR: + return ir.GetThreadBitScalarReg(IR::ScalarReg(operand.code)); + case OperandField::ConstZero: + return ir.Imm1(false); + case OperandField::SignedConstIntNeg: + ASSERT_MSG(-s32(operand.code) + SignedConstIntNegMin - 1 == -1, + "SignedConstIntNeg must be -1"); + return ir.Imm1(true); + default: + UNREACHABLE(); + } + }; + const IR::U1 src0{get_src(inst.src[0])}; + IR::U1 src1{get_src(inst.src[1])}; + if (negate == NegateMode::Src1) { + src1 = ir.LogicalNot(src1); + } + IR::U1 result = ir.LogicalAnd(src0, src1); + if (negate == NegateMode::Result) { + result = ir.LogicalNot(result); + } + ir.SetScc(result); + switch (inst.dst[0].field) { + case OperandField::VccLo: + ir.SetVcc(result); + break; + case OperandField::ScalarGPR: + ir.SetThreadBitScalarReg(IR::ScalarReg(inst.dst[0].code), result); + break; + case OperandField::ExecLo: + ir.SetExec(result); + break; + default: + UNREACHABLE(); + } +} + +void Translator::S_OR_B32(const GcnInst& inst) { const IR::U32 src0{GetSrc(inst.src[0])}; const IR::U32 src1{GetSrc(inst.src[1])}; - const IR::U32 offset{ir.BitwiseAnd(src1, ir.Imm32(0x1F))}; - const IR::U32 count{ir.BitFieldExtract(src1, ir.Imm32(16), ir.Imm32(7))}; - const IR::U32 result{ir.BitFieldExtract(src0, offset, count)}; + const IR::U32 result{ir.BitwiseOr(src0, src1)}; + SetDst(inst.dst[0], result); + ir.SetScc(ir.INotEqual(result, ir.Imm32(0))); +} + +void Translator::S_OR_B64(NegateMode negate, bool is_xor, const GcnInst& inst) { + const auto get_src = [&](const InstOperand& operand) { + switch (operand.field) { + case OperandField::ExecLo: + return ir.GetExec(); + case OperandField::VccLo: + return ir.GetVcc(); + case OperandField::ScalarGPR: + return ir.GetThreadBitScalarReg(IR::ScalarReg(operand.code)); + default: + UNREACHABLE(); + } + }; + + const IR::U1 src0{get_src(inst.src[0])}; + IR::U1 src1{get_src(inst.src[1])}; + if (negate == NegateMode::Src1) { + src1 = ir.LogicalNot(src1); + } + IR::U1 result = is_xor ? ir.LogicalXor(src0, src1) : ir.LogicalOr(src0, src1); + if (negate == NegateMode::Result) { + result = ir.LogicalNot(result); + } + ir.SetScc(result); + switch (inst.dst[0].field) { + case OperandField::VccLo: + ir.SetVcc(result); + break; + case OperandField::ScalarGPR: + ir.SetThreadBitScalarReg(IR::ScalarReg(inst.dst[0].code), result); + break; + default: + UNREACHABLE(); + } +} + +void Translator::S_XOR_B32(const GcnInst& inst) { + const IR::U32 src0{GetSrc(inst.src[0])}; + const IR::U32 src1{GetSrc(inst.src[1])}; + const IR::U32 result{ir.BitwiseXor(src0, src1)}; SetDst(inst.dst[0], result); ir.SetScc(ir.INotEqual(result, ir.Imm32(0))); } @@ -489,6 +376,22 @@ void Translator::S_LSHL_B32(const GcnInst& inst) { ir.SetScc(ir.INotEqual(result, ir.Imm32(0))); } +void Translator::S_LSHR_B32(const GcnInst& inst) { + const IR::U32 src0{GetSrc(inst.src[0])}; + const IR::U32 src1{GetSrc(inst.src[1])}; + const IR::U32 result{ir.ShiftRightLogical(src0, src1)}; + SetDst(inst.dst[0], result); + ir.SetScc(ir.INotEqual(result, ir.Imm32(0))); +} + +void Translator::S_ASHR_I32(const GcnInst& inst) { + const IR::U32 src0{GetSrc(inst.src[0])}; + const IR::U32 src1{GetSrc(inst.src[1])}; + const IR::U32 result{ir.ShiftRightArithmetic(src0, src1)}; + SetDst(inst.dst[0], result); + ir.SetScc(ir.INotEqual(result, ir.Imm32(0))); +} + void Translator::S_BFM_B32(const GcnInst& inst) { const IR::U32 src0{ir.BitwiseAnd(GetSrc(inst.src[0]), ir.Imm32(0x1F))}; const IR::U32 src1{ir.BitwiseAnd(GetSrc(inst.src[1]), ir.Imm32(0x1F))}; @@ -496,6 +399,110 @@ void Translator::S_BFM_B32(const GcnInst& inst) { SetDst(inst.dst[0], ir.ShiftLeftLogical(mask, src1)); } +void Translator::S_MUL_I32(const GcnInst& inst) { + SetDst(inst.dst[0], ir.IMul(GetSrc(inst.src[0]), GetSrc(inst.src[1]))); +} + +void Translator::S_BFE_U32(const GcnInst& inst) { + const IR::U32 src0{GetSrc(inst.src[0])}; + const IR::U32 src1{GetSrc(inst.src[1])}; + const IR::U32 offset{ir.BitwiseAnd(src1, ir.Imm32(0x1F))}; + const IR::U32 count{ir.BitFieldExtract(src1, ir.Imm32(16), ir.Imm32(7))}; + const IR::U32 result{ir.BitFieldExtract(src0, offset, count)}; + SetDst(inst.dst[0], result); + ir.SetScc(ir.INotEqual(result, ir.Imm32(0))); +} + +void Translator::S_ABSDIFF_I32(const GcnInst& inst) { + const IR::U32 src0{GetSrc(inst.src[0])}; + const IR::U32 src1{GetSrc(inst.src[1])}; + const IR::U32 result{ir.IAbs(ir.ISub(src0, src1))}; + SetDst(inst.dst[0], result); + ir.SetScc(ir.INotEqual(result, ir.Imm32(0))); +} + +// SOPK + +void Translator::S_MOVK(const GcnInst& inst) { + const auto simm16 = inst.control.sopk.simm; + if (simm16 & (1 << 15)) { + // TODO: need to verify the case of imm sign extension + UNREACHABLE(); + } + SetDst(inst.dst[0], ir.Imm32(simm16)); +} + +void Translator::S_CMPK(ConditionOp cond, bool is_signed, const GcnInst& inst) { + const s32 simm16 = inst.control.sopk.simm; + const IR::U32 lhs = GetSrc(inst.dst[0]); + const IR::U32 rhs = ir.Imm32(simm16); + const IR::U1 result = [&] { + switch (cond) { + case ConditionOp::EQ: + return ir.IEqual(lhs, rhs); + case ConditionOp::LG: + return ir.INotEqual(lhs, rhs); + case ConditionOp::GT: + return ir.IGreaterThan(lhs, rhs, is_signed); + case ConditionOp::GE: + return ir.IGreaterThanEqual(lhs, rhs, is_signed); + case ConditionOp::LT: + return ir.ILessThan(lhs, rhs, is_signed); + case ConditionOp::LE: + return ir.ILessThanEqual(lhs, rhs, is_signed); + default: + UNREACHABLE(); + } + }(); + ir.SetScc(result); +} + +void Translator::S_ADDK_I32(const GcnInst& inst) { + const s32 simm16 = inst.control.sopk.simm; + SetDst(inst.dst[0], ir.IAdd(GetSrc(inst.dst[0]), ir.Imm32(simm16))); +} + +void Translator::S_MULK_I32(const GcnInst& inst) { + const s32 simm16 = inst.control.sopk.simm; + SetDst(inst.dst[0], ir.IMul(GetSrc(inst.dst[0]), ir.Imm32(simm16))); +} + +// SOP1 + +void Translator::S_MOV(const GcnInst& inst) { + SetDst(inst.dst[0], GetSrc(inst.src[0])); +} + +void Translator::S_MOV_B64(const GcnInst& inst) { + const IR::U1 src = [&] { + switch (inst.src[0].field) { + case OperandField::VccLo: + return ir.GetVcc(); + case OperandField::ExecLo: + return ir.GetExec(); + case OperandField::ScalarGPR: + return ir.GetThreadBitScalarReg(IR::ScalarReg(inst.src[0].code)); + case OperandField::ConstZero: + return ir.Imm1(false); + default: + UNREACHABLE(); + } + }(); + switch (inst.dst[0].field) { + case OperandField::ScalarGPR: + ir.SetThreadBitScalarReg(IR::ScalarReg(inst.dst[0].code), src); + break; + case OperandField::ExecLo: + ir.SetExec(src); + break; + case OperandField::VccLo: + ir.SetVcc(src); + break; + default: + UNREACHABLE(); + } +} + void Translator::S_NOT_B64(const GcnInst& inst) { const auto get_src = [&](const InstOperand& operand) { switch (operand.field) { @@ -505,6 +512,8 @@ void Translator::S_NOT_B64(const GcnInst& inst) { return ir.GetExec(); case OperandField::ScalarGPR: return ir.GetThreadBitScalarReg(IR::ScalarReg(operand.code)); + case OperandField::ConstZero: + return ir.Imm1(false); default: UNREACHABLE(); } @@ -519,6 +528,9 @@ void Translator::S_NOT_B64(const GcnInst& inst) { case OperandField::ScalarGPR: ir.SetThreadBitScalarReg(IR::ScalarReg(inst.dst[0].code), result); break; + case OperandField::ExecLo: + ir.SetExec(result); + break; default: UNREACHABLE(); } @@ -528,22 +540,6 @@ void Translator::S_BREV_B32(const GcnInst& inst) { SetDst(inst.dst[0], ir.BitReverse(GetSrc(inst.src[0]))); } -void Translator::S_ADD_U32(const GcnInst& inst) { - const IR::U32 src0{GetSrc(inst.src[0])}; - const IR::U32 src1{GetSrc(inst.src[1])}; - SetDst(inst.dst[0], ir.IAdd(src0, src1)); - // TODO: Carry out - ir.SetScc(ir.Imm1(false)); -} - -void Translator::S_SUB_U32(const GcnInst& inst) { - const IR::U32 src0{GetSrc(inst.src[0])}; - const IR::U32 src1{GetSrc(inst.src[1])}; - SetDst(inst.dst[0], ir.ISub(src0, src1)); - // TODO: Carry out - ir.SetScc(ir.Imm1(false)); -} - void Translator::S_GETPC_B64(u32 pc, const GcnInst& inst) { // This only really exists to let resource tracking pass know // there is an inline cbuf. @@ -552,35 +548,63 @@ void Translator::S_GETPC_B64(u32 pc, const GcnInst& inst) { ir.SetScalarReg(dst + 1, ir.Imm32(0)); } -void Translator::S_ADDC_U32(const GcnInst& inst) { - const IR::U32 src0{GetSrc(inst.src[0])}; - const IR::U32 src1{GetSrc(inst.src[1])}; - const IR::U32 carry{ir.Select(ir.GetScc(), ir.Imm32(1U), ir.Imm32(0U))}; - SetDst(inst.dst[0], ir.IAdd(ir.IAdd(src0, src1), carry)); +void Translator::S_AND_SAVEEXEC_B64(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()}; + const IR::U1 src = [&] { + switch (inst.src[0].field) { + case OperandField::VccLo: + return ir.GetVcc(); + case OperandField::ScalarGPR: + return ir.GetThreadBitScalarReg(IR::ScalarReg(inst.src[0].code)); + default: + UNREACHABLE(); + } + }(); + + switch (inst.dst[0].field) { + case OperandField::ScalarGPR: + ir.SetThreadBitScalarReg(IR::ScalarReg(inst.dst[0].code), exec); + break; + case OperandField::VccLo: + ir.SetVcc(exec); + break; + default: + UNREACHABLE(); + } + + // Update EXEC. + const IR::U1 result = ir.LogicalAnd(exec, src); + ir.SetExec(result); + ir.SetScc(result); } -void Translator::S_MAX_U32(bool is_signed, const GcnInst& inst) { - const IR::U32 src0{GetSrc(inst.src[0])}; - const IR::U32 src1{GetSrc(inst.src[1])}; - const IR::U32 result = ir.IMax(src0, src1, is_signed); - SetDst(inst.dst[0], result); - ir.SetScc(ir.IEqual(result, src0)); -} +// SOPC -void Translator::S_MIN_U32(bool is_signed, const GcnInst& inst) { - const IR::U32 src0{GetSrc(inst.src[0])}; - const IR::U32 src1{GetSrc(inst.src[1])}; - const IR::U32 result = ir.IMin(src0, src1, is_signed); - SetDst(inst.dst[0], result); - ir.SetScc(ir.IEqual(result, src0)); -} - -void Translator::S_ABSDIFF_I32(const GcnInst& inst) { - const IR::U32 src0{GetSrc(inst.src[0])}; - const IR::U32 src1{GetSrc(inst.src[1])}; - const IR::U32 result{ir.IAbs(ir.ISub(src0, src1))}; - SetDst(inst.dst[0], result); - ir.SetScc(ir.INotEqual(result, ir.Imm32(0))); +void Translator::S_CMP(ConditionOp cond, bool is_signed, const GcnInst& inst) { + const IR::U32 lhs = GetSrc(inst.src[0]); + const IR::U32 rhs = GetSrc(inst.src[1]); + const IR::U1 result = [&] { + switch (cond) { + case ConditionOp::EQ: + return ir.IEqual(lhs, rhs); + case ConditionOp::LG: + return ir.INotEqual(lhs, rhs); + case ConditionOp::GT: + return ir.IGreaterThan(lhs, rhs, is_signed); + case ConditionOp::GE: + return ir.IGreaterThanEqual(lhs, rhs, is_signed); + case ConditionOp::LT: + return ir.ILessThan(lhs, rhs, is_signed); + case ConditionOp::LE: + return ir.ILessThanEqual(lhs, rhs, is_signed); + default: + UNREACHABLE(); + } + }(); + ir.SetScc(result); } } // namespace Shader::Gcn diff --git a/src/shader_recompiler/frontend/translate/scalar_memory.cpp b/src/shader_recompiler/frontend/translate/scalar_memory.cpp index 29f2acc27..a6f8cafd7 100644 --- a/src/shader_recompiler/frontend/translate/scalar_memory.cpp +++ b/src/shader_recompiler/frontend/translate/scalar_memory.cpp @@ -9,6 +9,7 @@ static constexpr u32 SQ_SRC_LITERAL = 0xFF; void Translator::EmitScalarMemory(const GcnInst& inst) { switch (inst.opcode) { + // SMRD case Opcode::S_LOAD_DWORDX4: return S_LOAD_DWORD(4, inst); case Opcode::S_LOAD_DWORDX8: @@ -30,6 +31,8 @@ void Translator::EmitScalarMemory(const GcnInst& inst) { } } +// SMRD + void Translator::S_LOAD_DWORD(int num_dwords, const GcnInst& inst) { const auto& smrd = inst.control.smrd; const u32 dword_offset = [&] -> u32 { diff --git a/src/shader_recompiler/frontend/translate/translate.cpp b/src/shader_recompiler/frontend/translate/translate.cpp index 07295f5b3..cfef5858a 100644 --- a/src/shader_recompiler/frontend/translate/translate.cpp +++ b/src/shader_recompiler/frontend/translate/translate.cpp @@ -174,7 +174,7 @@ T Translator::GetSrc(const InstOperand& operand) { value = ir.IAbs(value); } if (operand.input_modifier.neg) { - UNREACHABLE(); + value = ir.INeg(value); } } return value; @@ -281,12 +281,15 @@ template IR::F64 Translator::GetSrc64(const InstOperand&); void Translator::SetDst(const InstOperand& operand, const IR::U32F32& value) { IR::U32F32 result = value; - if (operand.output_modifier.multiplier != 0.f) { - result = ir.FPMul(result, ir.Imm32(operand.output_modifier.multiplier)); - } - if (operand.output_modifier.clamp) { - result = ir.FPSaturate(value); + if (value.Type() == IR::Type::F32) { + if (operand.output_modifier.multiplier != 0.f) { + result = ir.FPMul(result, ir.Imm32(operand.output_modifier.multiplier)); + } + if (operand.output_modifier.clamp) { + result = ir.FPSaturate(value); + } } + switch (operand.field) { case OperandField::ScalarGPR: return ir.SetScalarReg(IR::ScalarReg(operand.code), result); diff --git a/src/shader_recompiler/frontend/translate/translate.h b/src/shader_recompiler/frontend/translate/translate.h index d2053b765..7559b8533 100644 --- a/src/shader_recompiler/frontend/translate/translate.h +++ b/src/shader_recompiler/frontend/translate/translate.h @@ -61,177 +61,203 @@ public: // Instruction categories void EmitPrologue(); void EmitFetch(const GcnInst& inst); - void EmitDataShare(const GcnInst& inst); - void EmitVectorInterpolation(const GcnInst& inst); - void EmitScalarMemory(const GcnInst& inst); - void EmitVectorMemory(const GcnInst& inst); void EmitExport(const GcnInst& inst); void EmitFlowControl(u32 pc, const GcnInst& inst); void EmitScalarAlu(const GcnInst& inst); + void EmitScalarMemory(const GcnInst& inst); void EmitVectorAlu(const GcnInst& inst); + void EmitVectorInterpolation(const GcnInst& inst); + void EmitDataShare(const GcnInst& inst); + void EmitVectorMemory(const GcnInst& inst); // Instruction encodings void EmitSOPC(const GcnInst& inst); void EmitSOPK(const GcnInst& inst); // Scalar ALU - void S_MOVK(const GcnInst& inst); - void S_MOV(const GcnInst& inst); - void S_MUL_I32(const GcnInst& inst); - void S_CMP(ConditionOp cond, bool is_signed, const GcnInst& inst); - void S_AND_SAVEEXEC_B64(const GcnInst& inst); - void S_MOV_B64(const GcnInst& inst); - void S_OR_B64(NegateMode negate, bool is_xor, const GcnInst& inst); - void S_AND_B64(NegateMode negate, const GcnInst& inst); - void S_ADD_I32(const GcnInst& inst); - void S_AND_B32(NegateMode negate, const GcnInst& inst); - void S_ASHR_I32(const GcnInst& inst); - void S_OR_B32(const GcnInst& inst); - void S_XOR_B32(const GcnInst& inst); - void S_LSHR_B32(const GcnInst& inst); - void S_CSELECT_B32(const GcnInst& inst); - void S_CSELECT_B64(const GcnInst& inst); - void S_BFE_U32(const GcnInst& inst); - void S_LSHL_B32(const GcnInst& inst); - void S_BFM_B32(const GcnInst& inst); - void S_NOT_B64(const GcnInst& inst); - void S_BREV_B32(const GcnInst& inst); + // SOP2 void S_ADD_U32(const GcnInst& inst); void S_SUB_U32(const GcnInst& inst); - void S_GETPC_B64(u32 pc, const GcnInst& inst); + void S_ADD_I32(const GcnInst& inst); void S_ADDC_U32(const GcnInst& inst); - void S_MULK_I32(const GcnInst& inst); - void S_ADDK_I32(const GcnInst& inst); - void S_MAX_U32(bool is_signed, const GcnInst& inst); void S_MIN_U32(bool is_signed, const GcnInst& inst); + void S_MAX_U32(bool is_signed, const GcnInst& inst); + void S_CSELECT_B32(const GcnInst& inst); + void S_CSELECT_B64(const GcnInst& inst); + void S_AND_B32(NegateMode negate, const GcnInst& inst); + void S_AND_B64(NegateMode negate, const GcnInst& inst); + void S_OR_B32(const GcnInst& inst); + void S_OR_B64(NegateMode negate, bool is_xor, const GcnInst& inst); + void S_XOR_B32(const GcnInst& inst); + void S_LSHL_B32(const GcnInst& inst); + void S_LSHR_B32(const GcnInst& inst); + void S_ASHR_I32(const GcnInst& inst); + void S_BFM_B32(const GcnInst& inst); + void S_MUL_I32(const GcnInst& inst); + void S_BFE_U32(const GcnInst& inst); void S_ABSDIFF_I32(const GcnInst& inst); + + // SOPK + void S_MOVK(const GcnInst& inst); void S_CMPK(ConditionOp cond, bool is_signed, const GcnInst& inst); + void S_ADDK_I32(const GcnInst& inst); + void S_MULK_I32(const GcnInst& inst); + + // SOP1 + void S_MOV(const GcnInst& inst); + void S_MOV_B64(const GcnInst& inst); + void S_NOT_B64(const GcnInst& inst); + void S_BREV_B32(const GcnInst& inst); + void S_GETPC_B64(u32 pc, const GcnInst& inst); + void S_AND_SAVEEXEC_B64(const GcnInst& inst); + + // SOPC + void S_CMP(ConditionOp cond, bool is_signed, const GcnInst& inst); + + // SOPP + void S_BARRIER(); // Scalar Memory + // SMRD void S_LOAD_DWORD(int num_dwords, const GcnInst& inst); void S_BUFFER_LOAD_DWORD(int num_dwords, const GcnInst& inst); // Vector ALU - void V_MOV(const GcnInst& inst); - void V_SAD(const GcnInst& inst); - void V_MAC_F32(const GcnInst& inst); - void V_CVT_PKRTZ_F16_F32(const GcnInst& inst); - void V_CVT_F32_F16(const GcnInst& inst); - void V_CVT_F16_F32(const GcnInst& inst); - void V_MUL_F32(const GcnInst& inst); + // VOP2 void V_CNDMASK_B32(const GcnInst& inst); - void V_OR_B32(bool is_xor, const GcnInst& inst); - void V_AND_B32(const GcnInst& inst); - void V_LSHLREV_B32(const GcnInst& inst); + void V_READLANE_B32(const GcnInst& inst); + void V_WRITELANE_B32(const GcnInst& inst); + void V_ADD_F32(const GcnInst& inst); + void V_SUB_F32(const GcnInst& inst); + void V_SUBREV_F32(const GcnInst& inst); + void V_MUL_F32(const GcnInst& inst); + void V_MUL_I32_I24(const GcnInst& inst); + void V_MIN_F32(const GcnInst& inst, bool is_legacy = false); + void V_MAX_F32(const GcnInst& inst, bool is_legacy = false); + void V_MIN_I32(const GcnInst& inst); + void V_MIN_U32(const GcnInst& inst); + void V_MAX_U32(bool is_signed, const GcnInst& inst); + void V_LSHR_B32(const GcnInst& inst); + void V_LSHRREV_B32(const GcnInst& inst); + void V_ASHR_I32(const GcnInst& inst); + void V_ASHRREV_I32(const GcnInst& inst); void V_LSHL_B32(const GcnInst& inst); - void V_LSHL_B64(const GcnInst& inst); + void V_LSHLREV_B32(const GcnInst& inst); + void V_AND_B32(const GcnInst& inst); + void V_OR_B32(bool is_xor, const GcnInst& inst); + void V_BFM_B32(const GcnInst& inst); + void V_MAC_F32(const GcnInst& inst); + void V_MADMK_F32(const GcnInst& inst); + void V_BCNT_U32_B32(const GcnInst& inst); + void V_MBCNT_U32_B32(bool is_low, const GcnInst& inst); void V_ADD_I32(const GcnInst& inst); + void V_SUB_I32(const GcnInst& inst); + void V_SUBREV_I32(const GcnInst& inst); void V_ADDC_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); + + // VOP1 + void V_MOV(const GcnInst& inst); + void V_READFIRSTLANE_B32(const GcnInst& inst); void V_CVT_F32_I32(const GcnInst& inst); void V_CVT_F32_U32(const GcnInst& inst); - void V_MAD_F32(const GcnInst& inst); - void V_FRACT_F32(const GcnInst& inst); - void V_ADD_F32(const GcnInst& inst); - void V_CVT_OFF_F32_I4(const GcnInst& inst); - void V_MED3_F32(const GcnInst& inst); - void V_MED3_I32(const GcnInst& inst); - void V_FLOOR_F32(const GcnInst& inst); - void V_SUB_F32(const GcnInst& inst); - void V_RCP_F32(const GcnInst& inst); - void V_FMA_F32(const GcnInst& inst); - void V_CMP_F32(ConditionOp op, bool set_exec, const GcnInst& inst); - void V_MAX_F32(const GcnInst& inst, bool is_legacy = false); - void V_MAX_F64(const GcnInst& inst); - void V_MAX_U32(bool is_signed, const GcnInst& inst); - void V_RSQ_F32(const GcnInst& inst); - void V_SIN_F32(const GcnInst& inst); - void V_LOG_F32(const GcnInst& inst); - void V_EXP_F32(const GcnInst& inst); - void V_SQRT_F32(const GcnInst& inst); - void V_MIN_F32(const GcnInst& inst, bool is_legacy = false); - void V_MIN3_F32(const GcnInst& inst); - void V_MIN3_I32(const GcnInst& inst); - void V_MADMK_F32(const GcnInst& inst); - void V_CUBEMA_F32(const GcnInst& inst); - void V_CUBESC_F32(const GcnInst& inst); - void V_CUBETC_F32(const GcnInst& inst); - void V_CUBEID_F32(const GcnInst& inst); void V_CVT_U32_F32(const GcnInst& inst); - void V_SUBREV_F32(const GcnInst& inst); - void V_SUBREV_I32(const GcnInst& inst); - void V_MAD_U64_U32(const GcnInst& inst); - void V_CMP_U32(ConditionOp op, bool is_signed, bool set_exec, const GcnInst& inst); - void V_LSHRREV_B32(const GcnInst& inst); - void V_MUL_HI_U32(bool is_signed, const GcnInst& inst); - void V_SAD_U32(const GcnInst& inst); - void V_BFE_U32(bool is_signed, const GcnInst& inst); - void V_MAD_I32_I24(const GcnInst& inst, bool is_signed = true); - void V_MUL_I32_I24(const GcnInst& inst); - void V_SUB_I32(const GcnInst& inst); - void V_LSHR_B32(const GcnInst& inst); - void V_ASHRREV_I32(const GcnInst& inst); - void V_ASHR_I32(const GcnInst& inst); - void V_MAD_U32_U24(const GcnInst& inst); - void V_RNDNE_F32(const GcnInst& inst); - void V_BCNT_U32_B32(const GcnInst& inst); - void V_COS_F32(const GcnInst& inst); - void V_MAX3_F32(const GcnInst& inst); - void V_MAX3_U32(bool is_signed, const GcnInst& inst); void V_CVT_I32_F32(const GcnInst& inst); - void V_MIN_I32(const GcnInst& inst); - void V_MUL_LO_U32(const GcnInst& inst); + void V_CVT_F16_F32(const GcnInst& inst); + void V_CVT_F32_F16(const GcnInst& inst); + void V_CVT_FLR_I32_F32(const GcnInst& inst); + void V_CVT_OFF_F32_I4(const GcnInst& inst); + void V_CVT_F32_UBYTE(u32 index, const GcnInst& inst); + void V_FRACT_F32(const GcnInst& inst); void V_TRUNC_F32(const GcnInst& inst); void V_CEIL_F32(const GcnInst& inst); - void V_MIN_U32(const GcnInst& inst); - void V_CMP_NE_U64(const GcnInst& inst); - void V_BFI_B32(const GcnInst& inst); + void V_RNDNE_F32(const GcnInst& inst); + void V_FLOOR_F32(const GcnInst& inst); + void V_EXP_F32(const GcnInst& inst); + void V_LOG_F32(const GcnInst& inst); + void V_RCP_F32(const GcnInst& inst); + void V_RCP_F64(const GcnInst& inst); + void V_RSQ_F32(const GcnInst& inst); + void V_SQRT_F32(const GcnInst& inst); + void V_SIN_F32(const GcnInst& inst); + void V_COS_F32(const GcnInst& inst); void V_NOT_B32(const GcnInst& inst); - void V_CVT_F32_UBYTE(u32 index, const GcnInst& inst); void V_BFREV_B32(const GcnInst& inst); - void V_LDEXP_F32(const GcnInst& inst); - void V_CVT_FLR_I32_F32(const GcnInst& inst); - void V_CMP_CLASS_F32(const GcnInst& inst); - void V_FFBL_B32(const GcnInst& inst); - void V_MBCNT_U32_B32(bool is_low, const GcnInst& inst); - void V_BFM_B32(const GcnInst& inst); void V_FFBH_U32(const GcnInst& inst); - void V_MOVRELS_B32(const GcnInst& inst); + void V_FFBL_B32(const GcnInst& inst); void V_MOVRELD_B32(const GcnInst& inst); + void V_MOVRELS_B32(const GcnInst& inst); void V_MOVRELSD_B32(const GcnInst& inst); - // Vector Memory + // VOPC + void V_CMP_F32(ConditionOp op, bool set_exec, const GcnInst& inst); + void V_CMP_U32(ConditionOp op, bool is_signed, bool set_exec, const GcnInst& inst); + void V_CMP_NE_U64(const GcnInst& inst); + void V_CMP_CLASS_F32(const GcnInst& inst); + + // VOP3a + void V_MAD_F32(const GcnInst& inst); + void V_MAD_I32_I24(const GcnInst& inst, bool is_signed = false); + void V_MAD_U32_U24(const GcnInst& inst); + void V_CUBEID_F32(const GcnInst& inst); + void V_CUBESC_F32(const GcnInst& inst); + void V_CUBETC_F32(const GcnInst& inst); + void V_CUBEMA_F32(const GcnInst& inst); + void V_BFE_U32(bool is_signed, const GcnInst& inst); + void V_BFI_B32(const GcnInst& inst); + void V_FMA_F32(const GcnInst& inst); + void V_FMA_F64(const GcnInst& inst); + void V_MIN3_F32(const GcnInst& inst); + void V_MIN3_I32(const GcnInst& inst); + void V_MAX3_F32(const GcnInst& inst); + void V_MAX3_U32(bool is_signed, const GcnInst& inst); + void V_MED3_F32(const GcnInst& inst); + void V_MED3_I32(const GcnInst& inst); + void V_SAD(const GcnInst& inst); + void V_SAD_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); + void V_MAX_F64(const GcnInst& inst); + void V_MUL_LO_U32(const GcnInst& inst); + void V_MUL_HI_U32(bool is_signed, const GcnInst& inst); + void V_MAD_U64_U32(const GcnInst& inst); + + // Vector interpolation + // VINTRP + void V_INTERP_P2_F32(const GcnInst& inst); + void V_INTERP_MOV_F32(const GcnInst& inst); + + // Data share + // DS + void DS_ADD_U32(const GcnInst& inst, bool rtn); + void DS_MIN_U32(const GcnInst& inst, bool is_signed, bool rtn); + void DS_MAX_U32(const GcnInst& inst, bool is_signed, bool rtn); + void DS_WRITE(int bit_size, bool is_signed, bool is_pair, bool stride64, const GcnInst& inst); + void DS_SWIZZLE_B32(const GcnInst& inst); + void DS_READ(int bit_size, bool is_signed, bool is_pair, bool stride64, const GcnInst& inst); + void DS_APPEND(const GcnInst& inst); + void DS_CONSUME(const GcnInst& inst); + + // Buffer Memory + // MUBUF / MTBUF void BUFFER_LOAD(u32 num_dwords, bool is_typed, const GcnInst& inst); void BUFFER_LOAD_FORMAT(u32 num_dwords, const GcnInst& inst); void BUFFER_STORE(u32 num_dwords, bool is_typed, const GcnInst& inst); void BUFFER_STORE_FORMAT(u32 num_dwords, const GcnInst& inst); void BUFFER_ATOMIC(AtomicOp op, const GcnInst& inst); - // Vector interpolation - void V_INTERP_P2_F32(const GcnInst& inst); - void V_INTERP_MOV_F32(const GcnInst& inst); - - // Data share - void DS_SWIZZLE_B32(const GcnInst& inst); - void DS_READ(int bit_size, bool is_signed, bool is_pair, bool stride64, const GcnInst& inst); - void DS_WRITE(int bit_size, bool is_signed, bool is_pair, bool stride64, const GcnInst& inst); - void DS_ADD_U32(const GcnInst& inst, bool rtn); - void DS_MIN_U32(const GcnInst& inst, bool is_signed, bool rtn); - void DS_MAX_U32(const GcnInst& inst, bool is_signed, bool rtn); - void V_READFIRSTLANE_B32(const GcnInst& inst); - void V_READLANE_B32(const GcnInst& inst); - void V_WRITELANE_B32(const GcnInst& inst); - void DS_APPEND(const GcnInst& inst); - void DS_CONSUME(const GcnInst& inst); - void S_BARRIER(); - + // Image Memory // MIMG + void IMAGE_LOAD(bool has_mip, const GcnInst& inst); + void IMAGE_STORE(const GcnInst& inst); void IMAGE_GET_RESINFO(const GcnInst& inst); + void IMAGE_ATOMIC(AtomicOp op, const GcnInst& inst); void IMAGE_SAMPLE(const GcnInst& inst); void IMAGE_GATHER(const GcnInst& inst); - void IMAGE_STORE(const GcnInst& inst); - void IMAGE_LOAD(bool has_mip, const GcnInst& inst); void IMAGE_GET_LOD(const GcnInst& inst); - void IMAGE_ATOMIC(AtomicOp op, const GcnInst& inst); private: template @@ -241,6 +267,7 @@ private: void SetDst(const InstOperand& operand, const IR::U32F32& value); void SetDst64(const InstOperand& operand, const IR::U64F64& value_raw); + // Vector ALU Helprers 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 82a1e3e89..f497e2606 100644 --- a/src/shader_recompiler/frontend/translate/vector_alu.cpp +++ b/src/shader_recompiler/frontend/translate/vector_alu.cpp @@ -8,55 +8,113 @@ namespace Shader::Gcn { void Translator::EmitVectorAlu(const GcnInst& inst) { switch (inst.opcode) { - case Opcode::V_LSHLREV_B32: - return V_LSHLREV_B32(inst); - case Opcode::V_LSHL_B32: - return V_LSHL_B32(inst); - case Opcode::V_LSHL_B64: - return V_LSHL_B64(inst); - case Opcode::V_BFREV_B32: - return V_BFREV_B32(inst); - case Opcode::V_BFE_U32: - return V_BFE_U32(false, inst); - case Opcode::V_BFE_I32: - return V_BFE_U32(true, inst); - case Opcode::V_BFI_B32: - return V_BFI_B32(inst); + // VOP2 + case Opcode::V_CNDMASK_B32: + return V_CNDMASK_B32(inst); + case Opcode::V_READLANE_B32: + return V_READLANE_B32(inst); + case Opcode::V_WRITELANE_B32: + return V_WRITELANE_B32(inst); + case Opcode::V_ADD_F32: + return V_ADD_F32(inst); + case Opcode::V_SUB_F32: + return V_SUB_F32(inst); + case Opcode::V_SUBREV_F32: + return V_SUBREV_F32(inst); + case Opcode::V_MAC_LEGACY_F32: + return V_MAC_F32(inst); + case Opcode::V_MUL_LEGACY_F32: + return V_MUL_F32(inst); + case Opcode::V_MUL_F32: + return V_MUL_F32(inst); + case Opcode::V_MUL_I32_I24: + return V_MUL_I32_I24(inst); + case Opcode::V_MUL_U32_U24: + return V_MUL_I32_I24(inst); + case Opcode::V_MIN_LEGACY_F32: + return V_MIN_F32(inst, true); + case Opcode::V_MAX_LEGACY_F32: + return V_MAX_F32(inst, true); + case Opcode::V_MIN_F32: + return V_MIN_F32(inst, false); + case Opcode::V_MAX_F32: + return V_MAX_F32(inst); + case Opcode::V_MIN_I32: + return V_MIN_I32(inst); + case Opcode::V_MAX_I32: + return V_MAX_U32(true, inst); + case Opcode::V_MIN_U32: + return V_MIN_U32(inst); + case Opcode::V_MAX_U32: + return V_MAX_U32(false, inst); case Opcode::V_LSHR_B32: return V_LSHR_B32(inst); - case Opcode::V_ASHRREV_I32: - return V_ASHRREV_I32(inst); - case Opcode::V_ASHR_I32: - return V_ASHR_I32(inst); case Opcode::V_LSHRREV_B32: return V_LSHRREV_B32(inst); - case Opcode::V_NOT_B32: - return V_NOT_B32(inst); + case Opcode::V_ASHR_I32: + return V_ASHR_I32(inst); + case Opcode::V_ASHRREV_I32: + return V_ASHRREV_I32(inst); + case Opcode::V_LSHL_B32: + return V_LSHL_B32(inst); + case Opcode::V_LSHLREV_B32: + return V_LSHLREV_B32(inst); case Opcode::V_AND_B32: return V_AND_B32(inst); case Opcode::V_OR_B32: return V_OR_B32(false, inst); case Opcode::V_XOR_B32: return V_OR_B32(true, inst); - case Opcode::V_FFBL_B32: - return V_FFBL_B32(inst); - - case Opcode::V_MOV_B32: - return V_MOV(inst); + case Opcode::V_BFM_B32: + return V_BFM_B32(inst); + case Opcode::V_MAC_F32: + return V_MAC_F32(inst); + case Opcode::V_MADMK_F32: + return V_MADMK_F32(inst); + case Opcode::V_MADAK_F32: + return V_FMA_F32(inst); + case Opcode::V_BCNT_U32_B32: + return V_BCNT_U32_B32(inst); + case Opcode::V_MBCNT_LO_U32_B32: + return V_MBCNT_U32_B32(true, inst); + case Opcode::V_MBCNT_HI_U32_B32: + return V_MBCNT_U32_B32(false, inst); case Opcode::V_ADD_I32: return V_ADD_I32(inst); + case Opcode::V_SUB_I32: + return V_SUB_I32(inst); + case Opcode::V_SUBREV_I32: + return V_SUBREV_I32(inst); case Opcode::V_ADDC_U32: return V_ADDC_U32(inst); + case Opcode::V_LDEXP_F32: + return V_LDEXP_F32(inst); + case Opcode::V_CVT_PKNORM_U16_F32: + return V_CVT_PKNORM_U16_F32(inst); + case Opcode::V_CVT_PKRTZ_F16_F32: + return V_CVT_PKRTZ_F16_F32(inst); + + // VOP1 + case Opcode::V_MOV_B32: + return V_MOV(inst); + case Opcode::V_READFIRSTLANE_B32: + return V_READFIRSTLANE_B32(inst); case Opcode::V_CVT_F32_I32: return V_CVT_F32_I32(inst); case Opcode::V_CVT_F32_U32: return V_CVT_F32_U32(inst); - case Opcode::V_CVT_PKRTZ_F16_F32: - return V_CVT_PKRTZ_F16_F32(inst); - case Opcode::V_CVT_F32_F16: - return V_CVT_F32_F16(inst); + case Opcode::V_CVT_U32_F32: + return V_CVT_U32_F32(inst); + case Opcode::V_CVT_I32_F32: + return V_CVT_I32_F32(inst); case Opcode::V_CVT_F16_F32: return V_CVT_F16_F32(inst); + case Opcode::V_CVT_F32_F16: + return V_CVT_F32_F16(inst); + case Opcode::V_CVT_FLR_I32_F32: + return V_CVT_FLR_I32_F32(inst); + case Opcode::V_CVT_OFF_F32_I4: + return V_CVT_OFF_F32_I4(inst); case Opcode::V_CVT_F32_UBYTE0: return V_CVT_F32_UBYTE(0, inst); case Opcode::V_CVT_F32_UBYTE1: @@ -65,34 +123,55 @@ void Translator::EmitVectorAlu(const GcnInst& inst) { return V_CVT_F32_UBYTE(2, inst); case Opcode::V_CVT_F32_UBYTE3: return V_CVT_F32_UBYTE(3, inst); - case Opcode::V_CVT_OFF_F32_I4: - return V_CVT_OFF_F32_I4(inst); - case Opcode::V_MAD_U64_U32: - return V_MAD_U64_U32(inst); - case Opcode::V_CMP_GE_I32: - return V_CMP_U32(ConditionOp::GE, true, false, inst); - case Opcode::V_CMP_EQ_I32: - return V_CMP_U32(ConditionOp::EQ, true, false, inst); - case Opcode::V_CMP_LE_I32: - return V_CMP_U32(ConditionOp::LE, true, false, inst); - case Opcode::V_CMP_NE_I32: - return V_CMP_U32(ConditionOp::LG, true, false, inst); - case Opcode::V_CMP_NE_U32: - return V_CMP_U32(ConditionOp::LG, false, false, inst); - case Opcode::V_CMP_EQ_U32: - return V_CMP_U32(ConditionOp::EQ, false, false, inst); - case Opcode::V_CMP_F_U32: - return V_CMP_U32(ConditionOp::F, false, false, inst); - case Opcode::V_CMP_LT_U32: - return V_CMP_U32(ConditionOp::LT, false, false, inst); - case Opcode::V_CMP_GT_U32: - return V_CMP_U32(ConditionOp::GT, false, false, inst); - case Opcode::V_CMP_GE_U32: - return V_CMP_U32(ConditionOp::GE, false, false, inst); - case Opcode::V_CMP_TRU_U32: - return V_CMP_U32(ConditionOp::TRU, false, false, inst); - case Opcode::V_CMP_NEQ_F32: - return V_CMP_F32(ConditionOp::LG, false, inst); + case Opcode::V_FRACT_F32: + return V_FRACT_F32(inst); + case Opcode::V_TRUNC_F32: + return V_TRUNC_F32(inst); + case Opcode::V_CEIL_F32: + return V_CEIL_F32(inst); + case Opcode::V_RNDNE_F32: + return V_RNDNE_F32(inst); + case Opcode::V_FLOOR_F32: + return V_FLOOR_F32(inst); + case Opcode::V_EXP_F32: + return V_EXP_F32(inst); + case Opcode::V_LOG_F32: + return V_LOG_F32(inst); + case Opcode::V_RCP_F32: + return V_RCP_F32(inst); + case Opcode::V_RCP_F64: + return V_RCP_F64(inst); + case Opcode::V_RCP_IFLAG_F32: + return V_RCP_F32(inst); + case Opcode::V_RSQ_CLAMP_F32: + return V_RSQ_F32(inst); + case Opcode::V_RSQ_LEGACY_F32: + return V_RSQ_F32(inst); + case Opcode::V_RSQ_F32: + return V_RSQ_F32(inst); + case Opcode::V_SQRT_F32: + return V_SQRT_F32(inst); + case Opcode::V_SIN_F32: + return V_SIN_F32(inst); + case Opcode::V_COS_F32: + return V_COS_F32(inst); + case Opcode::V_NOT_B32: + return V_NOT_B32(inst); + case Opcode::V_BFREV_B32: + return V_BFREV_B32(inst); + case Opcode::V_FFBH_U32: + return V_FFBH_U32(inst); + case Opcode::V_FFBL_B32: + return V_FFBL_B32(inst); + case Opcode::V_MOVRELD_B32: + return V_MOVRELD_B32(inst); + case Opcode::V_MOVRELS_B32: + return V_MOVRELS_B32(inst); + case Opcode::V_MOVRELSD_B32: + return V_MOVRELSD_B32(inst); + + // VOPC + // V_CMP_{OP16}_F32 case Opcode::V_CMP_F_F32: return V_CMP_F32(ConditionOp::F, false, inst); case Opcode::V_CMP_LT_F32: @@ -107,149 +186,20 @@ void Translator::EmitVectorAlu(const GcnInst& inst) { return V_CMP_F32(ConditionOp::LG, false, inst); case Opcode::V_CMP_GE_F32: return V_CMP_F32(ConditionOp::GE, false, inst); - case Opcode::V_CMP_NLE_F32: - return V_CMP_F32(ConditionOp::GT, false, inst); - case Opcode::V_CMP_NLT_F32: - return V_CMP_F32(ConditionOp::GE, false, inst); - case Opcode::V_CMP_NGT_F32: - return V_CMP_F32(ConditionOp::LE, false, inst); - case Opcode::V_CMP_NGE_F32: - return V_CMP_F32(ConditionOp::LT, false, inst); case Opcode::V_CMP_U_F32: return V_CMP_F32(ConditionOp::U, false, inst); - case Opcode::V_CNDMASK_B32: - return V_CNDMASK_B32(inst); - case Opcode::V_MAX_I32: - return V_MAX_U32(true, inst); - case Opcode::V_MAX_U32: - return V_MAX_U32(false, inst); - case Opcode::V_MIN_I32: - return V_MIN_I32(inst); - case Opcode::V_CUBEMA_F32: - return V_CUBEMA_F32(inst); - case Opcode::V_CUBESC_F32: - return V_CUBESC_F32(inst); - case Opcode::V_CUBETC_F32: - return V_CUBETC_F32(inst); - case Opcode::V_CUBEID_F32: - return V_CUBEID_F32(inst); - case Opcode::V_CVT_U32_F32: - return V_CVT_U32_F32(inst); - case Opcode::V_CVT_I32_F32: - return V_CVT_I32_F32(inst); - case Opcode::V_CVT_FLR_I32_F32: - return V_CVT_FLR_I32_F32(inst); - case Opcode::V_SUBREV_I32: - return V_SUBREV_I32(inst); - case Opcode::V_MUL_HI_U32: - return V_MUL_HI_U32(false, inst); - case Opcode::V_MUL_LO_I32: - return V_MUL_LO_U32(inst); - case Opcode::V_SAD_U32: - return V_SAD_U32(inst); - case Opcode::V_SUB_I32: - return V_SUB_I32(inst); - case Opcode::V_MAD_I32_I24: - return V_MAD_I32_I24(inst); - case Opcode::V_MUL_I32_I24: - case Opcode::V_MUL_U32_U24: - return V_MUL_I32_I24(inst); - case Opcode::V_MAD_U32_U24: - return V_MAD_U32_U24(inst); - case Opcode::V_BCNT_U32_B32: - return V_BCNT_U32_B32(inst); - case Opcode::V_MUL_LO_U32: - return V_MUL_LO_U32(inst); - case Opcode::V_MIN_U32: - return V_MIN_U32(inst); - case Opcode::V_CMP_NE_U64: - return V_CMP_NE_U64(inst); - case Opcode::V_READFIRSTLANE_B32: - return V_READFIRSTLANE_B32(inst); - case Opcode::V_READLANE_B32: - return V_READLANE_B32(inst); - case Opcode::V_WRITELANE_B32: - return V_WRITELANE_B32(inst); - - case Opcode::V_MAD_F32: - return V_MAD_F32(inst); - case Opcode::V_MAC_F32: - return V_MAC_F32(inst); - case Opcode::V_MUL_F32: - return V_MUL_F32(inst); - case Opcode::V_RCP_F32: - return V_RCP_F32(inst); - case Opcode::V_LDEXP_F32: - return V_LDEXP_F32(inst); - case Opcode::V_FRACT_F32: - return V_FRACT_F32(inst); - case Opcode::V_ADD_F32: - return V_ADD_F32(inst); - case Opcode::V_MED3_F32: - return V_MED3_F32(inst); - case Opcode::V_MED3_I32: - return V_MED3_I32(inst); - case Opcode::V_FLOOR_F32: - return V_FLOOR_F32(inst); - case Opcode::V_SUB_F32: - return V_SUB_F32(inst); - case Opcode::V_FMA_F32: - case Opcode::V_MADAK_F32: - return V_FMA_F32(inst); - case Opcode::V_MAX_F32: - return V_MAX_F32(inst); - case Opcode::V_MAX_F64: - return V_MAX_F64(inst); - case Opcode::V_RSQ_F32: - return V_RSQ_F32(inst); - case Opcode::V_SIN_F32: - return V_SIN_F32(inst); - case Opcode::V_COS_F32: - return V_COS_F32(inst); - case Opcode::V_LOG_F32: - return V_LOG_F32(inst); - case Opcode::V_EXP_F32: - return V_EXP_F32(inst); - case Opcode::V_SQRT_F32: - return V_SQRT_F32(inst); - case Opcode::V_MIN_F32: - return V_MIN_F32(inst, false); - case Opcode::V_MIN3_F32: - return V_MIN3_F32(inst); - case Opcode::V_MIN3_I32: - return V_MIN3_I32(inst); - case Opcode::V_MIN_LEGACY_F32: - return V_MIN_F32(inst, true); - case Opcode::V_MADMK_F32: - return V_MADMK_F32(inst); - case Opcode::V_SUBREV_F32: - return V_SUBREV_F32(inst); - case Opcode::V_RNDNE_F32: - return V_RNDNE_F32(inst); - case Opcode::V_MAX3_F32: - return V_MAX3_F32(inst); - case Opcode::V_MAX3_U32: - return V_MAX3_U32(false, inst); - case Opcode::V_MAX3_I32: - return V_MAX_U32(true, inst); - case Opcode::V_TRUNC_F32: - return V_TRUNC_F32(inst); - case Opcode::V_CEIL_F32: - return V_CEIL_F32(inst); - case Opcode::V_MUL_LEGACY_F32: - return V_MUL_F32(inst); - case Opcode::V_MAC_LEGACY_F32: - return V_MAC_F32(inst); - case Opcode::V_MAD_LEGACY_F32: - return V_MAD_F32(inst); - case Opcode::V_MAX_LEGACY_F32: - return V_MAX_F32(inst, true); - case Opcode::V_RSQ_LEGACY_F32: - case Opcode::V_RSQ_CLAMP_F32: - return V_RSQ_F32(inst); - case Opcode::V_RCP_IFLAG_F32: - return V_RCP_F32(inst); + case Opcode::V_CMP_NGE_F32: + return V_CMP_F32(ConditionOp::LT, false, inst); + case Opcode::V_CMP_NGT_F32: + return V_CMP_F32(ConditionOp::LE, false, inst); + case Opcode::V_CMP_NLE_F32: + return V_CMP_F32(ConditionOp::GT, false, inst); + case Opcode::V_CMP_NEQ_F32: + return V_CMP_F32(ConditionOp::LG, false, inst); + case Opcode::V_CMP_NLT_F32: + return V_CMP_F32(ConditionOp::GE, false, inst); + // V_CMPX_{OP16}_F32 case Opcode::V_CMPX_F_F32: return V_CMP_F32(ConditionOp::F, true, inst); case Opcode::V_CMPX_LT_F32: @@ -278,19 +228,50 @@ void Translator::EmitVectorAlu(const GcnInst& inst) { return V_CMP_F32(ConditionOp::GE, true, inst); case Opcode::V_CMPX_TRU_F32: return V_CMP_F32(ConditionOp::TRU, true, inst); - case Opcode::V_CMP_CLASS_F32: - return V_CMP_CLASS_F32(inst); - case Opcode::V_CMP_LE_U32: - return V_CMP_U32(ConditionOp::LE, false, false, inst); - case Opcode::V_CMP_GT_I32: - return V_CMP_U32(ConditionOp::GT, true, false, inst); + // V_CMP_{OP8}_I32 case Opcode::V_CMP_LT_I32: return V_CMP_U32(ConditionOp::LT, true, false, inst); - case Opcode::V_CMPX_GT_I32: - return V_CMP_U32(ConditionOp::GT, true, true, inst); + case Opcode::V_CMP_EQ_I32: + return V_CMP_U32(ConditionOp::EQ, true, false, inst); + case Opcode::V_CMP_LE_I32: + return V_CMP_U32(ConditionOp::LE, true, false, inst); + case Opcode::V_CMP_GT_I32: + return V_CMP_U32(ConditionOp::GT, true, false, inst); + case Opcode::V_CMP_NE_I32: + return V_CMP_U32(ConditionOp::LG, true, false, inst); + case Opcode::V_CMP_GE_I32: + return V_CMP_U32(ConditionOp::GE, true, false, inst); + + // V_CMPX_{OP8}_I32 case Opcode::V_CMPX_LT_I32: return V_CMP_U32(ConditionOp::LT, true, true, inst); + case Opcode::V_CMPX_EQ_I32: + return V_CMP_U32(ConditionOp::EQ, true, true, inst); + case Opcode::V_CMPX_GT_I32: + return V_CMP_U32(ConditionOp::GT, true, true, inst); + case Opcode::V_CMPX_LG_I32: + return V_CMP_U32(ConditionOp::LG, true, true, inst); + + // V_CMP_{OP8}_U32 + case Opcode::V_CMP_F_U32: + return V_CMP_U32(ConditionOp::F, false, false, inst); + case Opcode::V_CMP_LT_U32: + return V_CMP_U32(ConditionOp::LT, false, false, inst); + case Opcode::V_CMP_EQ_U32: + return V_CMP_U32(ConditionOp::EQ, false, false, inst); + case Opcode::V_CMP_LE_U32: + return V_CMP_U32(ConditionOp::LE, false, false, inst); + case Opcode::V_CMP_GT_U32: + return V_CMP_U32(ConditionOp::GT, false, false, inst); + case Opcode::V_CMP_NE_U32: + return V_CMP_U32(ConditionOp::LG, false, false, inst); + case Opcode::V_CMP_GE_U32: + return V_CMP_U32(ConditionOp::GE, false, false, inst); + case Opcode::V_CMP_TRU_U32: + return V_CMP_U32(ConditionOp::TRU, false, false, inst); + + // V_CMPX_{OP8}_U32 case Opcode::V_CMPX_F_U32: return V_CMP_U32(ConditionOp::F, false, true, inst); case Opcode::V_CMPX_LT_U32: @@ -307,99 +288,172 @@ void Translator::EmitVectorAlu(const GcnInst& inst) { return V_CMP_U32(ConditionOp::GE, false, true, inst); case Opcode::V_CMPX_TRU_U32: return V_CMP_U32(ConditionOp::TRU, false, true, inst); - case Opcode::V_CMPX_LG_I32: - return V_CMP_U32(ConditionOp::LG, true, true, inst); - case Opcode::V_MBCNT_LO_U32_B32: - return V_MBCNT_U32_B32(true, inst); - case Opcode::V_MBCNT_HI_U32_B32: - return V_MBCNT_U32_B32(false, inst); - case Opcode::V_MOVRELS_B32: - return V_MOVRELS_B32(inst); - case Opcode::V_MOVRELD_B32: - return V_MOVRELD_B32(inst); - case Opcode::V_MOVRELSD_B32: - return V_MOVRELSD_B32(inst); + // V_CMP_{OP8}_U64 + case Opcode::V_CMP_NE_U64: + return V_CMP_NE_U64(inst); + + case Opcode::V_CMP_CLASS_F32: + return V_CMP_CLASS_F32(inst); + + // VOP3a + case Opcode::V_MAD_LEGACY_F32: + return V_MAD_F32(inst); + case Opcode::V_MAD_F32: + return V_MAD_F32(inst); + case Opcode::V_MAD_I32_I24: + return V_MAD_I32_I24(inst); + case Opcode::V_MAD_U32_U24: + return V_MAD_U32_U24(inst); + case Opcode::V_CUBEID_F32: + return V_CUBEID_F32(inst); + case Opcode::V_CUBESC_F32: + return V_CUBESC_F32(inst); + case Opcode::V_CUBETC_F32: + return V_CUBETC_F32(inst); + case Opcode::V_CUBEMA_F32: + return V_CUBEMA_F32(inst); + case Opcode::V_BFE_U32: + return V_BFE_U32(false, inst); + case Opcode::V_BFE_I32: + return V_BFE_U32(true, inst); + case Opcode::V_BFI_B32: + return V_BFI_B32(inst); + case Opcode::V_FMA_F32: + return V_FMA_F32(inst); + case Opcode::V_FMA_F64: + return V_FMA_F64(inst); + case Opcode::V_MIN3_F32: + return V_MIN3_F32(inst); + case Opcode::V_MIN3_I32: + return V_MIN3_I32(inst); + case Opcode::V_MAX3_F32: + return V_MAX3_F32(inst); + case Opcode::V_MAX3_I32: + return V_MAX3_U32(true, inst); + case Opcode::V_MAX3_U32: + return V_MAX3_U32(false, inst); + case Opcode::V_MED3_F32: + return V_MED3_F32(inst); + case Opcode::V_MED3_I32: + return V_MED3_I32(inst); + case Opcode::V_SAD_U32: + return V_SAD_U32(inst); + case Opcode::V_CVT_PK_U8_F32: + return V_CVT_PK_U8_F32(inst); + case Opcode::V_LSHL_B64: + return V_LSHL_B64(inst); + case Opcode::V_MUL_F64: + return V_MUL_F64(inst); + case Opcode::V_MAX_F64: + return V_MAX_F64(inst); + case Opcode::V_MUL_LO_U32: + return V_MUL_LO_U32(inst); + case Opcode::V_MUL_HI_U32: + return V_MUL_HI_U32(false, inst); + case Opcode::V_MUL_LO_I32: + return V_MUL_LO_U32(inst); + case Opcode::V_MAD_U64_U32: + return V_MAD_U64_U32(inst); case Opcode::V_NOP: return; - - case Opcode::V_BFM_B32: - return V_BFM_B32(inst); - case Opcode::V_FFBH_U32: - return V_FFBH_U32(inst); default: LogMissingOpcode(inst); } } -void Translator::V_MOV(const GcnInst& inst) { - SetDst(inst.dst[0], GetSrc(inst.src[0])); -} - -void Translator::V_SAD(const GcnInst& inst) { - const IR::U32 abs_diff = ir.IAbs(ir.ISub(GetSrc(inst.src[0]), GetSrc(inst.src[1]))); - SetDst(inst.dst[0], ir.IAdd(abs_diff, GetSrc(inst.src[2]))); -} - -void Translator::V_MAC_F32(const GcnInst& inst) { - SetDst(inst.dst[0], ir.FPFma(GetSrc(inst.src[0]), GetSrc(inst.src[1]), - GetSrc(inst.dst[0]))); -} - -void Translator::V_CVT_PKRTZ_F16_F32(const GcnInst& inst) { - const IR::VectorReg dst_reg{inst.dst[0].code}; - const IR::Value vec_f32 = - ir.CompositeConstruct(GetSrc(inst.src[0]), GetSrc(inst.src[1])); - ir.SetVectorReg(dst_reg, ir.PackHalf2x16(vec_f32)); -} - -void Translator::V_CVT_F32_F16(const GcnInst& inst) { - const IR::U32 src0 = GetSrc(inst.src[0]); - const IR::U16 src0l = ir.UConvert(16, src0); - SetDst(inst.dst[0], ir.FPConvert(32, ir.BitCast(src0l))); -} - -void Translator::V_CVT_F16_F32(const GcnInst& inst) { - const IR::F32 src0 = GetSrc(inst.src[0]); - const IR::F16 src0fp16 = ir.FPConvert(16, src0); - SetDst(inst.dst[0], ir.UConvert(32, ir.BitCast(src0fp16))); -} - -void Translator::V_MUL_F32(const GcnInst& inst) { - SetDst(inst.dst[0], ir.FPMul(GetSrc(inst.src[0]), GetSrc(inst.src[1]))); -} +// VOP2 void Translator::V_CNDMASK_B32(const GcnInst& inst) { - const IR::VectorReg dst_reg{inst.dst[0].code}; const IR::ScalarReg flag_reg{inst.src[2].code}; const IR::U1 flag = inst.src[2].field == OperandField::ScalarGPR ? ir.GetThreadBitScalarReg(flag_reg) : ir.GetVcc(); const IR::Value result = ir.Select(flag, GetSrc(inst.src[1]), GetSrc(inst.src[0])); - ir.SetVectorReg(dst_reg, IR::U32F32{result}); + SetDst(inst.dst[0], IR::U32F32{result}); } -void Translator::V_OR_B32(bool is_xor, const GcnInst& inst) { - const IR::U32 src0{GetSrc(inst.src[0])}; - const IR::U32 src1{ir.GetVectorReg(IR::VectorReg(inst.src[1].code))}; - const IR::VectorReg dst_reg{inst.dst[0].code}; - ir.SetVectorReg(dst_reg, - is_xor ? ir.BitwiseXor(src0, src1) : IR::U32(ir.BitwiseOr(src0, src1))); +void Translator::V_ADD_F32(const GcnInst& inst) { + const IR::F32 src0{GetSrc(inst.src[0])}; + const IR::F32 src1{GetSrc(inst.src[1])}; + SetDst(inst.dst[0], ir.FPAdd(src0, src1)); } -void Translator::V_AND_B32(const GcnInst& inst) { - const IR::U32 src0{GetSrc(inst.src[0])}; - const IR::U32 src1{ir.GetVectorReg(IR::VectorReg(inst.src[1].code))}; - const IR::VectorReg dst_reg{inst.dst[0].code}; - ir.SetVectorReg(dst_reg, ir.BitwiseAnd(src0, src1)); +void Translator::V_SUB_F32(const GcnInst& inst) { + const IR::F32 src0{GetSrc(inst.src[0])}; + const IR::F32 src1{GetSrc(inst.src[1])}; + SetDst(inst.dst[0], ir.FPSub(src0, src1)); } -void Translator::V_LSHLREV_B32(const GcnInst& inst) { +void Translator::V_SUBREV_F32(const GcnInst& inst) { + const IR::F32 src0{GetSrc(inst.src[0])}; + const IR::F32 src1{GetSrc(inst.src[1])}; + SetDst(inst.dst[0], ir.FPSub(src1, src0)); +} + +void Translator::V_MUL_F32(const GcnInst& inst) { + SetDst(inst.dst[0], ir.FPMul(GetSrc(inst.src[0]), GetSrc(inst.src[1]))); +} + +void Translator::V_MUL_I32_I24(const GcnInst& inst) { + const IR::U32 src0{ir.BitFieldExtract(GetSrc(inst.src[0]), ir.Imm32(0), ir.Imm32(24), true)}; + const IR::U32 src1{ir.BitFieldExtract(GetSrc(inst.src[1]), ir.Imm32(0), ir.Imm32(24), true)}; + SetDst(inst.dst[0], ir.IMul(src0, src1)); +} + +void Translator::V_MIN_F32(const GcnInst& inst, bool is_legacy) { + const IR::F32 src0{GetSrc(inst.src[0])}; + const IR::F32 src1{GetSrc(inst.src[1])}; + SetDst(inst.dst[0], ir.FPMin(src0, src1, is_legacy)); +} + +void Translator::V_MAX_F32(const GcnInst& inst, bool is_legacy) { + const IR::F32 src0{GetSrc(inst.src[0])}; + const IR::F32 src1{GetSrc(inst.src[1])}; + SetDst(inst.dst[0], ir.FPMax(src0, src1, is_legacy)); +} + +void Translator::V_MIN_I32(const GcnInst& inst) { const IR::U32 src0{GetSrc(inst.src[0])}; const IR::U32 src1{GetSrc(inst.src[1])}; - const IR::VectorReg dst_reg{inst.dst[0].code}; - ir.SetVectorReg(dst_reg, ir.ShiftLeftLogical(src1, ir.BitwiseAnd(src0, ir.Imm32(0x1F)))); + SetDst(inst.dst[0], ir.SMin(src0, src1)); +} + +void Translator::V_MIN_U32(const GcnInst& inst) { + const IR::U32 src0{GetSrc(inst.src[0])}; + const IR::U32 src1{GetSrc(inst.src[1])}; + SetDst(inst.dst[0], ir.IMin(src0, src1, false)); +} + +void Translator::V_MAX_U32(bool is_signed, const GcnInst& inst) { + const IR::U32 src0{GetSrc(inst.src[0])}; + const IR::U32 src1{GetSrc(inst.src[1])}; + SetDst(inst.dst[0], ir.IMax(src0, src1, is_signed)); +} + +void Translator::V_LSHR_B32(const GcnInst& inst) { + const IR::U32 src0{GetSrc(inst.src[0])}; + const IR::U32 src1{GetSrc(inst.src[1])}; + SetDst(inst.dst[0], ir.ShiftRightLogical(src0, ir.BitwiseAnd(src1, ir.Imm32(0x1F)))); +} + +void Translator::V_LSHRREV_B32(const GcnInst& inst) { + const IR::U32 src0{GetSrc(inst.src[0])}; + const IR::U32 src1{GetSrc(inst.src[1])}; + SetDst(inst.dst[0], ir.ShiftRightLogical(src1, ir.BitwiseAnd(src0, ir.Imm32(0x1F)))); +} + +void Translator::V_ASHR_I32(const GcnInst& inst) { + const IR::U32 src0{GetSrc(inst.src[0])}; + const IR::U32 src1{GetSrc(inst.src[1])}; + SetDst(inst.dst[0], ir.ShiftRightArithmetic(src0, ir.BitwiseAnd(src1, ir.Imm32(0x1F)))); +} + +void Translator::V_ASHRREV_I32(const GcnInst& inst) { + const IR::U32 src0{GetSrc(inst.src[0])}; + const IR::U32 src1{GetSrc(inst.src[1])}; + SetDst(inst.dst[0], ir.ShiftRightArithmetic(src1, ir.BitwiseAnd(src0, ir.Imm32(0x1F)))); } void Translator::V_LSHL_B32(const GcnInst& inst) { @@ -408,26 +462,98 @@ void Translator::V_LSHL_B32(const GcnInst& inst) { SetDst(inst.dst[0], ir.ShiftLeftLogical(src0, ir.BitwiseAnd(src1, ir.Imm32(0x1F)))); } -void Translator::V_LSHL_B64(const GcnInst& inst) { - const IR::U64 src0{GetSrc64(inst.src[0])}; - const IR::U64 src1{GetSrc64(inst.src[1])}; - const IR::VectorReg dst_reg{inst.dst[0].code}; - ASSERT_MSG(src0.IsImmediate() && src0.U64() == 0 && src1.IsImmediate() && src1.U64() == 0, - "V_LSHL_B64 with non-zero src0 or src1 is not supported"); - ir.SetVectorReg(dst_reg, ir.Imm32(0)); - ir.SetVectorReg(dst_reg + 1, ir.Imm32(0)); +void Translator::V_LSHLREV_B32(const GcnInst& inst) { + const IR::U32 src0{GetSrc(inst.src[0])}; + const IR::U32 src1{GetSrc(inst.src[1])}; + SetDst(inst.dst[0], ir.ShiftLeftLogical(src1, ir.BitwiseAnd(src0, ir.Imm32(0x1F)))); +} + +void Translator::V_AND_B32(const GcnInst& inst) { + 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.BitwiseAnd(src0, src1)); +} + +void Translator::V_OR_B32(bool is_xor, const GcnInst& inst) { + const IR::U32 src0{GetSrc(inst.src[0])}; + const IR::U32 src1{ir.GetVectorReg(IR::VectorReg(inst.src[1].code))}; + SetDst(inst.dst[0], is_xor ? ir.BitwiseXor(src0, src1) : IR::U32(ir.BitwiseOr(src0, src1))); +} + +void Translator::V_BFM_B32(const GcnInst& inst) { + // bitmask width + const IR::U32 src0{ir.BitFieldExtract(GetSrc(inst.src[0]), ir.Imm32(0), ir.Imm32(4))}; + // bitmask offset + const IR::U32 src1{ir.BitFieldExtract(GetSrc(inst.src[1]), ir.Imm32(0), ir.Imm32(4))}; + const IR::U32 ones = ir.ISub(ir.ShiftLeftLogical(ir.Imm32(1), src0), ir.Imm32(1)); + SetDst(inst.dst[0], ir.ShiftLeftLogical(ones, src1)); +} + +void Translator::V_MAC_F32(const GcnInst& inst) { + SetDst(inst.dst[0], ir.FPFma(GetSrc(inst.src[0]), GetSrc(inst.src[1]), + GetSrc(inst.dst[0]))); +} + +void Translator::V_MADMK_F32(const GcnInst& inst) { + const IR::F32 src0{GetSrc(inst.src[0])}; + const IR::F32 src1{GetSrc(inst.src[1])}; + const IR::F32 k{GetSrc(inst.src[2])}; + SetDst(inst.dst[0], ir.FPFma(src0, k, src1)); +} + +void Translator::V_BCNT_U32_B32(const GcnInst& inst) { + const IR::U32 src0{GetSrc(inst.src[0])}; + const IR::U32 src1{GetSrc(inst.src[1])}; + SetDst(inst.dst[0], ir.IAdd(ir.BitCount(src0), src1)); +} + +void Translator::V_MBCNT_U32_B32(bool is_low, const GcnInst& inst) { + if (!is_low) { + // v_mbcnt_hi_u32_b32 v2, -1, 0 + if (inst.src[0].field == OperandField::SignedConstIntNeg && inst.src[0].code == 193 && + inst.src[1].field == OperandField::ConstZero) { + return; + } + // v_mbcnt_hi_u32_b32 vX, exec_hi, 0 + if (inst.src[0].field == OperandField::ExecHi && + inst.src[1].field == OperandField::ConstZero) { + return; + } + } else { + // v_mbcnt_lo_u32_b32 v2, -1, vX + // used combined with above to fetch lane id in non-compute stages + if (inst.src[0].field == OperandField::SignedConstIntNeg && inst.src[0].code == 193) { + SetDst(inst.dst[0], ir.LaneId()); + } + // v_mbcnt_lo_u32_b32 v20, exec_lo, vX + // used combined in above for append buffer indexing. + if (inst.src[0].field == OperandField::ExecLo) { + SetDst(inst.dst[0], ir.Imm32(0)); + } + } } void Translator::V_ADD_I32(const GcnInst& inst) { const IR::U32 src0{GetSrc(inst.src[0])}; const IR::U32 src1{ir.GetVectorReg(IR::VectorReg(inst.src[1].code))}; - const IR::VectorReg dst_reg{inst.dst[0].code}; - ir.SetVectorReg(dst_reg, ir.IAdd(src0, src1)); + SetDst(inst.dst[0], ir.IAdd(src0, src1)); // TODO: Carry } -void Translator::V_ADDC_U32(const GcnInst& inst) { +void Translator::V_SUB_I32(const GcnInst& inst) { + const IR::U32 src0{GetSrc(inst.src[0])}; + const IR::U32 src1{GetSrc(inst.src[1])}; + SetDst(inst.dst[0], ir.ISub(src0, src1)); +} +void Translator::V_SUBREV_I32(const GcnInst& inst) { + 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 +} + +void Translator::V_ADDC_U32(const GcnInst& inst) { const auto src0 = GetSrc(inst.src[0]); const auto src1 = GetSrc(inst.src[1]); @@ -447,8 +573,7 @@ void Translator::V_ADDC_U32(const GcnInst& inst) { 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); - const IR::VectorReg dst_reg{inst.dst[0].code}; - ir.SetVectorReg(dst_reg, result); + 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); @@ -456,73 +581,118 @@ void Translator::V_ADDC_U32(const GcnInst& inst) { ir.SetVcc(did_overflow); } +void Translator::V_LDEXP_F32(const GcnInst& inst) { + const IR::F32 src0{GetSrc(inst.src[0])}; + const IR::U32 src1{GetSrc(inst.src[1])}; + SetDst(inst.dst[0], ir.FPLdexp(src0, src1)); +} + +void Translator::V_CVT_PKNORM_U16_F32(const GcnInst& inst) { + const IR::F32 src0{GetSrc(inst.src[0])}; + const IR::F32 src1{GetSrc(inst.src[1])}; + const IR::U32 dst0 = ir.ConvertFToU(32, ir.FPMul(src0, ir.Imm32(65535.f))); + const IR::U32 dst1 = ir.ConvertFToU(32, ir.FPMul(src1, ir.Imm32(65535.f))); + const IR::VectorReg dst_reg{inst.dst[0].code}; + ir.SetVectorReg(dst_reg, ir.BitFieldInsert(dst0, dst1, ir.Imm32(16), ir.Imm32(16))); +} + +void Translator::V_CVT_PKRTZ_F16_F32(const GcnInst& inst) { + const IR::Value vec_f32 = + ir.CompositeConstruct(GetSrc(inst.src[0]), GetSrc(inst.src[1])); + SetDst(inst.dst[0], ir.PackHalf2x16(vec_f32)); +} + +// VOP1 + +void Translator::V_MOV(const GcnInst& inst) { + SetDst(inst.dst[0], GetSrc(inst.src[0])); +} + void Translator::V_CVT_F32_I32(const GcnInst& inst) { const IR::U32 src0{GetSrc(inst.src[0])}; - const IR::VectorReg dst_reg{inst.dst[0].code}; - ir.SetVectorReg(dst_reg, ir.ConvertSToF(32, 32, src0)); + SetDst(inst.dst[0], ir.ConvertSToF(32, 32, src0)); } void Translator::V_CVT_F32_U32(const GcnInst& inst) { const IR::U32 src0{GetSrc(inst.src[0])}; - const IR::VectorReg dst_reg{inst.dst[0].code}; - ir.SetVectorReg(dst_reg, ir.ConvertUToF(32, 32, src0)); + SetDst(inst.dst[0], ir.ConvertUToF(32, 32, src0)); } -void Translator::V_MAD_F32(const GcnInst& inst) { +void Translator::V_CVT_U32_F32(const GcnInst& inst) { const IR::F32 src0{GetSrc(inst.src[0])}; - const IR::F32 src1{GetSrc(inst.src[1])}; - const IR::F32 src2{GetSrc(inst.src[2])}; - SetDst(inst.dst[0], ir.FPFma(src0, src1, src2)); + SetDst(inst.dst[0], ir.ConvertFToU(32, src0)); } -void Translator::V_FRACT_F32(const GcnInst& inst) { +void Translator::V_CVT_I32_F32(const GcnInst& inst) { const IR::F32 src0{GetSrc(inst.src[0])}; - const IR::VectorReg dst_reg{inst.dst[0].code}; - ir.SetVectorReg(dst_reg, ir.Fract(src0)); + SetDst(inst.dst[0], ir.ConvertFToS(32, src0)); } -void Translator::V_ADD_F32(const GcnInst& inst) { +void Translator::V_CVT_F16_F32(const GcnInst& inst) { + const IR::F32 src0 = GetSrc(inst.src[0]); + const IR::F16 src0fp16 = ir.FPConvert(16, src0); + SetDst(inst.dst[0], ir.UConvert(32, ir.BitCast(src0fp16))); +} + +void Translator::V_CVT_F32_F16(const GcnInst& inst) { + const IR::U32 src0 = GetSrc(inst.src[0]); + const IR::U16 src0l = ir.UConvert(16, src0); + SetDst(inst.dst[0], ir.FPConvert(32, ir.BitCast(src0l))); +} + +void Translator::V_CVT_FLR_I32_F32(const GcnInst& inst) { const IR::F32 src0{GetSrc(inst.src[0])}; - const IR::F32 src1{GetSrc(inst.src[1])}; - SetDst(inst.dst[0], ir.FPAdd(src0, src1)); + SetDst(inst.dst[0], ir.ConvertFToI(32, true, ir.FPFloor(src0))); } void Translator::V_CVT_OFF_F32_I4(const GcnInst& inst) { const IR::U32 src0{GetSrc(inst.src[0])}; - const IR::VectorReg dst_reg{inst.dst[0].code}; ASSERT(src0.IsImmediate()); static constexpr std::array IntToFloat = { 0.0f, 0.0625f, 0.1250f, 0.1875f, 0.2500f, 0.3125f, 0.3750f, 0.4375f, -0.5000f, -0.4375f, -0.3750f, -0.3125f, -0.2500f, -0.1875f, -0.1250f, -0.0625f}; - ir.SetVectorReg(dst_reg, ir.Imm32(IntToFloat[src0.U32() & 0xF])); + SetDst(inst.dst[0], ir.Imm32(IntToFloat[src0.U32() & 0xF])); } -void Translator::V_MED3_F32(const GcnInst& inst) { - const IR::F32 src0{GetSrc(inst.src[0])}; - const IR::F32 src1{GetSrc(inst.src[1])}; - const IR::F32 src2{GetSrc(inst.src[2])}; - const IR::F32 mmx = ir.FPMin(ir.FPMax(src0, src1), src2); - SetDst(inst.dst[0], ir.FPMax(ir.FPMin(src0, src1), mmx)); -} - -void Translator::V_MED3_I32(const GcnInst& inst) { +void Translator::V_CVT_F32_UBYTE(u32 index, const GcnInst& inst) { const IR::U32 src0{GetSrc(inst.src[0])}; - const IR::U32 src1{GetSrc(inst.src[1])}; - const IR::U32 src2{GetSrc(inst.src[2])}; - const IR::U32 mmx = ir.SMin(ir.SMax(src0, src1), src2); - SetDst(inst.dst[0], ir.SMax(ir.SMin(src0, src1), mmx)); + const IR::U32 byte = ir.BitFieldExtract(src0, ir.Imm32(8 * index), ir.Imm32(8)); + SetDst(inst.dst[0], ir.ConvertUToF(32, 32, byte)); +} + +void Translator::V_FRACT_F32(const GcnInst& inst) { + const IR::F32 src0{GetSrc(inst.src[0])}; + SetDst(inst.dst[0], ir.Fract(src0)); +} + +void Translator::V_TRUNC_F32(const GcnInst& inst) { + const IR::F32 src0{GetSrc(inst.src[0])}; + SetDst(inst.dst[0], ir.FPTrunc(src0)); +} + +void Translator::V_CEIL_F32(const GcnInst& inst) { + const IR::F32 src0{GetSrc(inst.src[0])}; + SetDst(inst.dst[0], ir.FPCeil(src0)); +} + +void Translator::V_RNDNE_F32(const GcnInst& inst) { + const IR::F32 src0{GetSrc(inst.src[0])}; + SetDst(inst.dst[0], ir.FPRoundEven(src0)); } void Translator::V_FLOOR_F32(const GcnInst& inst) { const IR::F32 src0{GetSrc(inst.src[0])}; - const IR::VectorReg dst_reg{inst.dst[0].code}; - ir.SetVectorReg(dst_reg, ir.FPFloor(src0)); + SetDst(inst.dst[0], ir.FPFloor(src0)); } -void Translator::V_SUB_F32(const GcnInst& inst) { +void Translator::V_EXP_F32(const GcnInst& inst) { const IR::F32 src0{GetSrc(inst.src[0])}; - const IR::F32 src1{GetSrc(inst.src[1])}; - SetDst(inst.dst[0], ir.FPSub(src0, src1)); + SetDst(inst.dst[0], ir.FPExp2(src0)); +} + +void Translator::V_LOG_F32(const GcnInst& inst) { + const IR::F32 src0{GetSrc(inst.src[0])}; + SetDst(inst.dst[0], ir.FPLog2(src0)); } void Translator::V_RCP_F32(const GcnInst& inst) { @@ -530,13 +700,84 @@ void Translator::V_RCP_F32(const GcnInst& inst) { SetDst(inst.dst[0], ir.FPRecip(src0)); } -void Translator::V_FMA_F32(const GcnInst& inst) { - const IR::F32 src0{GetSrc(inst.src[0])}; - const IR::F32 src1{GetSrc(inst.src[1])}; - const IR::F32 src2{GetSrc(inst.src[2])}; - SetDst(inst.dst[0], ir.FPFma(src0, src1, src2)); +void Translator::V_RCP_F64(const GcnInst& inst) { + const IR::F64 src0{GetSrc64(inst.src[0])}; + SetDst64(inst.dst[0], ir.FPRecip(src0)); } +void Translator::V_RSQ_F32(const GcnInst& inst) { + const IR::F32 src0{GetSrc(inst.src[0])}; + SetDst(inst.dst[0], ir.FPRecipSqrt(src0)); +} + +void Translator::V_SQRT_F32(const GcnInst& inst) { + const IR::F32 src0{GetSrc(inst.src[0])}; + SetDst(inst.dst[0], ir.FPSqrt(src0)); +} + +void Translator::V_SIN_F32(const GcnInst& inst) { + const IR::F32 src0{GetSrc(inst.src[0])}; + SetDst(inst.dst[0], ir.FPSin(src0)); +} + +void Translator::V_COS_F32(const GcnInst& inst) { + const IR::F32 src0{GetSrc(inst.src[0])}; + SetDst(inst.dst[0], ir.FPCos(src0)); +} + +void Translator::V_NOT_B32(const GcnInst& inst) { + const IR::U32 src0{GetSrc(inst.src[0])}; + SetDst(inst.dst[0], ir.BitwiseNot(src0)); +} + +void Translator::V_BFREV_B32(const GcnInst& inst) { + const IR::U32 src0{GetSrc(inst.src[0])}; + SetDst(inst.dst[0], ir.BitReverse(src0)); +} + +void Translator::V_FFBH_U32(const GcnInst& inst) { + const IR::U32 src0{GetSrc(inst.src[0])}; + // Gcn wants the MSB position counting from the left, but SPIR-V counts from the rightmost (LSB) + // position + const IR::U32 msb_pos = ir.FindUMsb(src0); + const IR::U32 pos_from_left = ir.ISub(ir.Imm32(31), msb_pos); + // Select 0xFFFFFFFF if src0 was 0 + const IR::U1 cond = ir.INotEqual(src0, ir.Imm32(0)); + SetDst(inst.dst[0], IR::U32{ir.Select(cond, pos_from_left, ir.Imm32(~0U))}); +} + +void Translator::V_FFBL_B32(const GcnInst& inst) { + const IR::U32 src0{GetSrc(inst.src[0])}; + SetDst(inst.dst[0], ir.FindILsb(src0)); +} + +void Translator::V_MOVRELD_B32(const GcnInst& inst) { + const IR::U32 src_val{GetSrc(inst.src[0])}; + u32 dst_vgprno = inst.dst[0].code - static_cast(IR::VectorReg::V0); + IR::U32 m0 = ir.GetM0(); + + VMovRelDHelper(dst_vgprno, src_val, m0); +} + +void Translator::V_MOVRELS_B32(const GcnInst& inst) { + u32 src_vgprno = inst.src[0].code - static_cast(IR::VectorReg::V0); + const IR::U32 m0 = ir.GetM0(); + + const IR::U32 src_val = VMovRelSHelper(src_vgprno, m0); + SetDst(inst.dst[0], src_val); +} + +void Translator::V_MOVRELSD_B32(const GcnInst& inst) { + u32 src_vgprno = inst.src[0].code - static_cast(IR::VectorReg::V0); + u32 dst_vgprno = inst.dst[0].code - static_cast(IR::VectorReg::V0); + IR::U32 m0 = ir.GetM0(); + + const IR::U32 src_val = VMovRelSHelper(src_vgprno, m0); + VMovRelDHelper(dst_vgprno, src_val, m0); +} + +// VOPC + void Translator::V_CMP_F32(ConditionOp op, bool set_exec, const GcnInst& inst) { const IR::F32 src0{GetSrc(inst.src[0])}; const IR::F32 src1{GetSrc(inst.src[1])}; @@ -578,128 +819,6 @@ void Translator::V_CMP_F32(ConditionOp op, bool set_exec, const GcnInst& inst) { } } -void Translator::V_MAX_F32(const GcnInst& inst, bool is_legacy) { - const IR::F32 src0{GetSrc(inst.src[0])}; - const IR::F32 src1{GetSrc(inst.src[1])}; - SetDst(inst.dst[0], ir.FPMax(src0, src1, is_legacy)); -} - -void Translator::V_MAX_F64(const GcnInst& inst) { - const IR::F64 src0{GetSrc64(inst.src[0])}; - const IR::F64 src1{GetSrc64(inst.src[1])}; - SetDst64(inst.dst[0], ir.FPMax(src0, src1)); -} - -void Translator::V_MAX_U32(bool is_signed, const GcnInst& inst) { - const IR::U32 src0{GetSrc(inst.src[0])}; - const IR::U32 src1{GetSrc(inst.src[1])}; - SetDst(inst.dst[0], ir.IMax(src0, src1, is_signed)); -} - -void Translator::V_RSQ_F32(const GcnInst& inst) { - const IR::F32 src0{GetSrc(inst.src[0])}; - SetDst(inst.dst[0], ir.FPRecipSqrt(src0)); -} - -void Translator::V_SIN_F32(const GcnInst& inst) { - const IR::F32 src0{GetSrc(inst.src[0])}; - SetDst(inst.dst[0], ir.FPSin(src0)); -} - -void Translator::V_LOG_F32(const GcnInst& inst) { - const IR::F32 src0{GetSrc(inst.src[0])}; - SetDst(inst.dst[0], ir.FPLog2(src0)); -} - -void Translator::V_EXP_F32(const GcnInst& inst) { - const IR::F32 src0{GetSrc(inst.src[0])}; - SetDst(inst.dst[0], ir.FPExp2(src0)); -} - -void Translator::V_SQRT_F32(const GcnInst& inst) { - const IR::F32 src0{GetSrc(inst.src[0])}; - SetDst(inst.dst[0], ir.FPSqrt(src0)); -} - -void Translator::V_MIN_F32(const GcnInst& inst, bool is_legacy) { - const IR::F32 src0{GetSrc(inst.src[0])}; - const IR::F32 src1{GetSrc(inst.src[1])}; - SetDst(inst.dst[0], ir.FPMin(src0, src1, is_legacy)); -} - -void Translator::V_MIN3_F32(const GcnInst& inst) { - const IR::F32 src0{GetSrc(inst.src[0])}; - const IR::F32 src1{GetSrc(inst.src[1])}; - const IR::F32 src2{GetSrc(inst.src[2])}; - SetDst(inst.dst[0], ir.FPMin(src0, ir.FPMin(src1, src2))); -} - -void Translator::V_MIN3_I32(const GcnInst& inst) { - const IR::U32 src0{GetSrc(inst.src[0])}; - const IR::U32 src1{GetSrc(inst.src[1])}; - const IR::U32 src2{GetSrc(inst.src[2])}; - SetDst(inst.dst[0], ir.SMin(src0, ir.SMin(src1, src2))); -} - -void Translator::V_MADMK_F32(const GcnInst& inst) { - const IR::F32 src0{GetSrc(inst.src[0])}; - const IR::F32 src1{GetSrc(inst.src[1])}; - const IR::F32 k{GetSrc(inst.src[2])}; - SetDst(inst.dst[0], ir.FPFma(src0, k, src1)); -} - -void Translator::V_CUBEMA_F32(const GcnInst& inst) { - SetDst(inst.dst[0], ir.Imm32(1.f)); -} - -void Translator::V_CUBESC_F32(const GcnInst& inst) { - SetDst(inst.dst[0], GetSrc(inst.src[0])); -} - -void Translator::V_CUBETC_F32(const GcnInst& inst) { - SetDst(inst.dst[0], GetSrc(inst.src[1])); -} - -void Translator::V_CUBEID_F32(const GcnInst& inst) { - SetDst(inst.dst[0], GetSrc(inst.src[2])); -} - -void Translator::V_CVT_U32_F32(const GcnInst& inst) { - const IR::F32 src0{GetSrc(inst.src[0])}; - SetDst(inst.dst[0], ir.ConvertFToU(32, src0)); -} - -void Translator::V_SUBREV_F32(const GcnInst& inst) { - const IR::F32 src0{GetSrc(inst.src[0])}; - const IR::F32 src1{GetSrc(inst.src[1])}; - SetDst(inst.dst[0], ir.FPSub(src1, src0)); -} - -void Translator::V_SUBREV_I32(const GcnInst& inst) { - 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 -} - -void Translator::V_MAD_U64_U32(const GcnInst& inst) { - const auto src0 = GetSrc(inst.src[0]); - const auto src1 = GetSrc(inst.src[1]); - const auto src2 = GetSrc64(inst.src[2]); - - // const IR::U64 mul_result = ir.UConvert(64, ir.IMul(src0, src1)); - const IR::U64 mul_result = - ir.PackUint2x32(ir.CompositeConstruct(ir.IMul(src0, src1), ir.Imm32(0U))); - const IR::U64 sum_result = ir.IAdd(mul_result, src2); - - SetDst64(inst.dst[0], sum_result); - - const IR::U1 less_src0 = ir.ILessThan(sum_result, mul_result, false); - const IR::U1 less_src1 = ir.ILessThan(sum_result, src2, false); - const IR::U1 did_overflow = ir.LogicalOr(less_src0, less_src1); - ir.SetVcc(did_overflow); -} - void Translator::V_CMP_U32(ConditionOp op, bool is_signed, bool set_exec, const GcnInst& inst) { const IR::U32 src0{GetSrc(inst.src[0])}; const IR::U32 src1{GetSrc(inst.src[1])}; @@ -738,149 +857,6 @@ void Translator::V_CMP_U32(ConditionOp op, bool is_signed, bool set_exec, const } } -void Translator::V_LSHRREV_B32(const GcnInst& inst) { - const IR::U32 src0{GetSrc(inst.src[0])}; - const IR::U32 src1{GetSrc(inst.src[1])}; - SetDst(inst.dst[0], ir.ShiftRightLogical(src1, ir.BitwiseAnd(src0, ir.Imm32(0x1F)))); -} - -void Translator::V_MUL_HI_U32(bool is_signed, const GcnInst& inst) { - const IR::U32 src0{GetSrc(inst.src[0])}; - const IR::U32 src1{GetSrc(inst.src[1])}; - const IR::U32 hi{ir.CompositeExtract(ir.IMulExt(src0, src1, is_signed), 1)}; - SetDst(inst.dst[0], hi); -} - -void Translator::V_SAD_U32(const GcnInst& inst) { - const IR::U32 src0{GetSrc(inst.src[0])}; - const IR::U32 src1{GetSrc(inst.src[1])}; - const IR::U32 src2{GetSrc(inst.src[2])}; - IR::U32 result; - if (src0.IsImmediate() && src0.U32() == 0U) { - result = src1; - } else if (src1.IsImmediate() && src1.U32() == 0U) { - result = src0; - } else { - const IR::U32 max{ir.IMax(src0, src1, false)}; - const IR::U32 min{ir.IMin(src0, src1, false)}; - result = ir.ISub(max, min); - } - SetDst(inst.dst[0], ir.IAdd(result, src2)); -} - -void Translator::V_BFE_U32(bool is_signed, const GcnInst& inst) { - const IR::U32 src0{GetSrc(inst.src[0])}; - const IR::U32 src1{ir.BitwiseAnd(GetSrc(inst.src[1]), ir.Imm32(0x1F))}; - const IR::U32 src2{ir.BitwiseAnd(GetSrc(inst.src[2]), ir.Imm32(0x1F))}; - SetDst(inst.dst[0], ir.BitFieldExtract(src0, src1, src2, is_signed)); -} - -void Translator::V_MAD_I32_I24(const GcnInst& inst, bool is_signed) { - const IR::U32 src0{ - ir.BitFieldExtract(GetSrc(inst.src[0]), ir.Imm32(0), ir.Imm32(24), is_signed)}; - const IR::U32 src1{ - ir.BitFieldExtract(GetSrc(inst.src[1]), ir.Imm32(0), ir.Imm32(24), is_signed)}; - const IR::U32 src2{GetSrc(inst.src[2])}; - SetDst(inst.dst[0], ir.IAdd(ir.IMul(src0, src1), src2)); -} - -void Translator::V_MUL_I32_I24(const GcnInst& inst) { - const IR::U32 src0{ir.BitFieldExtract(GetSrc(inst.src[0]), ir.Imm32(0), ir.Imm32(24), true)}; - const IR::U32 src1{ir.BitFieldExtract(GetSrc(inst.src[1]), ir.Imm32(0), ir.Imm32(24), true)}; - SetDst(inst.dst[0], ir.IMul(src0, src1)); -} - -void Translator::V_SUB_I32(const GcnInst& inst) { - const IR::U32 src0{GetSrc(inst.src[0])}; - const IR::U32 src1{GetSrc(inst.src[1])}; - SetDst(inst.dst[0], ir.ISub(src0, src1)); -} - -void Translator::V_LSHR_B32(const GcnInst& inst) { - const IR::U32 src0{GetSrc(inst.src[0])}; - const IR::U32 src1{GetSrc(inst.src[1])}; - SetDst(inst.dst[0], ir.ShiftRightLogical(src0, ir.BitwiseAnd(src1, ir.Imm32(0x1F)))); -} - -void Translator::V_ASHRREV_I32(const GcnInst& inst) { - const IR::U32 src0{GetSrc(inst.src[0])}; - const IR::U32 src1{GetSrc(inst.src[1])}; - SetDst(inst.dst[0], ir.ShiftRightArithmetic(src1, ir.BitwiseAnd(src0, ir.Imm32(0x1F)))); -} - -void Translator::V_ASHR_I32(const GcnInst& inst) { - const IR::U32 src0{GetSrc(inst.src[0])}; - const IR::U32 src1{GetSrc(inst.src[1])}; - SetDst(inst.dst[0], ir.ShiftRightArithmetic(src0, ir.BitwiseAnd(src1, ir.Imm32(0x1F)))); -} - -void Translator::V_MAD_U32_U24(const GcnInst& inst) { - V_MAD_I32_I24(inst, false); -} - -void Translator::V_RNDNE_F32(const GcnInst& inst) { - const IR::F32 src0{GetSrc(inst.src[0])}; - SetDst(inst.dst[0], ir.FPRoundEven(src0)); -} - -void Translator::V_BCNT_U32_B32(const GcnInst& inst) { - const IR::U32 src0{GetSrc(inst.src[0])}; - const IR::U32 src1{GetSrc(inst.src[1])}; - SetDst(inst.dst[0], ir.IAdd(ir.BitCount(src0), src1)); -} - -void Translator::V_COS_F32(const GcnInst& inst) { - const IR::F32 src0{GetSrc(inst.src[0])}; - SetDst(inst.dst[0], ir.FPCos(src0)); -} - -void Translator::V_MAX3_F32(const GcnInst& inst) { - const IR::F32 src0{GetSrc(inst.src[0])}; - const IR::F32 src1{GetSrc(inst.src[1])}; - const IR::F32 src2{GetSrc(inst.src[2])}; - SetDst(inst.dst[0], ir.FPMax(src0, ir.FPMax(src1, src2))); -} - -void Translator::V_MAX3_U32(bool is_signed, const GcnInst& inst) { - const IR::U32 src0{GetSrc(inst.src[0])}; - const IR::U32 src1{GetSrc(inst.src[1])}; - const IR::U32 src2{GetSrc(inst.src[2])}; - SetDst(inst.dst[0], ir.IMax(src0, ir.IMax(src1, src2, is_signed), is_signed)); -} - -void Translator::V_CVT_I32_F32(const GcnInst& inst) { - const IR::F32 src0{GetSrc(inst.src[0])}; - SetDst(inst.dst[0], ir.ConvertFToS(32, src0)); -} - -void Translator::V_MIN_I32(const GcnInst& inst) { - const IR::U32 src0{GetSrc(inst.src[0])}; - const IR::U32 src1{GetSrc(inst.src[1])}; - SetDst(inst.dst[0], ir.SMin(src0, src1)); -} - -void Translator::V_MUL_LO_U32(const GcnInst& inst) { - const IR::U32 src0{GetSrc(inst.src[0])}; - const IR::U32 src1{GetSrc(inst.src[1])}; - SetDst(inst.dst[0], ir.IMul(src0, src1)); -} - -void Translator::V_TRUNC_F32(const GcnInst& inst) { - const IR::F32 src0{GetSrc(inst.src[0])}; - SetDst(inst.dst[0], ir.FPTrunc(src0)); -} - -void Translator::V_CEIL_F32(const GcnInst& inst) { - const IR::F32 src0{GetSrc(inst.src[0])}; - SetDst(inst.dst[0], ir.FPCeil(src0)); -} - -void Translator::V_MIN_U32(const GcnInst& inst) { - const IR::U32 src0{GetSrc(inst.src[0])}; - const IR::U32 src1{GetSrc(inst.src[1])}; - SetDst(inst.dst[0], ir.IMin(src0, src1, false)); -} - void Translator::V_CMP_NE_U64(const GcnInst& inst) { const auto get_src = [&](const InstOperand& operand) { switch (operand.field) { @@ -910,41 +886,6 @@ void Translator::V_CMP_NE_U64(const GcnInst& inst) { } } -void Translator::V_BFI_B32(const GcnInst& inst) { - const IR::U32 src0{GetSrc(inst.src[0])}; - const IR::U32 src1{GetSrc(inst.src[1])}; - const IR::U32 src2{GetSrc(inst.src[2])}; - SetDst(inst.dst[0], - ir.BitwiseOr(ir.BitwiseAnd(src0, src1), ir.BitwiseAnd(ir.BitwiseNot(src0), src2))); -} - -void Translator::V_NOT_B32(const GcnInst& inst) { - const IR::U32 src0{GetSrc(inst.src[0])}; - SetDst(inst.dst[0], ir.BitwiseNot(src0)); -} - -void Translator::V_CVT_F32_UBYTE(u32 index, const GcnInst& inst) { - const IR::U32 src0{GetSrc(inst.src[0])}; - const IR::U32 byte = ir.BitFieldExtract(src0, ir.Imm32(8 * index), ir.Imm32(8)); - SetDst(inst.dst[0], ir.ConvertUToF(32, 32, byte)); -} - -void Translator::V_BFREV_B32(const GcnInst& inst) { - const IR::U32 src0{GetSrc(inst.src[0])}; - SetDst(inst.dst[0], ir.BitReverse(src0)); -} - -void Translator::V_LDEXP_F32(const GcnInst& inst) { - const IR::F32 src0{GetSrc(inst.src[0])}; - const IR::U32 src1{GetSrc(inst.src[1])}; - SetDst(inst.dst[0], ir.FPLdexp(src0, src1)); -} - -void Translator::V_CVT_FLR_I32_F32(const GcnInst& inst) { - const IR::F32 src0{GetSrc(inst.src[0])}; - SetDst(inst.dst[0], ir.ConvertFToI(32, true, ir.FPFloor(src0))); -} - void Translator::V_CMP_CLASS_F32(const GcnInst& inst) { const IR::F32 src0{GetSrc(inst.src[0])}; const IR::U32 src1{GetSrc(inst.src[1])}; @@ -971,55 +912,205 @@ void Translator::V_CMP_CLASS_F32(const GcnInst& inst) { } } -void Translator::V_FFBL_B32(const GcnInst& inst) { - const IR::U32 src0{GetSrc(inst.src[0])}; - SetDst(inst.dst[0], ir.FindILsb(src0)); +// VOP3a + +void Translator::V_MAD_F32(const GcnInst& inst) { + const IR::F32 src0{GetSrc(inst.src[0])}; + const IR::F32 src1{GetSrc(inst.src[1])}; + const IR::F32 src2{GetSrc(inst.src[2])}; + SetDst(inst.dst[0], ir.FPFma(src0, src1, src2)); } -void Translator::V_MBCNT_U32_B32(bool is_low, const GcnInst& inst) { - if (!is_low) { - // v_mbcnt_hi_u32_b32 v2, -1, 0 - if (inst.src[0].field == OperandField::SignedConstIntNeg && inst.src[0].code == 193 && - inst.src[1].field == OperandField::ConstZero) { - return; - } - // v_mbcnt_hi_u32_b32 vX, exec_hi, 0 - if (inst.src[0].field == OperandField::ExecHi && - inst.src[1].field == OperandField::ConstZero) { - return; - } +void Translator::V_MAD_I32_I24(const GcnInst& inst, bool is_signed) { + const IR::U32 src0{ + ir.BitFieldExtract(GetSrc(inst.src[0]), ir.Imm32(0), ir.Imm32(24), is_signed)}; + const IR::U32 src1{ + ir.BitFieldExtract(GetSrc(inst.src[1]), ir.Imm32(0), ir.Imm32(24), is_signed)}; + const IR::U32 src2{GetSrc(inst.src[2])}; + SetDst(inst.dst[0], ir.IAdd(ir.IMul(src0, src1), src2)); +} + +void Translator::V_MAD_U32_U24(const GcnInst& inst) { + V_MAD_I32_I24(inst, false); +} + +void Translator::V_CUBEID_F32(const GcnInst& inst) { + SetDst(inst.dst[0], GetSrc(inst.src[2])); +} + +void Translator::V_CUBESC_F32(const GcnInst& inst) { + SetDst(inst.dst[0], GetSrc(inst.src[0])); +} + +void Translator::V_CUBETC_F32(const GcnInst& inst) { + SetDst(inst.dst[0], GetSrc(inst.src[1])); +} + +void Translator::V_CUBEMA_F32(const GcnInst& inst) { + SetDst(inst.dst[0], ir.Imm32(1.f)); +} + +void Translator::V_BFE_U32(bool is_signed, const GcnInst& inst) { + const IR::U32 src0{GetSrc(inst.src[0])}; + const IR::U32 src1{ir.BitwiseAnd(GetSrc(inst.src[1]), ir.Imm32(0x1F))}; + const IR::U32 src2{ir.BitwiseAnd(GetSrc(inst.src[2]), ir.Imm32(0x1F))}; + SetDst(inst.dst[0], ir.BitFieldExtract(src0, src1, src2, is_signed)); +} + +void Translator::V_BFI_B32(const GcnInst& inst) { + const IR::U32 src0{GetSrc(inst.src[0])}; + const IR::U32 src1{GetSrc(inst.src[1])}; + const IR::U32 src2{GetSrc(inst.src[2])}; + SetDst(inst.dst[0], + ir.BitwiseOr(ir.BitwiseAnd(src0, src1), ir.BitwiseAnd(ir.BitwiseNot(src0), src2))); +} + +void Translator::V_FMA_F32(const GcnInst& inst) { + const IR::F32 src0{GetSrc(inst.src[0])}; + const IR::F32 src1{GetSrc(inst.src[1])}; + const IR::F32 src2{GetSrc(inst.src[2])}; + SetDst(inst.dst[0], ir.FPFma(src0, src1, src2)); +} + +void Translator::V_FMA_F64(const GcnInst& inst) { + const IR::F64 src0{GetSrc64(inst.src[0])}; + const IR::F64 src1{GetSrc64(inst.src[1])}; + const IR::F64 src2{GetSrc64(inst.src[2])}; + SetDst64(inst.dst[0], ir.FPFma(src0, src1, src2)); +} + +void Translator::V_MIN3_F32(const GcnInst& inst) { + const IR::F32 src0{GetSrc(inst.src[0])}; + const IR::F32 src1{GetSrc(inst.src[1])}; + const IR::F32 src2{GetSrc(inst.src[2])}; + SetDst(inst.dst[0], ir.FPMin(src0, ir.FPMin(src1, src2))); +} + +void Translator::V_MIN3_I32(const GcnInst& inst) { + const IR::U32 src0{GetSrc(inst.src[0])}; + const IR::U32 src1{GetSrc(inst.src[1])}; + const IR::U32 src2{GetSrc(inst.src[2])}; + SetDst(inst.dst[0], ir.SMin(src0, ir.SMin(src1, src2))); +} + +void Translator::V_MAX3_F32(const GcnInst& inst) { + const IR::F32 src0{GetSrc(inst.src[0])}; + const IR::F32 src1{GetSrc(inst.src[1])}; + const IR::F32 src2{GetSrc(inst.src[2])}; + SetDst(inst.dst[0], ir.FPMax(src0, ir.FPMax(src1, src2))); +} + +void Translator::V_MAX3_U32(bool is_signed, const GcnInst& inst) { + const IR::U32 src0{GetSrc(inst.src[0])}; + const IR::U32 src1{GetSrc(inst.src[1])}; + const IR::U32 src2{GetSrc(inst.src[2])}; + SetDst(inst.dst[0], ir.IMax(src0, ir.IMax(src1, src2, is_signed), is_signed)); +} + +void Translator::V_MED3_F32(const GcnInst& inst) { + const IR::F32 src0{GetSrc(inst.src[0])}; + const IR::F32 src1{GetSrc(inst.src[1])}; + const IR::F32 src2{GetSrc(inst.src[2])}; + const IR::F32 mmx = ir.FPMin(ir.FPMax(src0, src1), src2); + SetDst(inst.dst[0], ir.FPMax(ir.FPMin(src0, src1), mmx)); +} + +void Translator::V_MED3_I32(const GcnInst& inst) { + const IR::U32 src0{GetSrc(inst.src[0])}; + const IR::U32 src1{GetSrc(inst.src[1])}; + const IR::U32 src2{GetSrc(inst.src[2])}; + const IR::U32 mmx = ir.SMin(ir.SMax(src0, src1), src2); + SetDst(inst.dst[0], ir.SMax(ir.SMin(src0, src1), mmx)); +} + +void Translator::V_SAD(const GcnInst& inst) { + const IR::U32 abs_diff = ir.IAbs(ir.ISub(GetSrc(inst.src[0]), GetSrc(inst.src[1]))); + SetDst(inst.dst[0], ir.IAdd(abs_diff, GetSrc(inst.src[2]))); +} + +void Translator::V_SAD_U32(const GcnInst& inst) { + const IR::U32 src0{GetSrc(inst.src[0])}; + const IR::U32 src1{GetSrc(inst.src[1])}; + const IR::U32 src2{GetSrc(inst.src[2])}; + IR::U32 result; + if (src0.IsImmediate() && src0.U32() == 0U) { + result = src1; + } else if (src1.IsImmediate() && src1.U32() == 0U) { + result = src0; } else { - // v_mbcnt_lo_u32_b32 v2, -1, vX - // used combined with above to fetch lane id in non-compute stages - if (inst.src[0].field == OperandField::SignedConstIntNeg && inst.src[0].code == 193) { - SetDst(inst.dst[0], ir.LaneId()); - } - // v_mbcnt_lo_u32_b32 v20, exec_lo, vX - // used combined in above for append buffer indexing. - if (inst.src[0].field == OperandField::ExecLo) { - SetDst(inst.dst[0], ir.Imm32(0)); - } + const IR::U32 max{ir.IMax(src0, src1, false)}; + const IR::U32 min{ir.IMin(src0, src1, false)}; + result = ir.ISub(max, min); } + SetDst(inst.dst[0], ir.IAdd(result, src2)); } -void Translator::V_BFM_B32(const GcnInst& inst) { - // bitmask width - const IR::U32 src0{ir.BitFieldExtract(GetSrc(inst.src[0]), ir.Imm32(0), ir.Imm32(4))}; - // bitmask offset - const IR::U32 src1{ir.BitFieldExtract(GetSrc(inst.src[1]), ir.Imm32(0), ir.Imm32(4))}; - const IR::U32 ones = ir.ISub(ir.ShiftLeftLogical(ir.Imm32(1), src0), ir.Imm32(1)); - SetDst(inst.dst[0], ir.ShiftLeftLogical(ones, src1)); +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])}; + const IR::U32 src2{GetSrc(inst.src[2])}; + + const IR::U32 value_uint = ir.ConvertFToU(32, src0); + const IR::U32 offset = ir.ShiftLeftLogical(src1, ir.Imm32(3)); + SetDst(inst.dst[0], ir.BitFieldInsert(src2, value_uint, offset, ir.Imm32(8))); } -void Translator::V_FFBH_U32(const GcnInst& inst) { +void Translator::V_LSHL_B64(const GcnInst& inst) { + const IR::U64 src0{GetSrc64(inst.src[0])}; + const IR::U64 src1{GetSrc64(inst.src[1])}; + const IR::VectorReg dst_reg{inst.dst[0].code}; + if (src0.IsImmediate() && src0.U64() == -1) { + ir.SetVectorReg(dst_reg, ir.Imm32(0xFFFFFFFF)); + ir.SetVectorReg(dst_reg + 1, ir.Imm32(0xFFFFFFFF)); + return; + } + ASSERT_MSG(src0.IsImmediate() && src0.U64() == 0 && src1.IsImmediate() && src1.U64() == 0, + "V_LSHL_B64 with non-zero src0 or src1 is not supported"); + ir.SetVectorReg(dst_reg, ir.Imm32(0)); + ir.SetVectorReg(dst_reg + 1, ir.Imm32(0)); +} + +void Translator::V_MUL_F64(const GcnInst& inst) { + const IR::F64 src0{GetSrc64(inst.src[0])}; + const IR::F64 src1{GetSrc64(inst.src[1])}; + SetDst64(inst.dst[0], ir.FPMul(src0, src1)); +} + +void Translator::V_MAX_F64(const GcnInst& inst) { + const IR::F64 src0{GetSrc64(inst.src[0])}; + const IR::F64 src1{GetSrc64(inst.src[1])}; + SetDst64(inst.dst[0], ir.FPMax(src0, src1)); +} + +void Translator::V_MUL_LO_U32(const GcnInst& inst) { const IR::U32 src0{GetSrc(inst.src[0])}; - // Gcn wants the MSB position counting from the left, but SPIR-V counts from the rightmost (LSB) - // position - const IR::U32 msb_pos = ir.FindUMsb(src0); - const IR::U32 pos_from_left = ir.ISub(ir.Imm32(31), msb_pos); - // Select 0xFFFFFFFF if src0 was 0 - const IR::U1 cond = ir.INotEqual(src0, ir.Imm32(0)); - SetDst(inst.dst[0], IR::U32{ir.Select(cond, pos_from_left, ir.Imm32(~0U))}); + const IR::U32 src1{GetSrc(inst.src[1])}; + SetDst(inst.dst[0], ir.IMul(src0, src1)); +} + +void Translator::V_MUL_HI_U32(bool is_signed, const GcnInst& inst) { + const IR::U32 src0{GetSrc(inst.src[0])}; + const IR::U32 src1{GetSrc(inst.src[1])}; + const IR::U32 hi{ir.CompositeExtract(ir.IMulExt(src0, src1, is_signed), 1)}; + SetDst(inst.dst[0], hi); +} + +void Translator::V_MAD_U64_U32(const GcnInst& inst) { + const auto src0 = GetSrc(inst.src[0]); + const auto src1 = GetSrc(inst.src[1]); + const auto src2 = GetSrc64(inst.src[2]); + + // const IR::U64 mul_result = ir.UConvert(64, ir.IMul(src0, src1)); + const IR::U64 mul_result = + ir.PackUint2x32(ir.CompositeConstruct(ir.IMul(src0, src1), ir.Imm32(0U))); + const IR::U64 sum_result = ir.IAdd(mul_result, src2); + + SetDst64(inst.dst[0], sum_result); + + const IR::U1 less_src0 = ir.ILessThan(sum_result, mul_result, false); + const IR::U1 less_src1 = ir.ILessThan(sum_result, src2, false); + const IR::U1 did_overflow = ir.LogicalOr(less_src0, less_src1); + ir.SetVcc(did_overflow); } // TODO: add range analysis pass to hopefully put an upper bound on m0, and only select one of @@ -1045,29 +1136,4 @@ void Translator::VMovRelDHelper(u32 dst_vgprno, const IR::U32 src_val, const IR: } } -void Translator::V_MOVRELS_B32(const GcnInst& inst) { - u32 src_vgprno = inst.src[0].code - static_cast(IR::VectorReg::V0); - const IR::U32 m0 = ir.GetM0(); - - const IR::U32 src_val = VMovRelSHelper(src_vgprno, m0); - SetDst(inst.dst[0], src_val); -} - -void Translator::V_MOVRELD_B32(const GcnInst& inst) { - const IR::U32 src_val{GetSrc(inst.src[0])}; - u32 dst_vgprno = inst.dst[0].code - static_cast(IR::VectorReg::V0); - IR::U32 m0 = ir.GetM0(); - - VMovRelDHelper(dst_vgprno, src_val, m0); -} - -void Translator::V_MOVRELSD_B32(const GcnInst& inst) { - u32 src_vgprno = inst.src[0].code - static_cast(IR::VectorReg::V0); - u32 dst_vgprno = inst.dst[0].code - static_cast(IR::VectorReg::V0); - IR::U32 m0 = ir.GetM0(); - - const IR::U32 src_val = VMovRelSHelper(src_vgprno, m0); - VMovRelDHelper(dst_vgprno, src_val, m0); -} - } // namespace Shader::Gcn diff --git a/src/shader_recompiler/frontend/translate/vector_interpolation.cpp b/src/shader_recompiler/frontend/translate/vector_interpolation.cpp index c12ae8f57..431cb2f04 100644 --- a/src/shader_recompiler/frontend/translate/vector_interpolation.cpp +++ b/src/shader_recompiler/frontend/translate/vector_interpolation.cpp @@ -5,22 +5,9 @@ namespace Shader::Gcn { -void Translator::V_INTERP_P2_F32(const GcnInst& inst) { - const IR::VectorReg dst_reg{inst.dst[0].code}; - auto& attr = runtime_info.fs_info.inputs.at(inst.control.vintrp.attr); - const IR::Attribute attrib{IR::Attribute::Param0 + attr.param_index}; - ir.SetVectorReg(dst_reg, ir.GetAttribute(attrib, inst.control.vintrp.chan)); -} - -void Translator::V_INTERP_MOV_F32(const GcnInst& inst) { - const IR::VectorReg dst_reg{inst.dst[0].code}; - auto& attr = runtime_info.fs_info.inputs.at(inst.control.vintrp.attr); - const IR::Attribute attrib{IR::Attribute::Param0 + attr.param_index}; - ir.SetVectorReg(dst_reg, ir.GetAttribute(attrib, inst.control.vintrp.chan)); -} - void Translator::EmitVectorInterpolation(const GcnInst& inst) { switch (inst.opcode) { + // VINTRP case Opcode::V_INTERP_P1_F32: return; case Opcode::V_INTERP_P2_F32: @@ -32,4 +19,18 @@ void Translator::EmitVectorInterpolation(const GcnInst& inst) { } } +// VINTRP + +void Translator::V_INTERP_P2_F32(const GcnInst& inst) { + auto& attr = runtime_info.fs_info.inputs.at(inst.control.vintrp.attr); + const IR::Attribute attrib{IR::Attribute::Param0 + attr.param_index}; + SetDst(inst.dst[0], ir.GetAttribute(attrib, inst.control.vintrp.chan)); +} + +void Translator::V_INTERP_MOV_F32(const GcnInst& inst) { + auto& attr = runtime_info.fs_info.inputs.at(inst.control.vintrp.attr); + const IR::Attribute attrib{IR::Attribute::Param0 + attr.param_index}; + SetDst(inst.dst[0], ir.GetAttribute(attrib, inst.control.vintrp.chan)); +} + } // namespace Shader::Gcn diff --git a/src/shader_recompiler/frontend/translate/vector_memory.cpp b/src/shader_recompiler/frontend/translate/vector_memory.cpp index e0f35fb90..7ecc2e762 100644 --- a/src/shader_recompiler/frontend/translate/vector_memory.cpp +++ b/src/shader_recompiler/frontend/translate/vector_memory.cpp @@ -7,55 +7,7 @@ namespace Shader::Gcn { void Translator::EmitVectorMemory(const GcnInst& inst) { switch (inst.opcode) { - case Opcode::IMAGE_SAMPLE_LZ_O: - case Opcode::IMAGE_SAMPLE_O: - case Opcode::IMAGE_SAMPLE_C: - case Opcode::IMAGE_SAMPLE_C_LZ: - case Opcode::IMAGE_SAMPLE_LZ: - case Opcode::IMAGE_SAMPLE: - case Opcode::IMAGE_SAMPLE_L: - case Opcode::IMAGE_SAMPLE_L_O: - case Opcode::IMAGE_SAMPLE_C_O: - case Opcode::IMAGE_SAMPLE_B: - case Opcode::IMAGE_SAMPLE_C_LZ_O: - case Opcode::IMAGE_SAMPLE_D: - case Opcode::IMAGE_SAMPLE_CD: - return IMAGE_SAMPLE(inst); - case Opcode::IMAGE_GATHER4_LZ: - case Opcode::IMAGE_GATHER4_C: - case Opcode::IMAGE_GATHER4_C_LZ: - case Opcode::IMAGE_GATHER4_LZ_O: - return IMAGE_GATHER(inst); - case Opcode::IMAGE_ATOMIC_ADD: - return IMAGE_ATOMIC(AtomicOp::Add, inst); - case Opcode::IMAGE_ATOMIC_AND: - return IMAGE_ATOMIC(AtomicOp::And, inst); - case Opcode::IMAGE_ATOMIC_OR: - return IMAGE_ATOMIC(AtomicOp::Or, inst); - case Opcode::IMAGE_ATOMIC_XOR: - return IMAGE_ATOMIC(AtomicOp::Xor, inst); - case Opcode::IMAGE_ATOMIC_UMAX: - return IMAGE_ATOMIC(AtomicOp::Umax, inst); - case Opcode::IMAGE_ATOMIC_SMAX: - return IMAGE_ATOMIC(AtomicOp::Smax, inst); - case Opcode::IMAGE_ATOMIC_UMIN: - return IMAGE_ATOMIC(AtomicOp::Umin, inst); - case Opcode::IMAGE_ATOMIC_SMIN: - return IMAGE_ATOMIC(AtomicOp::Smin, inst); - case Opcode::IMAGE_ATOMIC_INC: - return IMAGE_ATOMIC(AtomicOp::Inc, inst); - case Opcode::IMAGE_ATOMIC_DEC: - return IMAGE_ATOMIC(AtomicOp::Dec, inst); - case Opcode::IMAGE_GET_LOD: - return IMAGE_GET_LOD(inst); - case Opcode::IMAGE_STORE: - return IMAGE_STORE(inst); - case Opcode::IMAGE_LOAD_MIP: - return IMAGE_LOAD(true, inst); - case Opcode::IMAGE_LOAD: - return IMAGE_LOAD(false, inst); - case Opcode::IMAGE_GET_RESINFO: - return IMAGE_GET_RESINFO(inst); + // MUBUF / MTBUF // Buffer load operations case Opcode::TBUFFER_LOAD_FORMAT_X: @@ -137,239 +89,74 @@ void Translator::EmitVectorMemory(const GcnInst& inst) { case Opcode::BUFFER_ATOMIC_DEC: return BUFFER_ATOMIC(AtomicOp::Dec, inst); + // MIMG + // Image load operations + case Opcode::IMAGE_LOAD: + return IMAGE_LOAD(false, inst); + case Opcode::IMAGE_LOAD_MIP: + return IMAGE_LOAD(true, inst); + + // Buffer store operations + case Opcode::IMAGE_STORE: + return IMAGE_STORE(inst); + + // Image misc operations + case Opcode::IMAGE_GET_RESINFO: + return IMAGE_GET_RESINFO(inst); + + // Image atomic operations + case Opcode::IMAGE_ATOMIC_ADD: + return IMAGE_ATOMIC(AtomicOp::Add, inst); + case Opcode::IMAGE_ATOMIC_SMIN: + return IMAGE_ATOMIC(AtomicOp::Smin, inst); + case Opcode::IMAGE_ATOMIC_UMIN: + return IMAGE_ATOMIC(AtomicOp::Umin, inst); + case Opcode::IMAGE_ATOMIC_SMAX: + return IMAGE_ATOMIC(AtomicOp::Smax, inst); + case Opcode::IMAGE_ATOMIC_UMAX: + return IMAGE_ATOMIC(AtomicOp::Umax, inst); + case Opcode::IMAGE_ATOMIC_AND: + return IMAGE_ATOMIC(AtomicOp::And, inst); + case Opcode::IMAGE_ATOMIC_OR: + return IMAGE_ATOMIC(AtomicOp::Or, inst); + case Opcode::IMAGE_ATOMIC_XOR: + return IMAGE_ATOMIC(AtomicOp::Xor, inst); + case Opcode::IMAGE_ATOMIC_INC: + return IMAGE_ATOMIC(AtomicOp::Inc, inst); + case Opcode::IMAGE_ATOMIC_DEC: + return IMAGE_ATOMIC(AtomicOp::Dec, inst); + + case Opcode::IMAGE_SAMPLE: + case Opcode::IMAGE_SAMPLE_D: + case Opcode::IMAGE_SAMPLE_L: + case Opcode::IMAGE_SAMPLE_B: + case Opcode::IMAGE_SAMPLE_LZ: + case Opcode::IMAGE_SAMPLE_C: + case Opcode::IMAGE_SAMPLE_C_LZ: + case Opcode::IMAGE_SAMPLE_O: + case Opcode::IMAGE_SAMPLE_L_O: + case Opcode::IMAGE_SAMPLE_LZ_O: + case Opcode::IMAGE_SAMPLE_C_O: + case Opcode::IMAGE_SAMPLE_C_LZ_O: + case Opcode::IMAGE_SAMPLE_CD: + return IMAGE_SAMPLE(inst); + + // Image gather operations + case Opcode::IMAGE_GATHER4_LZ: + case Opcode::IMAGE_GATHER4_C: + case Opcode::IMAGE_GATHER4_C_LZ: + case Opcode::IMAGE_GATHER4_LZ_O: + return IMAGE_GATHER(inst); + + // Image misc operations + case Opcode::IMAGE_GET_LOD: + return IMAGE_GET_LOD(inst); + default: LogMissingOpcode(inst); } } -void Translator::IMAGE_GET_RESINFO(const GcnInst& inst) { - IR::VectorReg dst_reg{inst.dst[0].code}; - const IR::ScalarReg tsharp_reg{inst.src[2].code * 4}; - const auto flags = ImageResFlags(inst.control.mimg.dmask); - const bool has_mips = flags.test(ImageResComponent::MipCount); - const IR::U32 lod = ir.GetVectorReg(IR::VectorReg(inst.src[0].code)); - const IR::Value tsharp = ir.GetScalarReg(tsharp_reg); - const IR::Value size = ir.ImageQueryDimension(tsharp, lod, ir.Imm1(has_mips)); - - if (flags.test(ImageResComponent::Width)) { - ir.SetVectorReg(dst_reg++, IR::U32{ir.CompositeExtract(size, 0)}); - } - if (flags.test(ImageResComponent::Height)) { - ir.SetVectorReg(dst_reg++, IR::U32{ir.CompositeExtract(size, 1)}); - } - if (flags.test(ImageResComponent::Depth)) { - ir.SetVectorReg(dst_reg++, IR::U32{ir.CompositeExtract(size, 2)}); - } - if (has_mips) { - ir.SetVectorReg(dst_reg++, IR::U32{ir.CompositeExtract(size, 3)}); - } -} - -void Translator::IMAGE_SAMPLE(const GcnInst& inst) { - const auto& mimg = inst.control.mimg; - IR::VectorReg addr_reg{inst.src[0].code}; - IR::VectorReg dest_reg{inst.dst[0].code}; - const IR::ScalarReg tsharp_reg{inst.src[2].code * 4}; - 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); - - // 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); - }(); - - for (u32 i = 0; i < 4; i++) { - if (((mimg.dmask >> i) & 1) == 0) { - continue; - } - IR::F32 value; - if (flags.test(MimgModifier::Pcf)) { - value = i < 3 ? IR::F32{texel} : ir.Imm32(1.0f); - } else { - value = IR::F32{ir.CompositeExtract(texel, i)}; - } - ir.SetVectorReg(dest_reg++, value); - } -} - -void Translator::IMAGE_GATHER(const GcnInst& inst) { - const auto& mimg = inst.control.mimg; - if (mimg.da) { - LOG_WARNING(Render_Vulkan, "Image instruction declares an array"); - } - - IR::VectorReg addr_reg{inst.src[0].code}; - IR::VectorReg dest_reg{inst.dst[0].code}; - const IR::ScalarReg tsharp_reg{inst.src[2].code * 4}; - 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); - - // 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"); - for (u32 i = 0; i < 4; i++) { - const IR::F32 value = IR::F32{ir.CompositeExtract(texel, i)}; - ir.SetVectorReg(dest_reg++, value); - } -} - -void Translator::IMAGE_LOAD(bool has_mip, const GcnInst& inst) { - const auto& mimg = inst.control.mimg; - IR::VectorReg addr_reg{inst.src[0].code}; - IR::VectorReg dest_reg{inst.dst[0].code}; - const IR::ScalarReg tsharp_reg{inst.src[2].code * 4}; - - const IR::Value handle = ir.GetScalarReg(tsharp_reg); - 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)); - - IR::TextureInstInfo info{}; - info.explicit_lod.Assign(has_mip); - const IR::Value texel = ir.ImageFetch(handle, body, {}, {}, {}, info); - - for (u32 i = 0; i < 4; i++) { - if (((mimg.dmask >> i) & 1) == 0) { - continue; - } - IR::F32 value = IR::F32{ir.CompositeExtract(texel, i)}; - ir.SetVectorReg(dest_reg++, value); - } -} - -void Translator::IMAGE_STORE(const GcnInst& inst) { - const auto& mimg = inst.control.mimg; - IR::VectorReg addr_reg{inst.src[0].code}; - IR::VectorReg data_reg{inst.dst[0].code}; - const IR::ScalarReg tsharp_reg{inst.src[2].code * 4}; - - const IR::Value handle = ir.GetScalarReg(tsharp_reg); - 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)); - - boost::container::static_vector comps; - for (u32 i = 0; i < 4; i++) { - if (((mimg.dmask >> i) & 1) == 0) { - comps.push_back(ir.Imm32(0.f)); - continue; - } - comps.push_back(ir.GetVectorReg(data_reg++)); - } - const IR::Value value = ir.CompositeConstruct(comps[0], comps[1], comps[2], comps[3]); - ir.ImageWrite(handle, body, value, {}); -} - void Translator::BUFFER_LOAD(u32 num_dwords, bool is_typed, const GcnInst& inst) { const auto& mtbuf = inst.control.mtbuf; const IR::VectorReg vaddr{inst.src[0].code}; @@ -588,19 +375,77 @@ void Translator::BUFFER_ATOMIC(AtomicOp op, const GcnInst& inst) { } } -void Translator::IMAGE_GET_LOD(const GcnInst& inst) { +// Image Memory +// MIMG + +void Translator::IMAGE_LOAD(bool has_mip, const GcnInst& inst) { const auto& mimg = inst.control.mimg; - IR::VectorReg dst_reg{inst.dst[0].code}; IR::VectorReg addr_reg{inst.src[0].code}; + IR::VectorReg dest_reg{inst.dst[0].code}; const IR::ScalarReg tsharp_reg{inst.src[2].code * 4}; const IR::Value handle = ir.GetScalarReg(tsharp_reg); - 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 IR::Value lod = ir.ImageQueryLod(handle, body, {}); - ir.SetVectorReg(dst_reg++, IR::F32{ir.CompositeExtract(lod, 0)}); - ir.SetVectorReg(dst_reg++, IR::F32{ir.CompositeExtract(lod, 1)}); + 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)); + + IR::TextureInstInfo info{}; + info.explicit_lod.Assign(has_mip); + const IR::Value texel = ir.ImageFetch(handle, body, {}, {}, {}, info); + + for (u32 i = 0; i < 4; i++) { + if (((mimg.dmask >> i) & 1) == 0) { + continue; + } + IR::F32 value = IR::F32{ir.CompositeExtract(texel, i)}; + ir.SetVectorReg(dest_reg++, value); + } +} + +void Translator::IMAGE_STORE(const GcnInst& inst) { + const auto& mimg = inst.control.mimg; + IR::VectorReg addr_reg{inst.src[0].code}; + IR::VectorReg data_reg{inst.dst[0].code}; + const IR::ScalarReg tsharp_reg{inst.src[2].code * 4}; + + const IR::Value handle = ir.GetScalarReg(tsharp_reg); + 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)); + + boost::container::static_vector comps; + for (u32 i = 0; i < 4; i++) { + if (((mimg.dmask >> i) & 1) == 0) { + comps.push_back(ir.Imm32(0.f)); + continue; + } + comps.push_back(ir.GetVectorReg(data_reg++)); + } + const IR::Value value = ir.CompositeConstruct(comps[0], comps[1], comps[2], comps[3]); + ir.ImageWrite(handle, body, value, {}); +} + +void Translator::IMAGE_GET_RESINFO(const GcnInst& inst) { + IR::VectorReg dst_reg{inst.dst[0].code}; + const IR::ScalarReg tsharp_reg{inst.src[2].code * 4}; + const auto flags = ImageResFlags(inst.control.mimg.dmask); + const bool has_mips = flags.test(ImageResComponent::MipCount); + const IR::U32 lod = ir.GetVectorReg(IR::VectorReg(inst.src[0].code)); + const IR::Value tsharp = ir.GetScalarReg(tsharp_reg); + const IR::Value size = ir.ImageQueryDimension(tsharp, lod, ir.Imm1(has_mips)); + + if (flags.test(ImageResComponent::Width)) { + ir.SetVectorReg(dst_reg++, IR::U32{ir.CompositeExtract(size, 0)}); + } + if (flags.test(ImageResComponent::Height)) { + ir.SetVectorReg(dst_reg++, IR::U32{ir.CompositeExtract(size, 1)}); + } + if (flags.test(ImageResComponent::Depth)) { + ir.SetVectorReg(dst_reg++, IR::U32{ir.CompositeExtract(size, 2)}); + } + if (has_mips) { + ir.SetVectorReg(dst_reg++, IR::U32{ir.CompositeExtract(size, 3)}); + } } void Translator::IMAGE_ATOMIC(AtomicOp op, const GcnInst& inst) { @@ -647,4 +492,179 @@ void Translator::IMAGE_ATOMIC(AtomicOp op, const GcnInst& inst) { } } +void Translator::IMAGE_SAMPLE(const GcnInst& inst) { + const auto& mimg = inst.control.mimg; + IR::VectorReg addr_reg{inst.src[0].code}; + IR::VectorReg dest_reg{inst.dst[0].code}; + const IR::ScalarReg tsharp_reg{inst.src[2].code * 4}; + 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); + }(); + + for (u32 i = 0; i < 4; i++) { + if (((mimg.dmask >> i) & 1) == 0) { + continue; + } + IR::F32 value; + if (flags.test(MimgModifier::Pcf)) { + value = i < 3 ? IR::F32{texel} : ir.Imm32(1.0f); + } else { + value = IR::F32{ir.CompositeExtract(texel, i)}; + } + ir.SetVectorReg(dest_reg++, value); + } +} + +void Translator::IMAGE_GATHER(const GcnInst& inst) { + const auto& mimg = inst.control.mimg; + if (mimg.da) { + LOG_WARNING(Render_Vulkan, "Image instruction declares an array"); + } + + IR::VectorReg addr_reg{inst.src[0].code}; + IR::VectorReg dest_reg{inst.dst[0].code}; + const IR::ScalarReg tsharp_reg{inst.src[2].code * 4}; + 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"); + for (u32 i = 0; i < 4; i++) { + const IR::F32 value = IR::F32{ir.CompositeExtract(texel, i)}; + ir.SetVectorReg(dest_reg++, value); + } +} + +void Translator::IMAGE_GET_LOD(const GcnInst& inst) { + const auto& mimg = inst.control.mimg; + IR::VectorReg dst_reg{inst.dst[0].code}; + IR::VectorReg addr_reg{inst.src[0].code}; + const IR::ScalarReg tsharp_reg{inst.src[2].code * 4}; + + const IR::Value handle = ir.GetScalarReg(tsharp_reg); + 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 IR::Value lod = ir.ImageQueryLod(handle, body, {}); + ir.SetVectorReg(dst_reg++, IR::F32{ir.CompositeExtract(lod, 0)}); + ir.SetVectorReg(dst_reg++, IR::F32{ir.CompositeExtract(lod, 1)}); +} + } // namespace Shader::Gcn diff --git a/src/shader_recompiler/info.h b/src/shader_recompiler/info.h index d8282bf49..739214ec9 100644 --- a/src/shader_recompiler/info.h +++ b/src/shader_recompiler/info.h @@ -7,6 +7,7 @@ #include #include "common/assert.h" #include "common/types.h" +#include "shader_recompiler/backend/bindings.h" #include "shader_recompiler/ir/attribute.h" #include "shader_recompiler/ir/reg.h" #include "shader_recompiler/ir/type.h" @@ -64,9 +65,10 @@ struct ImageResource { u32 dword_offset; AmdGpu::ImageType type; AmdGpu::NumberFormat nfmt; - bool is_storage; - bool is_depth; + bool is_storage{}; + bool is_depth{}; bool is_atomic{}; + bool is_array{}; constexpr AmdGpu::Image GetSharp(const Info& info) const noexcept; }; @@ -84,17 +86,21 @@ struct SamplerResource { using SamplerResourceList = boost::container::small_vector; struct PushData { - static constexpr size_t BufOffsetIndex = 2; + static constexpr u32 BufOffsetIndex = 2; + static constexpr u32 UdRegsIndex = 4; u32 step0; u32 step1; - std::array buf_offsets; + std::array buf_offsets; + std::array ud_regs; void AddOffset(u32 binding, u32 offset) { ASSERT(offset < 256 && binding < buf_offsets.size()); buf_offsets[binding] = offset; } }; +static_assert(sizeof(PushData) <= 128, + "PushData size is greater than minimum size guaranteed by Vulkan spec"); /** * Contains general information generated by the shader recompiler for an input program. @@ -144,6 +150,24 @@ struct Info { AttributeFlags loads{}; AttributeFlags stores{}; + struct UserDataMask { + void Set(IR::ScalarReg reg) noexcept { + mask |= 1 << static_cast(reg); + } + + u32 Index(IR::ScalarReg reg) const noexcept { + const u32 reg_mask = (1 << static_cast(reg)) - 1; + return std::popcount(mask & reg_mask); + } + + u32 NumRegs() const noexcept { + return std::popcount(mask); + } + + u32 mask; + }; + UserDataMask ud_mask{}; + s8 vertex_offset_sgpr = -1; s8 instance_offset_sgpr = -1; @@ -171,6 +195,7 @@ struct Info { bool uses_fp64{}; bool uses_step_rates{}; bool translation_failed{}; // indicates that shader has unsupported instructions + u8 mrt_mask{0u}; explicit Info(Stage stage_, ShaderParams params) : stage{stage_}, pgm_hash{params.hash}, pgm_base{params.Base()}, @@ -188,11 +213,23 @@ struct Info { return data; } - size_t NumBindings() const noexcept { - return buffers.size() + texture_buffers.size() + images.size() + samplers.size(); + void PushUd(Backend::Bindings& bnd, PushData& push) const { + u32 mask = ud_mask.mask; + while (mask) { + const u32 index = std::countr_zero(mask); + ASSERT(bnd.user_data < NumUserDataRegs && index < NumUserDataRegs); + mask &= ~(1U << index); + push.ud_regs[bnd.user_data++] = user_data[index]; + } } - [[nodiscard]] std::pair GetDrawOffsets() const noexcept { + void AddBindings(Backend::Bindings& bnd) const { + bnd.buffer += buffers.size() + texture_buffers.size(); + bnd.unified += bnd.buffer + images.size() + samplers.size(); + bnd.user_data += ud_mask.NumRegs(); + } + + [[nodiscard]] std::pair GetDrawOffsets() const { u32 vertex_offset = 0; u32 instance_offset = 0; if (vertex_offset_sgpr != -1) { diff --git a/src/shader_recompiler/ir/ir_emitter.cpp b/src/shader_recompiler/ir/ir_emitter.cpp index ce809514d..a7edb6d9c 100644 --- a/src/shader_recompiler/ir/ir_emitter.cpp +++ b/src/shader_recompiler/ir/ir_emitter.cpp @@ -1079,6 +1079,10 @@ U32 IREmitter::IAbs(const U32& value) { } U32U64 IREmitter::ShiftLeftLogical(const U32U64& base, const U32& shift) { + if (shift.IsImmediate() && shift.U32() == 0) { + return base; + } + switch (base.Type()) { case Type::U32: return Inst(Opcode::ShiftLeftLogical32, base, shift); @@ -1090,6 +1094,10 @@ U32U64 IREmitter::ShiftLeftLogical(const U32U64& base, const U32& shift) { } U32U64 IREmitter::ShiftRightLogical(const U32U64& base, const U32& shift) { + if (shift.IsImmediate() && shift.U32() == 0) { + return base; + } + switch (base.Type()) { case Type::U32: return Inst(Opcode::ShiftRightLogical32, base, shift); @@ -1101,6 +1109,10 @@ U32U64 IREmitter::ShiftRightLogical(const U32U64& base, const U32& shift) { } U32U64 IREmitter::ShiftRightArithmetic(const U32U64& base, const U32& shift) { + if (shift.IsImmediate() && shift.U32() == 0) { + return base; + } + switch (base.Type()) { case Type::U32: return Inst(Opcode::ShiftRightArithmetic32, base, shift); diff --git a/src/shader_recompiler/ir/passes/constant_propagation_pass.cpp b/src/shader_recompiler/ir/passes/constant_propagation_pass.cpp index 87a069338..775aed5b3 100644 --- a/src/shader_recompiler/ir/passes/constant_propagation_pass.cpp +++ b/src/shader_recompiler/ir/passes/constant_propagation_pass.cpp @@ -278,6 +278,12 @@ void ConstantPropagation(IR::Block& block, IR::Inst& inst) { case IR::Opcode::FPCmpClass32: FoldCmpClass(inst); return; + case IR::Opcode::ShiftLeftLogical32: + FoldWhenAllImmediates(inst, [](u32 a, u32 b) { return static_cast(a << b); }); + return; + case IR::Opcode::ShiftRightLogical32: + FoldWhenAllImmediates(inst, [](u32 a, u32 b) { return static_cast(a >> b); }); + return; case IR::Opcode::ShiftRightArithmetic32: FoldWhenAllImmediates(inst, [](s32 a, s32 b) { return static_cast(a >> b); }); return; @@ -347,7 +353,6 @@ void ConstantPropagation(IR::Block& block, IR::Inst& inst) { return; case IR::Opcode::INotEqual: FoldWhenAllImmediates(inst, [](u32 a, u32 b) { return a != b; }); - FoldBooleanConvert(inst); return; case IR::Opcode::BitwiseAnd32: FoldWhenAllImmediates(inst, [](u32 a, u32 b) { return 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 f1fc14d02..db0d75f0c 100644 --- a/src/shader_recompiler/ir/passes/resource_tracking_pass.cpp +++ b/src/shader_recompiler/ir/passes/resource_tracking_pass.cpp @@ -200,9 +200,10 @@ public: u32 Add(const ImageResource& desc) { const u32 index{Add(image_resources, desc, [&desc](const auto& existing) { return desc.sgpr_base == existing.sgpr_base && - desc.dword_offset == existing.dword_offset && desc.type == existing.type && - desc.is_storage == existing.is_storage; + desc.dword_offset == existing.dword_offset; })}; + auto& image = image_resources[index]; + image.is_storage |= desc.is_storage; return index; } @@ -441,18 +442,29 @@ void PatchTextureBufferInstruction(IR::Block& block, IR::Inst& inst, Info& info, } IR::Value PatchCubeCoord(IR::IREmitter& ir, const IR::Value& s, const IR::Value& t, - const IR::Value& z, bool is_storage) { + const IR::Value& z, bool is_storage, bool is_array) { // When cubemap is written with imageStore it is treated like 2DArray. if (is_storage) { return ir.CompositeConstruct(s, t, z); } + + ASSERT(s.Type() == IR::Type::F32); // in case of fetched image need to adjust the code below + // We need to fix x and y coordinate, // because the s and t coordinate will be scaled and plus 1.5 by v_madak_f32. // We already force the scale value to be 1.0 when handling v_cubema_f32, // here we subtract 1.5 to recover the original value. const IR::Value x = ir.FPSub(IR::F32{s}, ir.Imm32(1.5f)); const IR::Value y = ir.FPSub(IR::F32{t}, ir.Imm32(1.5f)); - return ir.CompositeConstruct(x, y, z); + if (is_array) { + const IR::U32 array_index = ir.ConvertFToU(32, IR::F32{z}); + const IR::U32 face_id = ir.BitwiseAnd(array_index, ir.Imm32(7u)); + const IR::U32 slice_id = ir.ShiftRightLogical(array_index, ir.Imm32(3u)); + return ir.CompositeConstruct(x, y, ir.ConvertIToF(32, 32, false, face_id), + ir.ConvertIToF(32, 32, false, slice_id)); + } else { + return ir.CompositeConstruct(x, y, z); + } } void PatchImageInstruction(IR::Block& block, IR::Inst& inst, Info& info, Descriptors& descriptors) { @@ -481,14 +493,16 @@ void PatchImageInstruction(IR::Block& block, IR::Inst& inst, Info& info, Descrip } ASSERT(image.GetType() != AmdGpu::ImageType::Invalid); const bool is_storage = IsImageStorageInstruction(inst); + const auto type = image.IsPartialCubemap() ? AmdGpu::ImageType::Color2DArray : image.GetType(); u32 image_binding = descriptors.Add(ImageResource{ .sgpr_base = tsharp.sgpr_base, .dword_offset = tsharp.dword_offset, - .type = image.GetType(), + .type = type, .nfmt = static_cast(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 @@ -545,7 +559,8 @@ void PatchImageInstruction(IR::Block& block, IR::Inst& inst, Info& info, Descrip case AmdGpu::ImageType::Color3D: // x, y, z return {ir.CompositeConstruct(body->Arg(0), body->Arg(1), body->Arg(2)), body->Arg(3)}; case AmdGpu::ImageType::Cube: // x, y, face - return {PatchCubeCoord(ir, body->Arg(0), body->Arg(1), body->Arg(2), is_storage), + return {PatchCubeCoord(ir, body->Arg(0), body->Arg(1), body->Arg(2), is_storage, + inst_info.is_array), body->Arg(3)}; default: UNREACHABLE_MSG("Unknown image type {}", image.GetType()); @@ -584,7 +599,8 @@ void PatchImageInstruction(IR::Block& block, IR::Inst& inst, Info& info, Descrip } } if (inst_info.has_derivatives) { - ASSERT_MSG(image.GetType() == AmdGpu::ImageType::Color2D, + 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) { diff --git a/src/shader_recompiler/ir/passes/shader_info_collection_pass.cpp b/src/shader_recompiler/ir/passes/shader_info_collection_pass.cpp index 7251473d1..e995852d5 100644 --- a/src/shader_recompiler/ir/passes/shader_info_collection_pass.cpp +++ b/src/shader_recompiler/ir/passes/shader_info_collection_pass.cpp @@ -8,14 +8,15 @@ namespace Shader::Optimization { void Visit(Info& info, IR::Inst& inst) { switch (inst.GetOpcode()) { case IR::Opcode::GetAttribute: - case IR::Opcode::GetAttributeU32: { + case IR::Opcode::GetAttributeU32: info.loads.Set(inst.Arg(0).Attribute(), inst.Arg(1).U32()); break; - } - case IR::Opcode::SetAttribute: { + case IR::Opcode::SetAttribute: info.stores.Set(inst.Arg(0).Attribute(), inst.Arg(2).U32()); break; - } + case IR::Opcode::GetUserData: + info.ud_mask.Set(inst.Arg(0).ScalarReg()); + break; case IR::Opcode::LoadSharedU32: case IR::Opcode::LoadSharedU64: case IR::Opcode::WriteSharedU32: diff --git a/src/shader_recompiler/ir/passes/ssa_rewrite_pass.cpp b/src/shader_recompiler/ir/passes/ssa_rewrite_pass.cpp index 54dce0355..df73c1bc8 100644 --- a/src/shader_recompiler/ir/passes/ssa_rewrite_pass.cpp +++ b/src/shader_recompiler/ir/passes/ssa_rewrite_pass.cpp @@ -80,10 +80,10 @@ struct DefTable { } const IR::Value& Def(IR::Block* block, ThreadBitScalar variable) { - return block->ssa_sreg_values[RegIndex(variable.sgpr)]; + return block->ssa_sbit_values[RegIndex(variable.sgpr)]; } void SetDef(IR::Block* block, ThreadBitScalar variable, const IR::Value& value) { - block->ssa_sreg_values[RegIndex(variable.sgpr)] = value; + block->ssa_sbit_values[RegIndex(variable.sgpr)] = value; } const IR::Value& Def(IR::Block* block, SccFlagTag) { diff --git a/src/shader_recompiler/ir/reg.h b/src/shader_recompiler/ir/reg.h index fba04f33e..4783d08e5 100644 --- a/src/shader_recompiler/ir/reg.h +++ b/src/shader_recompiler/ir/reg.h @@ -59,6 +59,7 @@ union TextureInstInfo { BitField<5, 1, u32> has_offset; BitField<6, 2, u32> gather_comp; BitField<8, 1, u32> has_derivatives; + BitField<9, 1, u32> is_array; }; union BufferInstInfo { diff --git a/src/shader_recompiler/specialization.h b/src/shader_recompiler/specialization.h index bbcafdb86..0a3a696bc 100644 --- a/src/shader_recompiler/specialization.h +++ b/src/shader_recompiler/specialization.h @@ -6,6 +6,7 @@ #include #include "common/types.h" +#include "shader_recompiler/backend/bindings.h" #include "shader_recompiler/info.h" namespace Shader { @@ -45,11 +46,11 @@ struct StageSpecialization { boost::container::small_vector buffers; boost::container::small_vector tex_buffers; boost::container::small_vector images; - u32 start_binding{}; + Backend::Bindings start{}; explicit StageSpecialization(const Shader::Info& info_, RuntimeInfo runtime_info_, - u32 start_binding_) - : info{&info_}, runtime_info{runtime_info_}, start_binding{start_binding_} { + Backend::Bindings start_) + : info{&info_}, runtime_info{runtime_info_}, start{start_} { u32 binding{}; ForEachSharp(binding, buffers, info->buffers, [](auto& spec, const auto& desc, AmdGpu::Buffer sharp) { @@ -62,7 +63,8 @@ struct StageSpecialization { }); ForEachSharp(binding, images, info->images, [](auto& spec, const auto& desc, AmdGpu::Image sharp) { - spec.type = sharp.GetType(); + spec.type = sharp.IsPartialCubemap() ? AmdGpu::ImageType::Color2DArray + : sharp.GetType(); spec.is_integer = AmdGpu::IsInteger(sharp.GetNumberFmt()); }); } @@ -81,7 +83,7 @@ struct StageSpecialization { } bool operator==(const StageSpecialization& other) const { - if (start_binding != other.start_binding) { + if (start != other.start) { return false; } if (runtime_info != other.runtime_info) { diff --git a/src/video_core/amdgpu/resource.h b/src/video_core/amdgpu/resource.h index 1721c1aea..fc572a04b 100644 --- a/src/video_core/amdgpu/resource.h +++ b/src/video_core/amdgpu/resource.h @@ -66,7 +66,7 @@ struct Buffer { } u32 GetStride() const noexcept { - return stride == 0 ? 1U : stride; + return stride; } u32 NumDwords() const noexcept { @@ -74,7 +74,7 @@ struct Buffer { } u32 GetSize() const noexcept { - return GetStride() * num_records; + return stride == 0 ? num_records : (stride * num_records); } }; static_assert(sizeof(Buffer) == 16); // 128bits @@ -238,10 +238,15 @@ struct Image { return pitch + 1; } - u32 NumLayers() const { + u32 NumLayers(bool is_array) const { u32 slices = GetType() == ImageType::Color3D ? 1 : depth + 1; if (GetType() == ImageType::Cube) { - slices *= 6; + if (is_array) { + slices = last_array + 1; + ASSERT(slices % 6 == 0); + } else { + slices = 6; + } } if (pow2pad) { slices = std::bit_ceil(slices); @@ -282,6 +287,11 @@ struct Image { bool IsTiled() const { return GetTilingMode() != TilingMode::Display_Linear; } + + bool IsPartialCubemap() const { + const auto viewed_slice = last_array - base_array + 1; + return GetType() == ImageType::Cube && viewed_slice < 6; + } }; static_assert(sizeof(Image) == 32); // 256bits diff --git a/src/video_core/buffer_cache/buffer_cache.cpp b/src/video_core/buffer_cache/buffer_cache.cpp index 2ed0ddc87..caffee6ba 100644 --- a/src/video_core/buffer_cache/buffer_cache.cpp +++ b/src/video_core/buffer_cache/buffer_cache.cpp @@ -17,7 +17,7 @@ namespace VideoCore { static constexpr size_t NumVertexBuffers = 32; static constexpr size_t GdsBufferSize = 64_KB; static constexpr size_t StagingBufferSize = 1_GB; -static constexpr size_t UboStreamBufferSize = 128_MB; +static constexpr size_t UboStreamBufferSize = 64_MB; BufferCache::BufferCache(const Vulkan::Instance& instance_, Vulkan::Scheduler& scheduler_, const AmdGpu::Liverpool* liverpool_, TextureCache& texture_cache_, @@ -31,7 +31,11 @@ BufferCache::BufferCache(const Vulkan::Instance& instance_, Vulkan::Scheduler& s Vulkan::SetObjectName(instance.GetDevice(), gds_buffer.Handle(), "GDS Buffer"); // Ensure the first slot is used for the null buffer - void(slot_buffers.insert(instance, scheduler, MemoryUsage::DeviceLocal, 0, ReadFlags, 1)); + const auto null_id = + slot_buffers.insert(instance, scheduler, MemoryUsage::DeviceLocal, 0, ReadFlags, 1); + ASSERT(null_id.index == 0); + const vk::Buffer& null_buffer = slot_buffers[null_id].buffer; + Vulkan::SetObjectName(instance.GetDevice(), null_buffer, "Null Buffer"); } BufferCache::~BufferCache() = default; @@ -577,15 +581,26 @@ bool BufferCache::SynchronizeBufferFromImage(Buffer& buffer, VAddr device_addr, return false; } Image& image = texture_cache.GetImage(image_id); + if (False(image.flags & ImageFlagBits::GpuModified)) { + return false; + } + ASSERT_MSG(device_addr == image.info.guest_address, + "Texel buffer aliases image subresources {:x} : {:x}", device_addr, + image.info.guest_address); boost::container::small_vector copies; u32 offset = buffer.Offset(image.cpu_addr); const u32 num_layers = image.info.resources.layers; + const u32 max_offset = offset + size; for (u32 m = 0; m < image.info.resources.levels; m++) { const u32 width = std::max(image.info.size.width >> m, 1u); const u32 height = std::max(image.info.size.height >> m, 1u); const u32 depth = image.info.props.is_volume ? std::max(image.info.size.depth >> m, 1u) : 1u; const auto& [mip_size, mip_pitch, mip_height, mip_ofs] = image.info.mips_layout[m]; + offset += mip_ofs * num_layers; + if (offset + (mip_size * num_layers) > max_offset) { + break; + } copies.push_back({ .bufferOffset = offset, .bufferRowLength = static_cast(mip_pitch), @@ -599,11 +614,10 @@ bool BufferCache::SynchronizeBufferFromImage(Buffer& buffer, VAddr device_addr, .imageOffset = {0, 0, 0}, .imageExtent = {width, height, depth}, }); - offset += mip_ofs * num_layers; } if (!copies.empty()) { scheduler.EndRendering(); - image.Transit(vk::ImageLayout::eTransferSrcOptimal, vk::AccessFlagBits::eTransferRead); + image.Transit(vk::ImageLayout::eTransferSrcOptimal, vk::AccessFlagBits2::eTransferRead, {}); const auto cmdbuf = scheduler.CommandBuffer(); cmdbuf.copyImageToBuffer(image.image, vk::ImageLayout::eTransferSrcOptimal, buffer.buffer, copies); diff --git a/src/video_core/buffer_cache/buffer_cache.h b/src/video_core/buffer_cache/buffer_cache.h index cd6ea28fc..8927083cd 100644 --- a/src/video_core/buffer_cache/buffer_cache.h +++ b/src/video_core/buffer_cache/buffer_cache.h @@ -62,6 +62,11 @@ public: return &gds_buffer; } + /// Retrieves the buffer with the specified id. + [[nodiscard]] Buffer& GetBuffer(BufferId id) { + return slot_buffers[id]; + } + /// Invalidates any buffer in the logical page range. void InvalidateMemory(VAddr device_addr, u64 size); diff --git a/src/video_core/renderer_vulkan/liverpool_to_vk.cpp b/src/video_core/renderer_vulkan/liverpool_to_vk.cpp index 37bd7ebc4..c4b779fad 100644 --- a/src/video_core/renderer_vulkan/liverpool_to_vk.cpp +++ b/src/video_core/renderer_vulkan/liverpool_to_vk.cpp @@ -199,8 +199,17 @@ vk::SamplerAddressMode ClampMode(AmdGpu::ClampMode mode) { return vk::SamplerAddressMode::eMirroredRepeat; case AmdGpu::ClampMode::ClampLastTexel: return vk::SamplerAddressMode::eClampToEdge; + case AmdGpu::ClampMode::MirrorOnceHalfBorder: + case AmdGpu::ClampMode::MirrorOnceBorder: + LOG_WARNING(Render_Vulkan, "Unimplemented clamp mode {}, using closest equivalent.", + static_cast(mode)); + [[fallthrough]]; case AmdGpu::ClampMode::MirrorOnceLastTexel: return vk::SamplerAddressMode::eMirrorClampToEdge; + case AmdGpu::ClampMode::ClampHalfBorder: + LOG_WARNING(Render_Vulkan, "Unimplemented clamp mode {}, using closest equivalent.", + static_cast(mode)); + [[fallthrough]]; case AmdGpu::ClampMode::ClampBorder: return vk::SamplerAddressMode::eClampToBorder; default: diff --git a/src/video_core/renderer_vulkan/liverpool_to_vk.h b/src/video_core/renderer_vulkan/liverpool_to_vk.h index 8432d2141..f5d10d48f 100644 --- a/src/video_core/renderer_vulkan/liverpool_to_vk.h +++ b/src/video_core/renderer_vulkan/liverpool_to_vk.h @@ -4,6 +4,7 @@ #pragma once #include +#include "common/assert.h" #include "video_core/amdgpu/liverpool.h" #include "video_core/amdgpu/pixel_format.h" #include "video_core/amdgpu/resource.h" @@ -55,4 +56,13 @@ vk::SampleCountFlagBits NumSamples(u32 num_samples, vk::SampleCountFlags support void EmitQuadToTriangleListIndices(u8* out_indices, u32 num_vertices); +static inline vk::Format PromoteFormatToDepth(vk::Format fmt) { + if (fmt == vk::Format::eR32Sfloat) { + return vk::Format::eD32Sfloat; + } else if (fmt == vk::Format::eR16Unorm) { + return vk::Format::eD16Unorm; + } + UNREACHABLE(); +} + } // namespace Vulkan::LiverpoolToVK diff --git a/src/video_core/renderer_vulkan/renderer_vulkan.cpp b/src/video_core/renderer_vulkan/renderer_vulkan.cpp index d019ff034..d7954bf79 100644 --- a/src/video_core/renderer_vulkan/renderer_vulkan.cpp +++ b/src/video_core/renderer_vulkan/renderer_vulkan.cpp @@ -202,7 +202,8 @@ Frame* RendererVulkan::PrepareFrameInternal(VideoCore::Image& image, bool is_eop scheduler.EndRendering(); const auto cmdbuf = scheduler.CommandBuffer(); - image.Transit(vk::ImageLayout::eTransferSrcOptimal, vk::AccessFlagBits::eTransferRead, cmdbuf); + image.Transit(vk::ImageLayout::eTransferSrcOptimal, vk::AccessFlagBits2::eTransferRead, {}, + cmdbuf); const std::array pre_barrier{ vk::ImageMemoryBarrier{ @@ -228,7 +229,7 @@ Frame* RendererVulkan::PrepareFrameInternal(VideoCore::Image& image, bool is_eop // Post-processing (Anti-aliasing, FSR etc) goes here. For now just blit to the frame image. cmdbuf.blitImage( - image.image, image.layout, frame->image, vk::ImageLayout::eTransferDstOptimal, + image.image, image.last_state.layout, frame->image, vk::ImageLayout::eTransferDstOptimal, MakeImageBlit(image.info.size.width, image.info.size.height, frame->width, frame->height), vk::Filter::eLinear); @@ -269,6 +270,9 @@ void RendererVulkan::Present(Frame* frame) { auto& scheduler = present_scheduler; const auto cmdbuf = scheduler.CommandBuffer(); + + ImGui::Core::Render(cmdbuf, frame); + { auto* profiler_ctx = instance.GetProfilerContext(); TracyVkNamedZoneC(profiler_ctx, renderer_gpu_zone, cmdbuf, "Host frame", @@ -326,8 +330,6 @@ void RendererVulkan::Present(Frame* frame) { }, }; - ImGui::Core::Render(cmdbuf, frame); - cmdbuf.pipelineBarrier(vk::PipelineStageFlagBits::eColorAttachmentOutput, vk::PipelineStageFlagBits::eTransfer, vk::DependencyFlagBits::eByRegion, {}, {}, pre_barriers); diff --git a/src/video_core/renderer_vulkan/vk_compute_pipeline.cpp b/src/video_core/renderer_vulkan/vk_compute_pipeline.cpp index 96358bf67..3558bf785 100644 --- a/src/video_core/renderer_vulkan/vk_compute_pipeline.cpp +++ b/src/video_core/renderer_vulkan/vk_compute_pipeline.cpp @@ -2,6 +2,7 @@ // SPDX-License-Identifier: GPL-2.0-or-later #include + #include "common/alignment.h" #include "video_core/buffer_cache/buffer_cache.h" #include "video_core/renderer_vulkan/vk_compute_pipeline.h" @@ -15,7 +16,7 @@ ComputePipeline::ComputePipeline(const Instance& instance_, Scheduler& scheduler DescriptorHeap& desc_heap_, vk::PipelineCache pipeline_cache, u64 compute_key_, const Shader::Info& info_, vk::ShaderModule module) - : instance{instance_}, scheduler{scheduler_}, desc_heap{desc_heap_}, compute_key{compute_key_}, + : Pipeline{instance_, scheduler_, desc_heap_, pipeline_cache}, compute_key{compute_key_}, info{&info_} { const vk::PipelineShaderStageCreateInfo shader_ci = { .stage = vk::ShaderStageFlagBits::eCompute, @@ -108,12 +109,14 @@ bool ComputePipeline::BindResources(VideoCore::BufferCache& buffer_cache, // Bind resource buffers and textures. boost::container::static_vector buffer_views; boost::container::static_vector buffer_infos; - boost::container::static_vector image_infos; boost::container::small_vector set_writes; boost::container::small_vector buffer_barriers; Shader::PushData push_data{}; - u32 binding{}; + Shader::Backend::Bindings binding{}; + image_infos.clear(); + + info->PushUd(binding, push_data); for (const auto& desc : info->buffers) { bool is_storage = true; if (desc.is_gds_buffer) { @@ -145,21 +148,20 @@ bool ComputePipeline::BindResources(VideoCore::BufferCache& buffer_cache, buffer_cache.ObtainBuffer(address, size, desc.is_written); const u32 offset_aligned = Common::AlignDown(offset, alignment); const u32 adjust = offset - offset_aligned; - if (adjust != 0) { - ASSERT(adjust % 4 == 0); - push_data.AddOffset(binding, adjust); - } + 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++, + .dstBinding = binding.unified++, .dstArrayElement = 0, .descriptorCount = 1, .descriptorType = is_storage ? vk::DescriptorType::eStorageBuffer : vk::DescriptorType::eUniformBuffer, .pBufferInfo = &buffer_infos.back(), }); + ++binding.buffer; } for (const auto& desc : info->texture_buffers) { @@ -186,10 +188,8 @@ bool ComputePipeline::BindResources(VideoCore::BufferCache& buffer_cache, "Texel buffer stride must match format stride"); const u32 offset_aligned = Common::AlignDown(offset, alignment); const u32 adjust = offset - offset_aligned; - if (adjust != 0) { - ASSERT(adjust % fmt_stride == 0); - push_data.AddOffset(binding, adjust / fmt_stride); - } + 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 = @@ -199,45 +199,23 @@ bool ComputePipeline::BindResources(VideoCore::BufferCache& buffer_cache, buffer_barriers.emplace_back(*barrier); } if (desc.is_written) { - texture_cache.MarkWritten(address, size); + texture_cache.InvalidateMemoryFromGPU(address, size); } } set_writes.push_back({ .dstSet = VK_NULL_HANDLE, - .dstBinding = binding++, + .dstBinding = binding.unified++, .dstArrayElement = 0, .descriptorCount = 1, .descriptorType = desc.is_written ? vk::DescriptorType::eStorageTexelBuffer : vk::DescriptorType::eUniformTexelBuffer, .pTexelBufferView = &buffer_view, }); + ++binding.buffer; } - for (const auto& image_desc : info->images) { - const auto tsharp = image_desc.GetSharp(*info); - if (tsharp.GetDataFmt() != AmdGpu::DataFormat::FormatInvalid) { - VideoCore::ImageInfo image_info{tsharp, image_desc.is_depth}; - VideoCore::ImageViewInfo view_info{tsharp, image_desc.is_storage}; - const auto& image_view = texture_cache.FindTexture(image_info, view_info); - const auto& image = texture_cache.GetImage(image_view.image_id); - image_infos.emplace_back(VK_NULL_HANDLE, *image_view.image_view, image.layout); - } else { - image_infos.emplace_back(VK_NULL_HANDLE, VK_NULL_HANDLE, vk::ImageLayout::eGeneral); - } - set_writes.push_back({ - .dstSet = VK_NULL_HANDLE, - .dstBinding = binding++, - .dstArrayElement = 0, - .descriptorCount = 1, - .descriptorType = image_desc.is_storage ? vk::DescriptorType::eStorageImage - : vk::DescriptorType::eSampledImage, - .pImageInfo = &image_infos.back(), - }); + BindTextures(texture_cache, *info, binding, set_writes); - if (texture_cache.IsMeta(tsharp.Address())) { - LOG_WARNING(Render_Vulkan, "Unexpected metadata read by a CS shader (texture)"); - } - } for (const auto& sampler : info->samplers) { const auto ssharp = sampler.GetSharp(*info); if (ssharp.force_degamma) { @@ -247,7 +225,7 @@ bool ComputePipeline::BindResources(VideoCore::BufferCache& buffer_cache, image_infos.emplace_back(vk_sampler, VK_NULL_HANDLE, vk::ImageLayout::eGeneral); set_writes.push_back({ .dstSet = VK_NULL_HANDLE, - .dstBinding = binding++, + .dstBinding = binding.unified++, .dstArrayElement = 0, .descriptorCount = 1, .descriptorType = vk::DescriptorType::eSampler, diff --git a/src/video_core/renderer_vulkan/vk_compute_pipeline.h b/src/video_core/renderer_vulkan/vk_compute_pipeline.h index 8a6213a29..f1bc7285a 100644 --- a/src/video_core/renderer_vulkan/vk_compute_pipeline.h +++ b/src/video_core/renderer_vulkan/vk_compute_pipeline.h @@ -3,9 +3,8 @@ #pragma once -#include -#include "shader_recompiler/info.h" #include "video_core/renderer_vulkan/vk_common.h" +#include "video_core/renderer_vulkan/vk_pipeline_common.h" namespace VideoCore { class BufferCache; @@ -18,27 +17,17 @@ class Instance; class Scheduler; class DescriptorHeap; -class ComputePipeline { +class ComputePipeline : public Pipeline { public: - explicit ComputePipeline(const Instance& instance, Scheduler& scheduler, - DescriptorHeap& desc_heap, vk::PipelineCache pipeline_cache, - u64 compute_key, const Shader::Info& info, vk::ShaderModule module); + ComputePipeline(const Instance& instance, Scheduler& scheduler, DescriptorHeap& desc_heap, + vk::PipelineCache pipeline_cache, u64 compute_key, const Shader::Info& info, + vk::ShaderModule module); ~ComputePipeline(); - [[nodiscard]] vk::Pipeline Handle() const noexcept { - return *pipeline; - } - bool BindResources(VideoCore::BufferCache& buffer_cache, VideoCore::TextureCache& texture_cache) const; private: - const Instance& instance; - Scheduler& scheduler; - DescriptorHeap& desc_heap; - vk::UniquePipeline pipeline; - vk::UniquePipelineLayout pipeline_layout; - vk::UniqueDescriptorSetLayout desc_layout; u64 compute_key; const Shader::Info* info; bool uses_push_descriptors{}; diff --git a/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp b/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp index f7474b24e..8edf2f50c 100644 --- a/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp +++ b/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp @@ -21,7 +21,7 @@ GraphicsPipeline::GraphicsPipeline(const Instance& instance_, Scheduler& schedul vk::PipelineCache pipeline_cache, std::span infos, std::span modules) - : instance{instance_}, scheduler{scheduler_}, desc_heap{desc_heap_}, key{key_} { + : Pipeline{instance_, scheduler_, desc_heap_, pipeline_cache}, key{key_} { const vk::Device device = instance.GetDevice(); std::ranges::copy(infos, stages.begin()); BuildDescSetLayout(); @@ -41,8 +41,8 @@ GraphicsPipeline::GraphicsPipeline(const Instance& instance_, Scheduler& schedul }; pipeline_layout = instance.GetDevice().createPipelineLayoutUnique(layout_info); - boost::container::static_vector bindings; - boost::container::static_vector attributes; + boost::container::static_vector vertex_bindings; + boost::container::static_vector vertex_attributes; const auto& vs_info = stages[u32(Shader::Stage::Vertex)]; for (const auto& input : vs_info->vs_inputs) { if (input.instance_step_rate == Shader::Info::VsInput::InstanceIdType::OverStepRate0 || @@ -52,13 +52,13 @@ GraphicsPipeline::GraphicsPipeline(const Instance& instance_, Scheduler& schedul } const auto buffer = vs_info->ReadUd(input.sgpr_base, input.dword_offset); - attributes.push_back({ + vertex_attributes.push_back({ .location = input.binding, .binding = input.binding, .format = LiverpoolToVK::SurfaceFormat(buffer.GetDataFmt(), buffer.GetNumberFmt()), .offset = 0, }); - bindings.push_back({ + vertex_bindings.push_back({ .binding = input.binding, .stride = buffer.GetStride(), .inputRate = input.instance_step_rate == Shader::Info::VsInput::None @@ -68,10 +68,10 @@ GraphicsPipeline::GraphicsPipeline(const Instance& instance_, Scheduler& schedul } const vk::PipelineVertexInputStateCreateInfo vertex_input_info = { - .vertexBindingDescriptionCount = static_cast(bindings.size()), - .pVertexBindingDescriptions = bindings.data(), - .vertexAttributeDescriptionCount = static_cast(attributes.size()), - .pVertexAttributeDescriptions = attributes.data(), + .vertexBindingDescriptionCount = static_cast(vertex_bindings.size()), + .pVertexBindingDescriptions = vertex_bindings.data(), + .vertexAttributeDescriptionCount = static_cast(vertex_attributes.size()), + .pVertexAttributeDescriptions = vertex_attributes.data(), }; if (key.prim_type == Liverpool::PrimitiveType::RectList && !IsEmbeddedVs()) { @@ -83,8 +83,9 @@ GraphicsPipeline::GraphicsPipeline(const Instance& instance_, Scheduler& schedul .topology = LiverpoolToVK::PrimitiveType(key.prim_type), .primitiveRestartEnable = key.enable_primitive_restart != 0, }; - ASSERT_MSG(!key.enable_primitive_restart || key.primitive_restart_index == 0xFFFF, - "Primitive restart index other than 0xFFFF is not supported yet"); + ASSERT_MSG(!key.enable_primitive_restart || key.primitive_restart_index == 0xFFFF || + key.primitive_restart_index == 0xFFFFFFFF, + "Primitive restart index other than -1 is not supported yet"); const vk::PipelineRasterizationStateCreateInfo raster_state = { .depthClampEnable = false, @@ -291,8 +292,9 @@ GraphicsPipeline::GraphicsPipeline(const Instance& instance_, Scheduler& schedul GraphicsPipeline::~GraphicsPipeline() = default; void GraphicsPipeline::BuildDescSetLayout() { - u32 binding{}; boost::container::small_vector bindings; + u32 binding{}; + for (const auto* stage : stages) { if (!stage) { continue; @@ -352,11 +354,12 @@ void GraphicsPipeline::BindResources(const Liverpool::Regs& regs, // Bind resource buffers and textures. boost::container::static_vector buffer_views; boost::container::static_vector buffer_infos; - boost::container::static_vector image_infos; boost::container::small_vector set_writes; boost::container::small_vector buffer_barriers; Shader::PushData push_data{}; - u32 binding{}; + Shader::Backend::Bindings binding{}; + + image_infos.clear(); for (const auto* stage : stages) { if (!stage) { @@ -366,6 +369,7 @@ void GraphicsPipeline::BindResources(const Liverpool::Regs& regs, push_data.step0 = regs.vgt_instance_step_rate_0; 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); @@ -381,23 +385,25 @@ void GraphicsPipeline::BindResources(const Liverpool::Regs& regs, buffer_cache.ObtainBuffer(address, size, buffer.is_written); const u32 offset_aligned = Common::AlignDown(offset, alignment); const u32 adjust = offset - offset_aligned; - if (adjust != 0) { - ASSERT(adjust % 4 == 0); - push_data.AddOffset(binding, adjust); - } + ASSERT(adjust % 4 == 0); + push_data.AddOffset(binding.buffer, adjust); buffer_infos.emplace_back(vk_buffer->Handle(), offset_aligned, size + adjust); - } else { + } 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++, + .dstBinding = binding.unified++, .dstArrayElement = 0, .descriptorCount = 1, .descriptorType = is_storage ? vk::DescriptorType::eStorageBuffer : vk::DescriptorType::eUniformBuffer, .pBufferInfo = &buffer_infos.back(), }); + ++binding.buffer; } for (const auto& desc : stage->texture_buffers) { @@ -414,10 +420,8 @@ void GraphicsPipeline::BindResources(const Liverpool::Regs& regs, "Texel buffer stride must match format stride"); const u32 offset_aligned = Common::AlignDown(offset, alignment); const u32 adjust = offset - offset_aligned; - if (adjust != 0) { - ASSERT(adjust % fmt_stride == 0); - push_data.AddOffset(binding, adjust / fmt_stride); - } + 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 @@ -427,54 +431,30 @@ void GraphicsPipeline::BindResources(const Liverpool::Regs& regs, buffer_barriers.emplace_back(*barrier); } if (desc.is_written) { - texture_cache.MarkWritten(address, size); + texture_cache.InvalidateMemoryFromGPU(address, size); } } set_writes.push_back({ .dstSet = VK_NULL_HANDLE, - .dstBinding = binding++, + .dstBinding = binding.unified++, .dstArrayElement = 0, .descriptorCount = 1, .descriptorType = desc.is_written ? vk::DescriptorType::eStorageTexelBuffer : vk::DescriptorType::eUniformTexelBuffer, .pTexelBufferView = &buffer_view, }); + ++binding.buffer; } - boost::container::static_vector tsharps; - for (const auto& image_desc : stage->images) { - const auto tsharp = image_desc.GetSharp(*stage); - if (tsharp.GetDataFmt() != AmdGpu::DataFormat::FormatInvalid) { - tsharps.emplace_back(tsharp); - VideoCore::ImageInfo image_info{tsharp, image_desc.is_depth}; - VideoCore::ImageViewInfo view_info{tsharp, image_desc.is_storage}; - const auto& image_view = texture_cache.FindTexture(image_info, view_info); - const auto& image = texture_cache.GetImage(image_view.image_id); - image_infos.emplace_back(VK_NULL_HANDLE, *image_view.image_view, image.layout); - } else { - image_infos.emplace_back(VK_NULL_HANDLE, VK_NULL_HANDLE, vk::ImageLayout::eGeneral); - } - set_writes.push_back({ - .dstSet = VK_NULL_HANDLE, - .dstBinding = binding++, - .dstArrayElement = 0, - .descriptorCount = 1, - .descriptorType = image_desc.is_storage ? vk::DescriptorType::eStorageImage - : vk::DescriptorType::eSampledImage, - .pImageInfo = &image_infos.back(), - }); + BindTextures(texture_cache, *stage, binding, set_writes); - if (texture_cache.IsMeta(tsharp.Address())) { - LOG_WARNING(Render_Vulkan, "Unexpected metadata read by a PS shader (texture)"); - } - } 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 = tsharps[sampler.associated_image]; + 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); } @@ -483,7 +463,7 @@ void GraphicsPipeline::BindResources(const Liverpool::Regs& regs, image_infos.emplace_back(vk_sampler, VK_NULL_HANDLE, vk::ImageLayout::eGeneral); set_writes.push_back({ .dstSet = VK_NULL_HANDLE, - .dstBinding = binding++, + .dstBinding = binding.unified++, .dstArrayElement = 0, .descriptorCount = 1, .descriptorType = vk::DescriptorType::eSampler, diff --git a/src/video_core/renderer_vulkan/vk_graphics_pipeline.h b/src/video_core/renderer_vulkan/vk_graphics_pipeline.h index c8a08b4f2..74817656a 100644 --- a/src/video_core/renderer_vulkan/vk_graphics_pipeline.h +++ b/src/video_core/renderer_vulkan/vk_graphics_pipeline.h @@ -5,7 +5,7 @@ #include "common/types.h" #include "video_core/renderer_vulkan/liverpool_to_vk.h" #include "video_core/renderer_vulkan/vk_common.h" -#include "video_core/renderer_vulkan/vk_compute_pipeline.h" +#include "video_core/renderer_vulkan/vk_pipeline_common.h" namespace VideoCore { class BufferCache; @@ -33,6 +33,7 @@ struct GraphicsPipelineKey { Liverpool::DepthControl depth_stencil; u32 depth_bias_enable; u32 num_samples; + u32 mrt_mask; Liverpool::StencilControl stencil; Liverpool::PrimitiveType prim_type; u32 enable_primitive_restart; @@ -50,26 +51,17 @@ struct GraphicsPipelineKey { } }; -class GraphicsPipeline { +class GraphicsPipeline : public Pipeline { public: - explicit GraphicsPipeline(const Instance& instance, Scheduler& scheduler, - DescriptorHeap& desc_heap, const GraphicsPipelineKey& key, - vk::PipelineCache pipeline_cache, - std::span stages, - std::span modules); + GraphicsPipeline(const Instance& instance, Scheduler& scheduler, DescriptorHeap& desc_heap, + const GraphicsPipelineKey& key, vk::PipelineCache pipeline_cache, + std::span stages, + std::span modules); ~GraphicsPipeline(); void BindResources(const Liverpool::Regs& regs, VideoCore::BufferCache& buffer_cache, VideoCore::TextureCache& texture_cache) const; - vk::Pipeline Handle() const noexcept { - return *pipeline; - } - - vk::PipelineLayout GetLayout() const { - return *pipeline_layout; - } - const Shader::Info& GetStage(Shader::Stage stage) const noexcept { return *stages[u32(stage)]; } @@ -83,6 +75,10 @@ public: return key.write_masks; } + auto GetMrtMask() const { + return key.mrt_mask; + } + bool IsDepthEnabled() const { return key.depth_stencil.depth_enable.Value(); } @@ -91,12 +87,6 @@ private: void BuildDescSetLayout(); private: - const Instance& instance; - Scheduler& scheduler; - DescriptorHeap& desc_heap; - vk::UniquePipeline pipeline; - vk::UniquePipelineLayout pipeline_layout; - vk::UniqueDescriptorSetLayout desc_layout; std::array stages{}; GraphicsPipelineKey key; bool uses_push_descriptors{}; diff --git a/src/video_core/renderer_vulkan/vk_instance.cpp b/src/video_core/renderer_vulkan/vk_instance.cpp index d88d43291..52143907c 100644 --- a/src/video_core/renderer_vulkan/vk_instance.cpp +++ b/src/video_core/renderer_vulkan/vk_instance.cpp @@ -33,11 +33,16 @@ std::vector GetSupportedExtensions(vk::PhysicalDevice physical) { return supported_extensions; } -std::unordered_map GetFormatProperties( +std::unordered_map GetFormatProperties( vk::PhysicalDevice physical) { - std::unordered_map format_properties; + std::unordered_map format_properties; for (const auto& format : LiverpoolToVK::GetAllFormats()) { - format_properties.emplace(format, physical.getFormatProperties(format)); + vk::FormatProperties3 properties3{}; + vk::FormatProperties2 properties2 = { + .pNext = &properties3, + }; + physical.getFormatProperties2(format, &properties2); + format_properties.emplace(format, properties3); } return format_properties; } @@ -277,6 +282,7 @@ bool Instance::CreateDevice() { vk::PhysicalDeviceFeatures2{ .features{ .robustBufferAccess = features.robustBufferAccess, + .imageCubeArray = features.imageCubeArray, .independentBlend = features.independentBlend, .geometryShader = features.geometryShader, .logicOp = features.logicOp, @@ -304,6 +310,7 @@ bool Instance::CreateDevice() { .separateDepthStencilLayouts = vk12_features.separateDepthStencilLayouts, .hostQueryReset = vk12_features.hostQueryReset, .timelineSemaphore = vk12_features.timelineSemaphore, + .samplerMirrorClampToEdge = vk12_features.samplerMirrorClampToEdge, }, vk::PhysicalDeviceMaintenance4FeaturesKHR{ .maintenance4 = true, @@ -376,9 +383,12 @@ bool Instance::CreateDevice() { device_chain.unlink(); } if (robustness) { - device_chain.get().nullDescriptor = + null_descriptor = feature_chain.get().nullDescriptor; + device_chain.get().nullDescriptor = + null_descriptor; } else { + null_descriptor = false; device_chain.unlink(); } if (!vertex_input_dynamic_state) { @@ -496,9 +506,9 @@ bool Instance::IsImageFormatSupported(const vk::Format format) const { UNIMPLEMENTED_MSG("Properties of format {} have not been queried.", vk::to_string(format)); } - constexpr vk::FormatFeatureFlags optimal_flags = vk::FormatFeatureFlagBits::eTransferSrc | - vk::FormatFeatureFlagBits::eTransferDst | - vk::FormatFeatureFlagBits::eSampledImage; + constexpr vk::FormatFeatureFlags2 optimal_flags = vk::FormatFeatureFlagBits2::eTransferSrc | + vk::FormatFeatureFlagBits2::eTransferDst | + vk::FormatFeatureFlagBits2::eSampledImage; return (it->second.optimalTilingFeatures & optimal_flags) == optimal_flags; } @@ -512,7 +522,7 @@ bool Instance::IsVertexFormatSupported(const vk::Format format) const { UNIMPLEMENTED_MSG("Properties of format {} have not been queried.", vk::to_string(format)); } - constexpr vk::FormatFeatureFlags optimal_flags = vk::FormatFeatureFlagBits::eVertexBuffer; + constexpr vk::FormatFeatureFlags2 optimal_flags = vk::FormatFeatureFlagBits2::eVertexBuffer; return (it->second.bufferFeatures & optimal_flags) == optimal_flags; } diff --git a/src/video_core/renderer_vulkan/vk_instance.h b/src/video_core/renderer_vulkan/vk_instance.h index 61687ac28..1c94f586e 100644 --- a/src/video_core/renderer_vulkan/vk_instance.h +++ b/src/video_core/renderer_vulkan/vk_instance.h @@ -132,6 +132,11 @@ public: return vertex_input_dynamic_state; } + /// Returns true when the nullDescriptor feature of VK_EXT_robustness2 is supported. + bool IsNullDescriptorSupported() const { + return null_descriptor; + } + /// Returns the vendor ID of the physical device u32 GetVendorID() const { return properties.vendorID; @@ -264,7 +269,7 @@ private: vk::Queue graphics_queue; std::vector physical_devices; std::vector available_extensions; - std::unordered_map format_properties; + std::unordered_map format_properties; TracyVkCtx profiler_context{}; u32 queue_family_index{0}; bool image_view_reinterpretation{true}; @@ -279,6 +284,7 @@ private: bool workgroup_memory_explicit_layout{}; bool color_write_en{}; bool vertex_input_dynamic_state{}; + bool null_descriptor{}; u64 min_imported_host_pointer_alignment{}; u32 subgroup_size{}; bool tooling_info{}; diff --git a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp index 7f6079a5c..7a094f66d 100644 --- a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp @@ -234,18 +234,20 @@ bool PipelineCache::RefreshGraphicsKey() { key.front_face = regs.polygon_control.front_face; key.num_samples = regs.aa_config.NumSamples(); - const auto skip_cb_binding = + const bool skip_cb_binding = regs.color_control.mode == AmdGpu::Liverpool::ColorControl::OperationMode::Disable; // `RenderingInfo` is assumed to be initialized with a contiguous array of valid color - // attachments. This might be not a case as HW color buffers can be bound in an arbitrary order. - // We need to do some arrays compaction at this stage + // attachments. This might be not a case as HW color buffers can be bound in an arbitrary + // order. We need to do some arrays compaction at this stage key.color_formats.fill(vk::Format::eUndefined); key.blend_controls.fill({}); key.write_masks.fill({}); key.mrt_swizzles.fill(Liverpool::ColorBuffer::SwapMode::Standard); - int remapped_cb{}; - for (auto cb = 0u; cb < Liverpool::NumColorBuffers; ++cb) { + + // First pass of bindings check to idenitfy formats and swizzles and pass them to rhe shader + // recompiler. + for (auto cb = 0u, remapped_cb = 0u; cb < Liverpool::NumColorBuffers; ++cb) { auto const& col_buf = regs.color_buffers[cb]; if (skip_cb_binding || !col_buf || !regs.color_target_mask.GetMask(cb)) { continue; @@ -258,16 +260,11 @@ bool PipelineCache::RefreshGraphicsKey() { if (base_format == key.color_formats[remapped_cb]) { key.mrt_swizzles[remapped_cb] = col_buf.info.comp_swap.Value(); } - key.blend_controls[remapped_cb] = regs.blend_control[cb]; - key.blend_controls[remapped_cb].enable.Assign(key.blend_controls[remapped_cb].enable && - !col_buf.info.blend_bypass); - key.write_masks[remapped_cb] = vk::ColorComponentFlags{regs.color_target_mask.GetMask(cb)}; - key.cb_shader_mask.SetMask(remapped_cb, regs.color_shader_mask.GetMask(cb)); ++remapped_cb; } - u32 binding{}; + Shader::Backend::Bindings binding{}; for (u32 i = 0; i < MaxShaderStages; i++) { if (!regs.stage_enable.IsStageEnabled(i)) { key.stage_hashes[i] = 0; @@ -309,11 +306,33 @@ bool PipelineCache::RefreshGraphicsKey() { std::tie(infos[i], modules[i], key.stage_hashes[i]) = GetProgram(stage, params, binding); } + + const auto* fs_info = infos[u32(Shader::Stage::Fragment)]; + key.mrt_mask = fs_info ? fs_info->mrt_mask : 0u; + + // Second pass to fill remain CB pipeline key data + for (auto cb = 0u, remapped_cb = 0u; cb < Liverpool::NumColorBuffers; ++cb) { + auto const& col_buf = regs.color_buffers[cb]; + if (skip_cb_binding || !col_buf || !regs.color_target_mask.GetMask(cb) || + (key.mrt_mask & (1u << cb)) == 0) { + key.color_formats[cb] = vk::Format::eUndefined; + key.mrt_swizzles[cb] = Liverpool::ColorBuffer::SwapMode::Standard; + continue; + } + + key.blend_controls[remapped_cb] = regs.blend_control[cb]; + key.blend_controls[remapped_cb].enable.Assign(key.blend_controls[remapped_cb].enable && + !col_buf.info.blend_bypass); + key.write_masks[remapped_cb] = vk::ColorComponentFlags{regs.color_target_mask.GetMask(cb)}; + key.cb_shader_mask.SetMask(remapped_cb, regs.color_shader_mask.GetMask(cb)); + + ++remapped_cb; + } return true; } bool PipelineCache::RefreshComputeKey() { - u32 binding{}; + Shader::Backend::Bindings binding{}; const auto* cs_pgm = &liverpool->regs.cs_program; const auto cs_params = Liverpool::GetParams(*cs_pgm); if (ShouldSkipShader(cs_params.hash, "compute")) { @@ -327,7 +346,7 @@ bool PipelineCache::RefreshComputeKey() { vk::ShaderModule PipelineCache::CompileModule(Shader::Info& info, const Shader::RuntimeInfo& runtime_info, std::span code, size_t perm_idx, - u32& binding) { + Shader::Backend::Bindings& binding) { LOG_INFO(Render_Vulkan, "Compiling {} shader {:#x} {}", info.stage, info.pgm_hash, perm_idx != 0 ? "(permutation)" : ""); if (Config::dumpShaders()) { @@ -347,14 +366,14 @@ vk::ShaderModule PipelineCache::CompileModule(Shader::Info& info, } std::tuple PipelineCache::GetProgram( - Shader::Stage stage, Shader::ShaderParams params, u32& binding) { + Shader::Stage stage, Shader::ShaderParams params, Shader::Backend::Bindings& binding) { const auto runtime_info = BuildRuntimeInfo(stage); auto [it_pgm, new_program] = program_cache.try_emplace(params.hash); if (new_program) { Program* program = program_pool.Create(stage, params); - u32 start_binding = binding; + auto start = binding; const auto module = CompileModule(program->info, runtime_info, params.code, 0, binding); - const auto spec = Shader::StageSpecialization(program->info, runtime_info, start_binding); + const auto spec = Shader::StageSpecialization(program->info, runtime_info, start); program->AddPermut(module, std::move(spec)); it_pgm.value() = program; return std::make_tuple(&program->info, module, HashCombine(params.hash, 0)); @@ -372,7 +391,7 @@ std::tuple PipelineCache::GetProgram module = CompileModule(new_info, runtime_info, params.code, perm_idx, binding); program->AddPermut(module, std::move(spec)); } else { - binding += info.NumBindings(); + info.AddBindings(binding); module = it->module; perm_idx = std::distance(program->modules.begin(), it); } diff --git a/src/video_core/renderer_vulkan/vk_pipeline_cache.h b/src/video_core/renderer_vulkan/vk_pipeline_cache.h index 7f0064fb8..7e44bbf09 100644 --- a/src/video_core/renderer_vulkan/vk_pipeline_cache.h +++ b/src/video_core/renderer_vulkan/vk_pipeline_cache.h @@ -49,9 +49,8 @@ public: const ComputePipeline* GetComputePipeline(); - std::tuple GetProgram(Shader::Stage stage, - Shader::ShaderParams params, - u32& binding); + std::tuple GetProgram( + Shader::Stage stage, Shader::ShaderParams params, Shader::Backend::Bindings& binding); private: bool RefreshGraphicsKey(); @@ -60,7 +59,8 @@ private: void DumpShader(std::span code, u64 hash, Shader::Stage stage, size_t perm_idx, std::string_view ext); vk::ShaderModule CompileModule(Shader::Info& info, const Shader::RuntimeInfo& runtime_info, - std::span code, size_t perm_idx, u32& binding); + std::span code, size_t perm_idx, + Shader::Backend::Bindings& binding); Shader::RuntimeInfo BuildRuntimeInfo(Shader::Stage stage); private: diff --git a/src/video_core/renderer_vulkan/vk_pipeline_common.cpp b/src/video_core/renderer_vulkan/vk_pipeline_common.cpp new file mode 100644 index 000000000..61e564150 --- /dev/null +++ b/src/video_core/renderer_vulkan/vk_pipeline_common.cpp @@ -0,0 +1,81 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include + +#include "shader_recompiler/info.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" +#include "video_core/texture_cache/texture_cache.h" + +namespace Vulkan { + +boost::container::static_vector Pipeline::image_infos; + +Pipeline::Pipeline(const Instance& instance_, Scheduler& scheduler_, DescriptorHeap& desc_heap_, + vk::PipelineCache pipeline_cache) + : instance{instance_}, scheduler{scheduler_}, desc_heap{desc_heap_} {} + +Pipeline::~Pipeline() = default; + +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; + + for (const auto& image_desc : stage.images) { + const auto tsharp = image_desc.GetSharp(stage); + if (tsharp.GetDataFmt() != AmdGpu::DataFormat::FormatInvalid) { + VideoCore::ImageInfo image_info{tsharp, image_desc}; + const auto image_id = texture_cache.FindImage(image_info); + auto& image = texture_cache.GetImage(image_id); + image.flags |= VideoCore::ImageFlagBits::Bound; + image_bindings.emplace_back(image_id, tsharp, image_desc); + } else { + image_bindings.emplace_back(VideoCore::ImageId{}, tsharp, image_desc); + } + + if (texture_cache.IsMeta(tsharp.Address())) { + LOG_WARNING(Render_Vulkan, "Unexpected metadata read by a PS shader (texture)"); + } + } + + // Second pass to re-bind images that were updated after binding + for (auto [image_id, tsharp, desc] : image_bindings) { + if (!image_id) { + if (instance.IsNullDescriptorSupported()) { + image_infos.emplace_back(VK_NULL_HANDLE, VK_NULL_HANDLE, vk::ImageLayout::eGeneral); + } else { + auto& null_image = texture_cache.GetImageView(VideoCore::NULL_IMAGE_VIEW_ID); + image_infos.emplace_back(VK_NULL_HANDLE, *null_image.image_view, + vk::ImageLayout::eGeneral); + } + } else { + auto& image = texture_cache.GetImage(image_id); + if (True(image.flags & VideoCore::ImageFlagBits::NeedsRebind)) { + image_id = texture_cache.FindImage(image.info); + } + VideoCore::ImageViewInfo view_info{tsharp, desc}; + auto& image_view = texture_cache.FindTexture(image_id, view_info); + image_infos.emplace_back(VK_NULL_HANDLE, *image_view.image_view, + texture_cache.GetImage(image_id).last_state.layout); + image.flags &= + ~(VideoCore::ImageFlagBits::NeedsRebind | VideoCore::ImageFlagBits::Bound); + } + + set_writes.push_back({ + .dstSet = VK_NULL_HANDLE, + .dstBinding = binding.unified++, + .dstArrayElement = 0, + .descriptorCount = 1, + .descriptorType = desc.is_storage ? vk::DescriptorType::eStorageImage + : vk::DescriptorType::eSampledImage, + .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 new file mode 100644 index 000000000..ab99e7b33 --- /dev/null +++ b/src/video_core/renderer_vulkan/vk_pipeline_common.h @@ -0,0 +1,49 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "shader_recompiler/backend/bindings.h" +#include "shader_recompiler/info.h" +#include "video_core/renderer_vulkan/vk_common.h" + +namespace VideoCore { +class BufferCache; +class TextureCache; +} // namespace VideoCore + +namespace Vulkan { + +class Instance; +class Scheduler; +class DescriptorHeap; + +class Pipeline { +public: + Pipeline(const Instance& instance, Scheduler& scheduler, DescriptorHeap& desc_heap, + vk::PipelineCache pipeline_cache); + virtual ~Pipeline(); + + vk::Pipeline Handle() const noexcept { + return *pipeline; + } + + vk::PipelineLayout GetLayout() const noexcept { + return *pipeline_layout; + } + + using DescriptorWrites = boost::container::small_vector; + void BindTextures(VideoCore::TextureCache& texture_cache, const Shader::Info& stage, + Shader::Backend::Bindings& binding, DescriptorWrites& set_writes) const; + +protected: + const Instance& instance; + Scheduler& scheduler; + DescriptorHeap& desc_heap; + vk::UniquePipeline pipeline; + vk::UniquePipelineLayout pipeline_layout; + vk::UniqueDescriptorSetLayout desc_layout; + static boost::container::static_vector image_infos; +}; + +} // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/vk_platform.cpp b/src/video_core/renderer_vulkan/vk_platform.cpp index feadda96c..6abd00aaa 100644 --- a/src/video_core/renderer_vulkan/vk_platform.cpp +++ b/src/video_core/renderer_vulkan/vk_platform.cpp @@ -44,7 +44,6 @@ static VKAPI_ATTR VkBool32 VKAPI_CALL DebugUtilsCallback( case 0xc81ad50e: case 0xb7c39078: case 0x32868fde: // vkCreateBufferView(): pCreateInfo->range does not equal VK_WHOLE_SIZE - case 0x92d66fc1: // `pMultisampleState is NULL` for depth only passes (confirmed VL error) return VK_FALSE; default: break; diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.cpp b/src/video_core/renderer_vulkan/vk_rasterizer.cpp index 23f60da13..eac272726 100644 --- a/src/video_core/renderer_vulkan/vk_rasterizer.cpp +++ b/src/video_core/renderer_vulkan/vk_rasterizer.cpp @@ -62,7 +62,7 @@ void Rasterizer::Draw(bool is_indexed, u32 index_offset) { buffer_cache.BindVertexBuffers(vs_info); const u32 num_indices = buffer_cache.BindIndexBuffer(is_indexed, index_offset); - BeginRendering(); + BeginRendering(*pipeline); UpdateDynamicState(*pipeline); const auto [vertex_offset, instance_offset] = vs_info.GetDrawOffsets(); @@ -102,7 +102,7 @@ void Rasterizer::DrawIndirect(bool is_indexed, VAddr address, u32 offset, u32 si buffer_cache.BindVertexBuffers(vs_info); const u32 num_indices = buffer_cache.BindIndexBuffer(is_indexed, 0); - BeginRendering(); + BeginRendering(*pipeline); UpdateDynamicState(*pipeline); const auto [buffer, base] = buffer_cache.ObtainBuffer(address, size, true); @@ -179,7 +179,7 @@ void Rasterizer::Finish() { scheduler.Finish(); } -void Rasterizer::BeginRendering() { +void Rasterizer::BeginRendering(const GraphicsPipeline& pipeline) { const auto& regs = liverpool->regs; RenderState state; @@ -199,6 +199,13 @@ void Rasterizer::BeginRendering() { continue; } + // Skip stale color buffers if shader doesn't output to them. Otherwise it will perform + // an unnecessary transition and may result in state conflict if the resource is already + // bound for reading. + if ((pipeline.GetMrtMask() & (1 << col_buf_id)) == 0) { + continue; + } + const auto& hint = liverpool->last_cb_extent[col_buf_id]; VideoCore::ImageInfo image_info{col_buf, hint}; VideoCore::ImageViewInfo view_info{col_buf, false /*!!image.info.usage.vo_buffer*/}; @@ -240,7 +247,7 @@ void Rasterizer::BeginRendering() { state.depth_image = image.image; state.depth_attachment = { .imageView = *image_view.image_view, - .imageLayout = image.layout, + .imageLayout = image.last_state.layout, .loadOp = is_clear ? vk::AttachmentLoadOp::eClear : vk::AttachmentLoadOp::eLoad, .storeOp = is_clear ? vk::AttachmentStoreOp::eNone : vk::AttachmentStoreOp::eStore, .clearValue = vk::ClearValue{.depthStencil = {.depth = regs.depth_clear, diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.h b/src/video_core/renderer_vulkan/vk_rasterizer.h index 5aa90c5cc..bd05c8faf 100644 --- a/src/video_core/renderer_vulkan/vk_rasterizer.h +++ b/src/video_core/renderer_vulkan/vk_rasterizer.h @@ -52,7 +52,7 @@ public: void Finish(); private: - void BeginRendering(); + void BeginRendering(const GraphicsPipeline& pipeline); void UpdateDynamicState(const GraphicsPipeline& pipeline); void UpdateViewportScissorState(); diff --git a/src/video_core/renderer_vulkan/vk_scheduler.cpp b/src/video_core/renderer_vulkan/vk_scheduler.cpp index 9ff332aef..08b5014ec 100644 --- a/src/video_core/renderer_vulkan/vk_scheduler.cpp +++ b/src/video_core/renderer_vulkan/vk_scheduler.cpp @@ -4,6 +4,7 @@ #include #include "common/assert.h" #include "common/debug.h" +#include "imgui/renderer/texture_manager.h" #include "video_core/renderer_vulkan/vk_instance.h" #include "video_core/renderer_vulkan/vk_scheduler.h" @@ -58,58 +59,6 @@ void Scheduler::EndRendering() { } is_rendering = false; current_cmdbuf.endRendering(); - - boost::container::static_vector barriers; - for (size_t i = 0; i < render_state.num_color_attachments; ++i) { - barriers.push_back(vk::ImageMemoryBarrier{ - .srcAccessMask = vk::AccessFlagBits::eColorAttachmentWrite, - .dstAccessMask = vk::AccessFlagBits::eShaderRead | vk::AccessFlagBits::eShaderWrite, - .oldLayout = vk::ImageLayout::eColorAttachmentOptimal, - .newLayout = vk::ImageLayout::eColorAttachmentOptimal, - .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .image = render_state.color_images[i], - .subresourceRange = - { - .aspectMask = vk::ImageAspectFlagBits::eColor, - .baseMipLevel = 0, - .levelCount = VK_REMAINING_MIP_LEVELS, - .baseArrayLayer = 0, - .layerCount = VK_REMAINING_ARRAY_LAYERS, - }, - }); - } - if (render_state.has_depth || render_state.has_stencil) { - barriers.push_back(vk::ImageMemoryBarrier{ - .srcAccessMask = vk::AccessFlagBits::eDepthStencilAttachmentWrite, - .dstAccessMask = vk::AccessFlagBits::eShaderRead | vk::AccessFlagBits::eShaderWrite, - .oldLayout = render_state.depth_attachment.imageLayout, - .newLayout = render_state.depth_attachment.imageLayout, - .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .image = render_state.depth_image, - .subresourceRange = - { - .aspectMask = vk::ImageAspectFlagBits::eDepth | - (render_state.has_stencil ? vk::ImageAspectFlagBits::eStencil - : vk::ImageAspectFlagBits::eNone), - .baseMipLevel = 0, - .levelCount = VK_REMAINING_MIP_LEVELS, - .baseArrayLayer = 0, - .layerCount = VK_REMAINING_ARRAY_LAYERS, - }, - }); - } - - if (!barriers.empty()) { - const auto src_stages = - vk::PipelineStageFlagBits::eColorAttachmentOutput | - (render_state.has_depth ? vk::PipelineStageFlagBits::eLateFragmentTests | - vk::PipelineStageFlagBits::eEarlyFragmentTests - : vk::PipelineStageFlagBits::eNone); - current_cmdbuf.pipelineBarrier(src_stages, vk::PipelineStageFlagBits::eFragmentShader, - vk::DependencyFlagBits::eByRegion, {}, {}, barriers); - } } void Scheduler::Flush(SubmitInfo& info) { @@ -190,6 +139,7 @@ void Scheduler::SubmitExecution(SubmitInfo& info) { }; try { + ImGui::Core::TextureManager::Submit(); instance.GetGraphicsQueue().submit(submit_info, info.fence); } catch (vk::DeviceLostError& err) { UNREACHABLE_MSG("Device lost during submit: {}", err.what()); diff --git a/src/video_core/texture_cache/image.cpp b/src/video_core/texture_cache/image.cpp index 9e8c38f0d..4ce6e1eea 100644 --- a/src/video_core/texture_cache/image.cpp +++ b/src/video_core/texture_cache/image.cpp @@ -2,6 +2,7 @@ // SPDX-License-Identifier: GPL-2.0-or-later #define VULKAN_HPP_NO_EXCEPTIONS +#include #include "common/assert.h" #include "video_core/renderer_vulkan/liverpool_to_vk.h" #include "video_core/renderer_vulkan/vk_instance.h" @@ -124,7 +125,7 @@ Image::Image(const Vulkan::Instance& instance_, Vulkan::Scheduler& scheduler_, // the texture cache should re-create the resource with the usage requested vk::ImageCreateFlags flags{vk::ImageCreateFlagBits::eMutableFormat | vk::ImageCreateFlagBits::eExtendedUsage}; - if (info.props.is_cube) { + if (info.props.is_cube || (info.type == vk::ImageType::e2D && info.resources.layers >= 6)) { flags |= vk::ImageCreateFlagBits::eCubeCompatible; } else if (info.props.is_volume) { flags |= vk::ImageCreateFlagBits::e2DArrayCompatible; @@ -179,52 +180,132 @@ Image::Image(const Vulkan::Instance& instance_, Vulkan::Scheduler& scheduler_, info.guest_size_bytes); } -void Image::Transit(vk::ImageLayout dst_layout, vk::Flags dst_mask, - vk::CommandBuffer cmdbuf) { - if (dst_layout == layout && dst_mask == access_mask) { - return; +boost::container::small_vector Image::GetBarriers( + vk::ImageLayout dst_layout, vk::Flags dst_mask, + vk::PipelineStageFlags2 dst_stage, std::optional subres_range) { + const bool needs_partial_transition = + subres_range && + (subres_range->base != SubresourceBase{} || subres_range->extent != info.resources); + const bool partially_transited = !subresource_states.empty(); + + boost::container::small_vector barriers{}; + if (needs_partial_transition || partially_transited) { + if (!partially_transited) { + subresource_states.resize(info.resources.levels * info.resources.layers); + std::fill(subresource_states.begin(), subresource_states.end(), last_state); + } + + // In case of partial transition, we need to change the specified subresources only. + // Otherwise all subresources need to be set to the same state so we can use a full + // resource transition for the next time. + const auto mips = + needs_partial_transition + ? std::ranges::views::iota(subres_range->base.level, + subres_range->base.level + subres_range->extent.levels) + : std::views::iota(0u, info.resources.levels); + const auto layers = + needs_partial_transition + ? std::ranges::views::iota(subres_range->base.layer, + subres_range->base.layer + subres_range->extent.layers) + : std::views::iota(0u, info.resources.layers); + + for (u32 mip : mips) { + for (u32 layer : layers) { + // NOTE: these loops may produce a lot of small barriers. + // If this becomes a problem, we can optimize it by merging adjacent barriers. + const auto subres_idx = mip * info.resources.layers + layer; + ASSERT(subres_idx < subresource_states.size()); + auto& state = subresource_states[subres_idx]; + + if (state.layout != dst_layout || state.access_mask != dst_mask) { + barriers.emplace_back(vk::ImageMemoryBarrier2{ + .srcStageMask = state.pl_stage, + .srcAccessMask = state.access_mask, + .dstStageMask = dst_stage, + .dstAccessMask = dst_mask, + .oldLayout = state.layout, + .newLayout = dst_layout, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = image, + .subresourceRange{ + .aspectMask = aspect_mask, + .baseMipLevel = mip, + .levelCount = 1, + .baseArrayLayer = layer, + .layerCount = 1, + }, + }); + state.layout = dst_layout; + state.access_mask = dst_mask; + state.pl_stage = dst_stage; + } + } + } + + if (!needs_partial_transition) { + subresource_states.clear(); + } + } else { // Full resource transition + if (last_state.layout == dst_layout && last_state.access_mask == dst_mask) { + return {}; + } + + barriers.emplace_back(vk::ImageMemoryBarrier2{ + .srcStageMask = last_state.pl_stage, + .srcAccessMask = last_state.access_mask, + .dstStageMask = dst_stage, + .dstAccessMask = dst_mask, + .oldLayout = last_state.layout, + .newLayout = dst_layout, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = image, + .subresourceRange{ + .aspectMask = aspect_mask, + .baseMipLevel = 0, + .levelCount = VK_REMAINING_MIP_LEVELS, + .baseArrayLayer = 0, + .layerCount = VK_REMAINING_ARRAY_LAYERS, + }, + }); } - const vk::ImageMemoryBarrier barrier = { - .srcAccessMask = access_mask, - .dstAccessMask = dst_mask, - .oldLayout = layout, - .newLayout = dst_layout, - .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .image = image, - .subresourceRange{ - .aspectMask = aspect_mask, - .baseMipLevel = 0, - .levelCount = VK_REMAINING_MIP_LEVELS, - .baseArrayLayer = 0, - .layerCount = VK_REMAINING_ARRAY_LAYERS, - }, - }; + last_state.layout = dst_layout; + last_state.access_mask = dst_mask; + last_state.pl_stage = dst_stage; + return barriers; +} + +void Image::Transit(vk::ImageLayout dst_layout, vk::Flags dst_mask, + std::optional range, vk::CommandBuffer cmdbuf /*= {}*/) { // Adjust pipieline stage - const vk::PipelineStageFlags dst_pl_stage = - (dst_mask == vk::AccessFlagBits::eTransferRead || - dst_mask == vk::AccessFlagBits::eTransferWrite) - ? vk::PipelineStageFlagBits::eTransfer - : vk::PipelineStageFlagBits::eAllGraphics | vk::PipelineStageFlagBits::eComputeShader; + const vk::PipelineStageFlags2 dst_pl_stage = + (dst_mask == vk::AccessFlagBits2::eTransferRead || + dst_mask == vk::AccessFlagBits2::eTransferWrite) + ? vk::PipelineStageFlagBits2::eTransfer + : vk::PipelineStageFlagBits2::eAllGraphics | vk::PipelineStageFlagBits2::eComputeShader; + + const auto barriers = GetBarriers(dst_layout, dst_mask, dst_pl_stage, range); + if (barriers.empty()) { + return; + } if (!cmdbuf) { // When using external cmdbuf you are responsible for ending rp. scheduler->EndRendering(); cmdbuf = scheduler->CommandBuffer(); } - cmdbuf.pipelineBarrier(pl_stage, dst_pl_stage, vk::DependencyFlagBits::eByRegion, {}, {}, - barrier); - - layout = dst_layout; - access_mask = dst_mask; - pl_stage = dst_pl_stage; + cmdbuf.pipelineBarrier2(vk::DependencyInfo{ + .imageMemoryBarrierCount = static_cast(barriers.size()), + .pImageMemoryBarriers = barriers.data(), + }); } void Image::Upload(vk::Buffer buffer, u64 offset) { scheduler->EndRendering(); - Transit(vk::ImageLayout::eTransferDstOptimal, vk::AccessFlagBits::eTransferWrite); + Transit(vk::ImageLayout::eTransferDstOptimal, vk::AccessFlagBits2::eTransferWrite, {}); // Copy to the image. const auto aspect = aspect_mask & vk::ImageAspectFlagBits::eStencil @@ -248,12 +329,12 @@ void Image::Upload(vk::Buffer buffer, u64 offset) { cmdbuf.copyBufferToImage(buffer, image, vk::ImageLayout::eTransferDstOptimal, image_copy); Transit(vk::ImageLayout::eGeneral, - vk::AccessFlagBits::eShaderRead | vk::AccessFlagBits::eTransferRead); + vk::AccessFlagBits2::eShaderRead | vk::AccessFlagBits2::eTransferRead, {}); } void Image::CopyImage(const Image& image) { scheduler->EndRendering(); - Transit(vk::ImageLayout::eTransferDstOptimal, vk::AccessFlagBits::eTransferWrite); + Transit(vk::ImageLayout::eTransferDstOptimal, vk::AccessFlagBits2::eTransferWrite, {}); auto cmdbuf = scheduler->CommandBuffer(); @@ -279,15 +360,16 @@ void Image::CopyImage(const Image& image) { .extent = {mip_w, mip_h, mip_d}, }); } - cmdbuf.copyImage(image.image, image.layout, this->image, this->layout, image_copy); + cmdbuf.copyImage(image.image, image.last_state.layout, this->image, this->last_state.layout, + image_copy); Transit(vk::ImageLayout::eGeneral, - vk::AccessFlagBits::eShaderRead | vk::AccessFlagBits::eTransferRead); + vk::AccessFlagBits2::eShaderRead | vk::AccessFlagBits2::eTransferRead, {}); } void Image::CopyMip(const Image& image, u32 mip) { scheduler->EndRendering(); - Transit(vk::ImageLayout::eTransferDstOptimal, vk::AccessFlagBits::eTransferWrite); + Transit(vk::ImageLayout::eTransferDstOptimal, vk::AccessFlagBits2::eTransferWrite, {}); auto cmdbuf = scheduler->CommandBuffer(); @@ -313,10 +395,11 @@ void Image::CopyMip(const Image& image, u32 mip) { }, .extent = {mip_w, mip_h, mip_d}, }; - cmdbuf.copyImage(image.image, image.layout, this->image, this->layout, image_copy); + cmdbuf.copyImage(image.image, image.last_state.layout, this->image, this->last_state.layout, + image_copy); Transit(vk::ImageLayout::eGeneral, - vk::AccessFlagBits::eShaderRead | vk::AccessFlagBits::eTransferRead); + vk::AccessFlagBits2::eShaderRead | vk::AccessFlagBits2::eTransferRead, {}); } Image::~Image() = default; diff --git a/src/video_core/texture_cache/image.h b/src/video_core/texture_cache/image.h index 1bbb975ba..01e6fe8f3 100644 --- a/src/video_core/texture_cache/image.h +++ b/src/video_core/texture_cache/image.h @@ -5,13 +5,9 @@ #include "common/enum.h" #include "common/types.h" -#include "core/libraries/videoout/buffer.h" -#include "video_core/amdgpu/liverpool.h" -#include "video_core/amdgpu/resource.h" #include "video_core/renderer_vulkan/vk_common.h" #include "video_core/texture_cache/image_info.h" #include "video_core/texture_cache/image_view.h" -#include "video_core/texture_cache/types.h" #include @@ -26,12 +22,16 @@ VK_DEFINE_HANDLE(VmaAllocator) namespace VideoCore { enum ImageFlagBits : u32 { - CpuModified = 1 << 2, ///< Contents have been modified from the CPU + CpuDirty = 1 << 1, ///< Contents have been modified from the CPU + GpuDirty = 1 << 2, ///< Contents have been modified from the GPU (valid data in buffer cache) + Dirty = CpuDirty | GpuDirty, GpuModified = 1 << 3, ///< Contents have been modified from the GPU Tracked = 1 << 4, ///< Writes and reads are being hooked from the CPU Registered = 1 << 6, ///< True when the image is registered Picked = 1 << 7, ///< Temporary flag to mark the image as picked MetaRegistered = 1 << 8, ///< True when metadata for this surface is known and registered + Bound = 1 << 9, ///< True when the image is bound to a descriptor set + NeedsRebind = 1 << 10, ///< True when the image needs to be rebound }; DECLARE_ENUM_FLAG_OPERATORS(ImageFlagBits) @@ -91,8 +91,11 @@ struct Image { return image_view_ids[std::distance(image_view_infos.begin(), it)]; } - void Transit(vk::ImageLayout dst_layout, vk::Flags dst_mask, - vk::CommandBuffer cmdbuf = {}); + boost::container::small_vector GetBarriers( + vk::ImageLayout dst_layout, vk::Flags dst_mask, + vk::PipelineStageFlags2 dst_stage, std::optional subres_range); + void Transit(vk::ImageLayout dst_layout, vk::Flags dst_mask, + std::optional range, vk::CommandBuffer cmdbuf = {}); void Upload(vk::Buffer buffer, u64 offset); void CopyImage(const Image& image); @@ -103,7 +106,7 @@ struct Image { ImageInfo info; UniqueImage image; vk::ImageAspectFlags aspect_mask = vk::ImageAspectFlagBits::eColor; - ImageFlagBits flags = ImageFlagBits::CpuModified; + ImageFlagBits flags = ImageFlagBits::Dirty; VAddr cpu_addr = 0; VAddr cpu_addr_end = 0; std::vector image_view_infos; @@ -111,10 +114,14 @@ struct Image { // Resource state tracking vk::ImageUsageFlags usage; - vk::Flags pl_stage = vk::PipelineStageFlagBits::eAllCommands; - vk::Flags access_mask = vk::AccessFlagBits::eNone; - vk::ImageLayout layout = vk::ImageLayout::eUndefined; - boost::container::small_vector mip_hashes; + struct State { + vk::Flags pl_stage = vk::PipelineStageFlagBits2::eAllCommands; + vk::Flags access_mask = vk::AccessFlagBits2::eNone; + vk::ImageLayout layout = vk::ImageLayout::eUndefined; + }; + State last_state{}; + std::vector subresource_states{}; + boost::container::small_vector mip_hashes{}; u64 tick_accessed_last{0}; }; diff --git a/src/video_core/texture_cache/image_info.cpp b/src/video_core/texture_cache/image_info.cpp index 011e19db8..521e4118f 100644 --- a/src/video_core/texture_cache/image_info.cpp +++ b/src/video_core/texture_cache/image_info.cpp @@ -200,18 +200,12 @@ ImageInfo::ImageInfo(const AmdGpu::Liverpool::DepthBuffer& buffer, u32 num_slice mips_layout.emplace_back(depth_slice_sz, pitch, 0); } -ImageInfo::ImageInfo(const AmdGpu::Image& image, bool force_depth /*= false*/) noexcept { +ImageInfo::ImageInfo(const AmdGpu::Image& image, const Shader::ImageResource& desc) noexcept { tiling_mode = image.GetTilingMode(); pixel_format = LiverpoolToVK::SurfaceFormat(image.GetDataFmt(), image.GetNumberFmt()); // Override format if image is forced to be a depth target - if (force_depth) { - if (pixel_format == vk::Format::eR32Sfloat || pixel_format == vk::Format::eR8Unorm) { - pixel_format = vk::Format::eD32SfloatS8Uint; - } else if (pixel_format == vk::Format::eR16Unorm) { - pixel_format = vk::Format::eD16UnormS8Uint; - } else { - UNREACHABLE(); - } + if (desc.is_depth) { + pixel_format = LiverpoolToVK::PromoteFormatToDepth(pixel_format); } type = ConvertImageType(image.GetType()); props.is_tiled = image.IsTiled(); @@ -224,7 +218,7 @@ ImageInfo::ImageInfo(const AmdGpu::Image& image, bool force_depth /*= false*/) n size.depth = props.is_volume ? image.depth + 1 : 1; pitch = image.Pitch(); resources.levels = image.NumLevels(); - resources.layers = image.NumLayers(); + resources.layers = image.NumLayers(desc.is_array); num_bits = NumBits(image.GetDataFmt()); usage.texture = true; diff --git a/src/video_core/texture_cache/image_info.h b/src/video_core/texture_cache/image_info.h index ba8985b8f..2ae2547f7 100644 --- a/src/video_core/texture_cache/image_info.h +++ b/src/video_core/texture_cache/image_info.h @@ -5,6 +5,7 @@ #include "common/types.h" #include "core/libraries/videoout/buffer.h" +#include "shader_recompiler/info.h" #include "video_core/amdgpu/liverpool.h" #include "video_core/texture_cache/types.h" @@ -19,7 +20,7 @@ struct ImageInfo { const AmdGpu::Liverpool::CbDbExtent& hint = {}) noexcept; ImageInfo(const AmdGpu::Liverpool::DepthBuffer& buffer, u32 num_slices, VAddr htile_address, const AmdGpu::Liverpool::CbDbExtent& hint = {}) noexcept; - ImageInfo(const AmdGpu::Image& image, bool force_depth = false) noexcept; + ImageInfo(const AmdGpu::Image& image, const Shader::ImageResource& desc) noexcept; bool IsTiled() const { return tiling_mode != AmdGpu::TilingMode::Display_Linear; diff --git a/src/video_core/texture_cache/image_view.cpp b/src/video_core/texture_cache/image_view.cpp index f94c1a37b..2aad1afb6 100644 --- a/src/video_core/texture_cache/image_view.cpp +++ b/src/video_core/texture_cache/image_view.cpp @@ -2,6 +2,7 @@ // SPDX-License-Identifier: GPL-2.0-or-later #include "common/logging/log.h" +#include "shader_recompiler/info.h" #include "video_core/amdgpu/resource.h" #include "video_core/renderer_vulkan/liverpool_to_vk.h" #include "video_core/renderer_vulkan/vk_instance.h" @@ -66,19 +67,40 @@ vk::Format TrySwizzleFormat(vk::Format format, u32 dst_sel) { return format; } -ImageViewInfo::ImageViewInfo(const AmdGpu::Image& image, bool is_storage_) noexcept - : is_storage{is_storage_} { - type = ConvertImageViewType(image.GetType()); +ImageViewInfo::ImageViewInfo(const AmdGpu::Image& image, const Shader::ImageResource& desc) noexcept + : is_storage{desc.is_storage} { const auto dfmt = image.GetDataFmt(); auto nfmt = image.GetNumberFmt(); if (is_storage && nfmt == AmdGpu::NumberFormat::Srgb) { nfmt = AmdGpu::NumberFormat::Unorm; } format = Vulkan::LiverpoolToVK::SurfaceFormat(dfmt, nfmt); + if (desc.is_depth) { + format = Vulkan::LiverpoolToVK::PromoteFormatToDepth(format); + } range.base.level = image.base_level; range.base.layer = image.base_array; - range.extent.levels = image.last_level + 1; - range.extent.layers = image.last_array + 1; + range.extent.levels = image.last_level - image.base_level + 1; + range.extent.layers = image.last_array - image.base_array + 1; + type = ConvertImageViewType(image.GetType()); + + // Adjust view type for partial cubemaps and arrays + if (image.IsPartialCubemap()) { + type = vk::ImageViewType::e2DArray; + } + if (type == vk::ImageViewType::eCube) { + if (desc.is_array) { + type = vk::ImageViewType::eCubeArray; + } else { + // Some games try to bind an array of cubemaps while shader reads only single one. + range.extent.layers = std::min(range.extent.layers, 6u); + } + } + if (type == vk::ImageViewType::e3D && range.extent.layers > 1) { + // Some games pass incorrect layer count for 3D textures so we need to fixup it. + range.extent.layers = 1; + } + if (!is_storage) { mapping.r = ConvertComponentSwizzle(image.dst_sel_x); mapping.g = ConvertComponentSwizzle(image.dst_sel_y); @@ -103,7 +125,7 @@ ImageViewInfo::ImageViewInfo(const AmdGpu::Liverpool::ColorBuffer& col_buffer, const auto base_format = Vulkan::LiverpoolToVK::SurfaceFormat(col_buffer.info.format, col_buffer.NumFormat()); range.base.layer = col_buffer.view.slice_start; - range.extent.layers = col_buffer.NumSlices(); + range.extent.layers = col_buffer.NumSlices() - range.base.layer; format = Vulkan::LiverpoolToVK::AdjustColorBufferFormat( base_format, col_buffer.info.comp_swap.Value(), is_vo_surface); } @@ -115,7 +137,7 @@ ImageViewInfo::ImageViewInfo(const AmdGpu::Liverpool::DepthBuffer& depth_buffer, depth_buffer.stencil_info.format); is_storage = ctl.depth_write_enable; range.base.layer = view.slice_start; - range.extent.layers = view.NumSlices(); + range.extent.layers = view.NumSlices() - range.base.layer; } ImageView::ImageView(const Vulkan::Instance& instance, const ImageViewInfo& info_, Image& image, @@ -147,9 +169,9 @@ ImageView::ImageView(const Vulkan::Instance& instance, const ImageViewInfo& info .subresourceRange{ .aspectMask = aspect, .baseMipLevel = info.range.base.level, - .levelCount = info.range.extent.levels - info.range.base.level, + .levelCount = info.range.extent.levels, .baseArrayLayer = info.range.base.layer, - .layerCount = info.range.extent.layers - info.range.base.layer, + .layerCount = info.range.extent.layers, }, }; image_view = instance.GetDevice().createImageViewUnique(image_view_ci); diff --git a/src/video_core/texture_cache/image_view.h b/src/video_core/texture_cache/image_view.h index 7d53590dd..ba8d2c72b 100644 --- a/src/video_core/texture_cache/image_view.h +++ b/src/video_core/texture_cache/image_view.h @@ -3,6 +3,7 @@ #pragma once +#include "shader_recompiler/info.h" #include "video_core/amdgpu/liverpool.h" #include "video_core/amdgpu/resource.h" #include "video_core/renderer_vulkan/vk_common.h" @@ -17,7 +18,7 @@ namespace VideoCore { struct ImageViewInfo { ImageViewInfo() = default; - ImageViewInfo(const AmdGpu::Image& image, bool is_storage) noexcept; + ImageViewInfo(const AmdGpu::Image& image, const Shader::ImageResource& desc) noexcept; ImageViewInfo(const AmdGpu::Liverpool::ColorBuffer& col_buffer, bool is_vo_surface) noexcept; ImageViewInfo(const AmdGpu::Liverpool::DepthBuffer& depth_buffer, AmdGpu::Liverpool::DepthView view, AmdGpu::Liverpool::DepthControl ctl); diff --git a/src/video_core/texture_cache/texture_cache.cpp b/src/video_core/texture_cache/texture_cache.cpp index 3219f45b9..4813a3c57 100644 --- a/src/video_core/texture_cache/texture_cache.cpp +++ b/src/video_core/texture_cache/texture_cache.cpp @@ -29,10 +29,16 @@ TextureCache::TextureCache(const Vulkan::Instance& instance_, Vulkan::Scheduler& info.UpdateSize(); const ImageId null_id = slot_images.insert(instance, scheduler, info); ASSERT(null_id.index == 0); + const vk::Image& null_image = slot_images[null_id].image; + Vulkan::SetObjectName(instance.GetDevice(), null_image, "Null Image"); slot_images[null_id].flags = ImageFlagBits::Tracked; ImageViewInfo view_info; - void(slot_image_views.insert(instance, view_info, slot_images[null_id], null_id)); + const auto null_view_id = + slot_image_views.insert(instance, view_info, slot_images[null_id], null_id); + ASSERT(null_view_id.index == 0); + const vk::ImageView& null_image_view = slot_image_views[null_view_id].image_view.get(); + Vulkan::SetObjectName(instance.GetDevice(), null_image_view, "Null Image View"); } TextureCache::~TextureCache() = default; @@ -41,24 +47,23 @@ void TextureCache::InvalidateMemory(VAddr address, size_t size) { std::scoped_lock lock{mutex}; ForEachImageInRegion(address, size, [&](ImageId image_id, Image& image) { // Ensure image is reuploaded when accessed again. - image.flags |= ImageFlagBits::CpuModified; + image.flags |= ImageFlagBits::CpuDirty; // Untrack image, so the range is unprotected and the guest can write freely. UntrackImage(image_id); }); } -void TextureCache::MarkWritten(VAddr address, size_t max_size) { - static constexpr FindFlags find_flags = - FindFlags::NoCreate | FindFlags::RelaxDim | FindFlags::RelaxFmt | FindFlags::RelaxSize; - ImageInfo info{}; - info.guest_address = address; - info.guest_size_bytes = max_size; - const ImageId image_id = FindImage(info, find_flags); - if (!image_id) { - return; - } - // Ensure image is copied when accessed again. - slot_images[image_id].flags |= ImageFlagBits::CpuModified; +void TextureCache::InvalidateMemoryFromGPU(VAddr address, size_t max_size) { + std::scoped_lock lock{mutex}; + ForEachImageInRegion(address, max_size, [&](ImageId image_id, Image& image) { + // Only consider images that match base address. + // TODO: Maybe also consider subresources + if (image.info.guest_address != address) { + return; + } + // Ensure image is reuploaded when accessed again. + image.flags |= ImageFlagBits::GpuDirty; + }); } void TextureCache::UnmapMemory(VAddr cpu_addr, size_t size) { @@ -81,8 +86,7 @@ ImageId TextureCache::ResolveDepthOverlap(const ImageInfo& requested_info, Image auto new_image_id = slot_images.insert(instance, scheduler, requested_info); RegisterImage(new_image_id); - // auto& new_image = slot_images[new_image_id]; - // TODO: need to run a helper for depth copy here + // TODO: perform a depth copy here FreeImage(cache_image_id); return new_image_id; @@ -92,7 +96,11 @@ ImageId TextureCache::ResolveDepthOverlap(const ImageInfo& requested_info, Image !requested_info.usage.depth_target && (requested_info.usage.texture || requested_info.usage.storage); if (cache_info.usage.depth_target && should_bind_as_texture) { - return cache_image_id; + if (cache_info.resources == requested_info.resources) { + return cache_image_id; + } else { + UNREACHABLE(); + } } return {}; @@ -148,7 +156,7 @@ ImageId TextureCache::ResolveOverlap(const ImageInfo& image_info, ImageId cache_ if (tex_cache_image.info.IsMipOf(image_info)) { tex_cache_image.Transit(vk::ImageLayout::eTransferSrcOptimal, - vk::AccessFlagBits::eTransferRead); + vk::AccessFlagBits2::eTransferRead, {}); const auto num_mips_to_copy = tex_cache_image.info.resources.levels; ASSERT(num_mips_to_copy == 1); @@ -170,13 +178,17 @@ ImageId TextureCache::ExpandImage(const ImageInfo& info, ImageId image_id) { auto& src_image = slot_images[image_id]; auto& new_image = slot_images[new_image_id]; - src_image.Transit(vk::ImageLayout::eTransferSrcOptimal, vk::AccessFlagBits::eTransferRead); + src_image.Transit(vk::ImageLayout::eTransferSrcOptimal, vk::AccessFlagBits2::eTransferRead, {}); new_image.CopyImage(src_image); + if (True(src_image.flags & ImageFlagBits::Bound)) { + src_image.flags |= ImageFlagBits::NeedsRebind; + } + FreeImage(image_id); TrackImage(new_image_id); - new_image.flags &= ~ImageFlagBits::CpuModified; + new_image.flags &= ~ImageFlagBits::Dirty; return new_image_id; } @@ -249,21 +261,21 @@ ImageView& TextureCache::RegisterImageView(ImageId image_id, const ImageViewInfo return slot_image_views[view_id]; } -ImageView& TextureCache::FindTexture(const ImageInfo& info, const ImageViewInfo& view_info) { - const ImageId image_id = FindImage(info); +ImageView& TextureCache::FindTexture(ImageId image_id, const ImageViewInfo& view_info) { Image& image = slot_images[image_id]; UpdateImage(image_id); auto& usage = image.info.usage; if (view_info.is_storage) { image.Transit(vk::ImageLayout::eGeneral, - vk::AccessFlagBits::eShaderRead | vk::AccessFlagBits::eShaderWrite); + vk::AccessFlagBits2::eShaderRead | vk::AccessFlagBits2::eShaderWrite, + view_info.range); usage.storage = true; } else { const auto new_layout = image.info.IsDepthStencil() ? vk::ImageLayout::eDepthStencilReadOnlyOptimal : vk::ImageLayout::eShaderReadOnlyOptimal; - image.Transit(new_layout, vk::AccessFlagBits::eShaderRead); + image.Transit(new_layout, vk::AccessFlagBits2::eShaderRead, view_info.range); usage.texture = true; } @@ -278,8 +290,9 @@ ImageView& TextureCache::FindRenderTarget(const ImageInfo& image_info, UpdateImage(image_id); image.Transit(vk::ImageLayout::eColorAttachmentOptimal, - vk::AccessFlagBits::eColorAttachmentWrite | - vk::AccessFlagBits::eColorAttachmentRead); + vk::AccessFlagBits2::eColorAttachmentWrite | + vk::AccessFlagBits2::eColorAttachmentRead, + view_info.range); // Register meta data for this color buffer if (!(image.flags & ImageFlagBits::MetaRegistered)) { @@ -311,7 +324,7 @@ ImageView& TextureCache::FindDepthTarget(const ImageInfo& image_info, const ImageId image_id = FindImage(image_info); Image& image = slot_images[image_id]; image.flags |= ImageFlagBits::GpuModified; - image.flags &= ~ImageFlagBits::CpuModified; + image.flags &= ~ImageFlagBits::Dirty; image.aspect_mask = vk::ImageAspectFlagBits::eDepth; const bool has_stencil = image_info.usage.stencil; @@ -324,8 +337,10 @@ ImageView& TextureCache::FindDepthTarget(const ImageInfo& image_info, : vk::ImageLayout::eDepthAttachmentOptimal : has_stencil ? vk::ImageLayout::eDepthStencilReadOnlyOptimal : vk::ImageLayout::eDepthReadOnlyOptimal; - image.Transit(new_layout, vk::AccessFlagBits::eDepthStencilAttachmentWrite | - vk::AccessFlagBits::eDepthStencilAttachmentRead); + image.Transit(new_layout, + vk::AccessFlagBits2::eDepthStencilAttachmentWrite | + vk::AccessFlagBits2::eDepthStencilAttachmentRead, + view_info.range); // Register meta data for this depth buffer if (!(image.flags & ImageFlagBits::MetaRegistered)) { @@ -346,11 +361,9 @@ ImageView& TextureCache::FindDepthTarget(const ImageInfo& image_info, } void TextureCache::RefreshImage(Image& image, Vulkan::Scheduler* custom_scheduler /*= nullptr*/) { - if (False(image.flags & ImageFlagBits::CpuModified)) { + if (False(image.flags & ImageFlagBits::Dirty)) { return; } - // Mark image as validated. - image.flags &= ~ImageFlagBits::CpuModified; const auto& num_layers = image.info.resources.layers; const auto& num_mips = image.info.resources.levels; @@ -364,9 +377,10 @@ void TextureCache::RefreshImage(Image& image, Vulkan::Scheduler* custom_schedule image.info.props.is_volume ? std::max(image.info.size.depth >> m, 1u) : 1u; const auto& [mip_size, mip_pitch, mip_height, mip_ofs] = image.info.mips_layout[m]; - // Protect GPU modified resources from accidental reuploads. - if (True(image.flags & ImageFlagBits::GpuModified) && - !buffer_cache.IsRegionGpuModified(image.info.guest_address + mip_ofs, mip_size)) { + // Protect GPU modified resources from accidental CPU reuploads. + const bool is_gpu_modified = True(image.flags & ImageFlagBits::GpuModified); + const bool is_gpu_dirty = True(image.flags & ImageFlagBits::GpuDirty); + if (is_gpu_modified && !is_gpu_dirty) { const u8* addr = std::bit_cast(image.info.guest_address); const u64 hash = XXH3_64bits(addr + mip_ofs, mip_size); if (image.mip_hashes[m] == hash) { @@ -398,7 +412,8 @@ void TextureCache::RefreshImage(Image& image, Vulkan::Scheduler* custom_schedule sched_ptr->EndRendering(); const auto cmdbuf = sched_ptr->CommandBuffer(); - image.Transit(vk::ImageLayout::eTransferDstOptimal, vk::AccessFlagBits::eTransferWrite, cmdbuf); + image.Transit(vk::ImageLayout::eTransferDstOptimal, vk::AccessFlagBits2::eTransferWrite, {}, + cmdbuf); const VAddr image_addr = image.info.guest_address; const size_t image_size = image.info.guest_size_bytes; @@ -421,6 +436,7 @@ void TextureCache::RefreshImage(Image& image, Vulkan::Scheduler* custom_schedule } cmdbuf.copyBufferToImage(buffer, image.image, vk::ImageLayout::eTransferDstOptimal, image_copy); + image.flags &= ~ImageFlagBits::Dirty; } vk::Sampler TextureCache::GetSampler(const AmdGpu::Sampler& sampler) { diff --git a/src/video_core/texture_cache/texture_cache.h b/src/video_core/texture_cache/texture_cache.h index cc19ac4a8..3bbfd952c 100644 --- a/src/video_core/texture_cache/texture_cache.h +++ b/src/video_core/texture_cache/texture_cache.h @@ -51,7 +51,7 @@ public: void InvalidateMemory(VAddr address, size_t size); /// Marks an image as dirty if it exists at the provided address. - void MarkWritten(VAddr address, size_t max_size); + void InvalidateMemoryFromGPU(VAddr address, size_t max_size); /// Evicts any images that overlap the unmapped range. void UnmapMemory(VAddr cpu_addr, size_t size); @@ -59,9 +59,8 @@ public: /// Retrieves the image handle of the image with the provided attributes. [[nodiscard]] ImageId FindImage(const ImageInfo& info, FindFlags flags = {}); - /// Retrieves an image view with the properties of the specified image descriptor. - [[nodiscard]] ImageView& FindTexture(const ImageInfo& image_info, - const ImageViewInfo& view_info); + /// Retrieves an image view with the properties of the specified image id. + [[nodiscard]] ImageView& FindTexture(ImageId image_id, const ImageViewInfo& view_info); /// Retrieves the render target with specified properties [[nodiscard]] ImageView& FindRenderTarget(const ImageInfo& image_info, @@ -98,6 +97,11 @@ public: return slot_images[id]; } + /// Retrieves the image view with the specified id. + [[nodiscard]] ImageView& GetImageView(ImageId id) { + return slot_image_views[id]; + } + bool IsMeta(VAddr address) const { return surface_metas.contains(address); }