mirror of
https://github.com/shadps4-emu/shadPS4.git
synced 2025-04-22 04:24:44 +00:00
Merge branch 'main' into pat
This commit is contained in:
commit
4978346443
136 changed files with 8229 additions and 3223 deletions
10
.github/workflows/linux-qt.yml
vendored
10
.github/workflows/linux-qt.yml
vendored
|
@ -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
|
||||
|
|
10
.github/workflows/linux.yml
vendored
10
.github/workflows/linux.yml
vendored
|
@ -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
|
||||
|
|
10
.github/workflows/macos-qt.yml
vendored
10
.github/workflows/macos-qt.yml
vendored
|
@ -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)
|
||||
|
|
10
.github/workflows/macos.yml
vendored
10
.github/workflows/macos.yml
vendored
|
@ -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)
|
||||
|
|
23
.github/workflows/windows-qt.yml
vendored
23
.github/workflows/windows-qt.yml
vendored
|
@ -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
|
||||
|
|
21
.github/workflows/windows.yml
vendored
21
.github/workflows/windows.yml
vendored
|
@ -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
|
||||
|
|
|
@ -58,3 +58,7 @@ License: MIT
|
|||
Files: externals/tracy/*
|
||||
Copyright: 2017-2024 Bartosz Taudul <wolf@nereid.pl>
|
||||
License: BSD-3-Clause
|
||||
|
||||
Files: src/imgui/renderer/fonts/NotoSansJP-Regular.ttf
|
||||
Copyright: 2012 Google Inc. All Rights Reserved.
|
||||
License: OFL-1.1
|
||||
|
|
|
@ -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
|
||||
|
|
43
LICENSES/OFL-1.1.txt
Normal file
43
LICENSES/OFL-1.1.txt
Normal file
|
@ -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.
|
|
@ -12,7 +12,7 @@ SPDX-License-Identifier: GPL-2.0-or-later
|
|||
</h1>
|
||||
|
||||
<h1 align="center">
|
||||
<a href="https://discord.gg/MyZRaBngxA">
|
||||
<a href="https://discord.gg/bFJxfftGW6">
|
||||
<img src="https://img.shields.io/discord/1080089157554155590?color=5865F2&label=shadPS4 Discord&logo=Discord&logoColor=white" width="240">
|
||||
<a href="https://github.com/shadps4-emu/shadPS4/releases/latest">
|
||||
<img src="https://img.shields.io/github/downloads/shadps4-emu/shadPS4/total.svg" width="140">
|
||||
|
|
52
scripts/file_formats/sfo.hexpat
Normal file
52
scripts/file_formats/sfo.hexpat
Normal file
|
@ -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<auto fmt, auto size> {
|
||||
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<auto KeyTableOffset, auto DataTableOffset> {
|
||||
u64 begin = $;
|
||||
IndexEntry index;
|
||||
KeyEntry key @ KeyTableOffset + index.key_offset;
|
||||
DataEntry<index.param_fmt, index.param_len> 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<header.key_table_offset, header.data_table_offset> list[header.index_table_entries] @ 0x14;
|
157
src/common/cstring.h
Normal file
157
src/common/cstring.h
Normal file
|
@ -0,0 +1,157 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string_view>
|
||||
|
||||
#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 <size_t N, typename T = char>
|
||||
class CString {
|
||||
T data[N]{};
|
||||
|
||||
public:
|
||||
class Iterator;
|
||||
|
||||
CString() = default;
|
||||
|
||||
template <size_t M>
|
||||
explicit CString(const CString<M>& other)
|
||||
requires(M <= N)
|
||||
{
|
||||
if (this == nullptr) {
|
||||
return;
|
||||
}
|
||||
std::ranges::copy(other.begin(), other.end(), data);
|
||||
}
|
||||
|
||||
void FromString(const std::basic_string_view<T>& 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<T>() const {
|
||||
if (this == nullptr) {
|
||||
return {};
|
||||
}
|
||||
return std::basic_string_view<T>{data};
|
||||
}
|
||||
|
||||
explicit operator std::basic_string<T>() const {
|
||||
if (this == nullptr) {
|
||||
return {};
|
||||
}
|
||||
return std::basic_string<T>{data};
|
||||
}
|
||||
|
||||
std::basic_string<T> to_string() const {
|
||||
if (this == nullptr) {
|
||||
return {};
|
||||
}
|
||||
return std::basic_string<T>{data};
|
||||
}
|
||||
|
||||
std::basic_string_view<T> to_view() const {
|
||||
if (this == nullptr) {
|
||||
return {};
|
||||
}
|
||||
return std::basic_string_view<T>{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<CString<13>::Iterator>);
|
||||
|
||||
#pragma clang diagnostic pop
|
||||
|
||||
} // namespace Common
|
72
src/common/elf_info.h
Normal file
72
src/common/elf_info.h
Normal file
|
@ -0,0 +1,72 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
#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<ElfInfo>::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
|
|
@ -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
|
||||
|
|
|
@ -219,4 +219,6 @@ private:
|
|||
uintptr_t file_mapping = 0;
|
||||
};
|
||||
|
||||
u64 GetDirectorySize(const std::filesystem::path& path);
|
||||
|
||||
} // namespace Common::FS
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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<char>(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<std::string> SplitString(const std::string& str, char delimiter) {
|
||||
std::istringstream iss(str);
|
||||
std::vector<std::string> output(1);
|
||||
|
|
|
@ -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<std::string> SplitString(const std::string& str, char delimiter);
|
||||
|
||||
|
|
|
@ -3,12 +3,15 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <string>
|
||||
#include <thread>
|
||||
|
||||
#include "common/error.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/thread.h"
|
||||
#include "ntapi.h"
|
||||
#ifdef __APPLE__
|
||||
#include <mach/mach.h>
|
||||
#include <mach/mach_time.h>
|
||||
#include <pthread.h>
|
||||
#elif defined(_WIN32)
|
||||
#include <windows.h>
|
||||
|
@ -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<double>(timebase.denom) / static_cast<double>(timebase.numer);
|
||||
|
||||
const auto period_ticks =
|
||||
static_cast<u32>(static_cast<double>(period_ns.count()) * ticks_per_ns);
|
||||
const auto computation_ticks =
|
||||
static_cast<u32>(static_cast<double>(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<thread_policy_t>(&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<std::chrono::nanoseconds>(start_time - begin_sleep);
|
||||
}
|
||||
|
||||
void AccurateTimer::End() {
|
||||
auto now = std::chrono::high_resolution_clock::now();
|
||||
total_wait +=
|
||||
target_interval - std::chrono::duration_cast<std::chrono::nanoseconds>(now - start_time);
|
||||
}
|
||||
|
||||
} // namespace Common
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include <chrono>
|
||||
#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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -2,61 +2,275 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <cstring>
|
||||
|
||||
#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<std::string_view, u32> 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<u8>& 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<u8> psf(psfSize);
|
||||
file.Seek(0);
|
||||
file.Read(psf);
|
||||
file.Close();
|
||||
return Open(psf);
|
||||
}
|
||||
|
||||
bool PSF::Open(const std::vector<u8>& 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<PSFEntryFmt>(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<u8> 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<const char*>(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<u8> PSF::Encode() const {
|
||||
std::vector<u8> psf_buffer;
|
||||
Encode(psf_buffer);
|
||||
return psf_buffer;
|
||||
}
|
||||
|
||||
void PSF::Encode(std::vector<u8>& 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<u16>(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<const u8*>(&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<std::span<const u8>> 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<std::string_view> 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<s32> 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<u8> 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<std::vector<PSF::Entry>::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<std::vector<PSF::Entry>::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)};
|
||||
}
|
||||
|
|
|
@ -3,11 +3,18 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include <filesystem>
|
||||
#include <span>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
#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<u8>& 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<u8>& psf_buffer);
|
||||
|
||||
std::unordered_map<std::string, std::string> map_strings;
|
||||
std::unordered_map<std::string, u32> map_integers;
|
||||
[[nodiscard]] std::vector<u8> Encode() const;
|
||||
void Encode(std::vector<u8>& buf) const;
|
||||
bool Encode(const std::filesystem::path& filepath) const;
|
||||
|
||||
std::optional<std::span<const u8>> GetBinary(std::string_view key) const;
|
||||
std::optional<std::string_view> GetString(std::string_view key) const;
|
||||
std::optional<s32> GetInteger(std::string_view key) const;
|
||||
|
||||
void AddBinary(std::string key, std::vector<u8> 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<Entry>& GetEntries() const {
|
||||
return entry_list;
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<u8> psf;
|
||||
mutable std::filesystem::file_time_type last_write;
|
||||
|
||||
std::vector<Entry> entry_list;
|
||||
|
||||
std::unordered_map<size_t, std::vector<u8>> map_binaries;
|
||||
std::unordered_map<size_t, std::string> map_strings;
|
||||
std::unordered_map<size_t, s32> map_integers;
|
||||
|
||||
[[nodiscard]] std::pair<std::vector<Entry>::iterator, size_t> FindEntry(std::string_view key);
|
||||
[[nodiscard]] std::pair<std::vector<Entry>::const_iterator, size_t> FindEntry(
|
||||
std::string_view key) const;
|
||||
};
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
#include <cmath>
|
||||
|
||||
#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<PSF>::Instance();
|
||||
std::optional<s32> 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<PSF>::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)) {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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<Core::FileSys::HandleTable>::Instance();
|
||||
auto* mnt = Common::Singleton<Core::FileSys::MntPoints>::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<Core::FileSys::MntPoints>::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<Core::FileSys::MntPoints>::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<Core::FileSys::MntPoints>::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) {
|
||||
|
|
|
@ -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<PSF>::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,
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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<std::chrono::seconds>(duration);
|
||||
auto microsecs = std::chrono::duration_cast<std::chrono::microseconds>(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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -31,10 +31,6 @@ public:
|
|||
void Finish();
|
||||
|
||||
void Draw() override;
|
||||
|
||||
bool ShouldGrabGamepad() override {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
}; // namespace Libraries::NpTrophy
|
164
src/core/libraries/save_data/dialog/savedatadialog.cpp
Normal file
164
src/core/libraries/save_data/dialog/savedatadialog.cpp
Normal file
|
@ -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<SaveDialogState::ProgressBarState>().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<SaveDialogState::ProgressBarState>().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
|
33
src/core/libraries/save_data/dialog/savedatadialog.h
Normal file
33
src/core/libraries/save_data/dialog/savedatadialog.h
Normal file
|
@ -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
|
849
src/core/libraries/save_data/dialog/savedatadialog_ui.cpp
Normal file
849
src/core/libraries/save_data/dialog/savedatadialog_ui.cpp
Normal file
|
@ -0,0 +1,849 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <fmt/chrono.h>
|
||||
#include <imgui.h>
|
||||
#include <magic_enum.hpp>
|
||||
|
||||
#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<FocusPos>(state->focus_pos)) {
|
||||
auto pos = std::get<FocusPos>(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<std::string>(state->focus_pos)) {
|
||||
auto dir_name = std::get<std::string>(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<SaveDialogState::UserState>();
|
||||
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<float>(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<SaveDialogState::SystemState>();
|
||||
|
||||
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<float>(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<SaveDialogState::ErrorCodeState>();
|
||||
|
||||
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<SaveDialogState::ProgressBarState>();
|
||||
|
||||
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<float>(bar_state.progress) / 100.0f,
|
||||
{PROGRESS_BAR_WIDTH * ws.x, BUTTON_SIZE.y});
|
||||
}
|
||||
}; // namespace Libraries::SaveData::Dialog
|
319
src/core/libraries/save_data/dialog/savedatadialog_ui.h
Normal file
319
src/core/libraries/save_data/dialog/savedatadialog_ui.h
Normal file
|
@ -0,0 +1,319 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <mutex>
|
||||
#include <variant>
|
||||
#include <vector>
|
||||
|
||||
#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<u8, 32> _reserved;
|
||||
};
|
||||
|
||||
struct SaveDialogNewItem {
|
||||
const char* title;
|
||||
void* iconBuf;
|
||||
size_t iconSize;
|
||||
std::array<u8, 32> _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<u8, 32> _reserved;
|
||||
};
|
||||
|
||||
struct UserMessageParam {
|
||||
ButtonType buttonType;
|
||||
UserMessageType msgType;
|
||||
const char* msg;
|
||||
std::array<u8, 32> _reserved;
|
||||
};
|
||||
|
||||
struct SystemMessageParam {
|
||||
SystemMessageType msgType;
|
||||
s32 : 32;
|
||||
u64 value;
|
||||
std::array<u8, 32> _reserved;
|
||||
};
|
||||
|
||||
struct ErrorCodeParam {
|
||||
u32 errorCode;
|
||||
std::array<u8, 32> _reserved;
|
||||
};
|
||||
|
||||
struct ProgressBarParam {
|
||||
ProgressBarType barType;
|
||||
s32 : 32;
|
||||
const char* msg;
|
||||
ProgressSystemMessageType sysMsgType;
|
||||
std::array<u8, 28> _reserved;
|
||||
};
|
||||
|
||||
struct OptionParam {
|
||||
OptionBack back;
|
||||
std::array<u8, 32> _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<u8, 24> _reserved;
|
||||
};
|
||||
|
||||
struct OrbisSaveDataDialogResult {
|
||||
SaveDataDialogMode mode{};
|
||||
CommonDialog::Result result{};
|
||||
ButtonId buttonId{};
|
||||
s32 : 32;
|
||||
OrbisSaveDataDirName* dirName;
|
||||
OrbisSaveDataParam* param;
|
||||
void* userData;
|
||||
std::array<u8, 32> _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<bool> enable_back{};
|
||||
|
||||
OrbisUserServiceUserId user_id{};
|
||||
std::string title_id{};
|
||||
std::vector<Item> save_list{};
|
||||
std::variant<FocusPos, std::string, std::monostate> focus_pos{std::monostate{}};
|
||||
ItemStyle style{};
|
||||
|
||||
std::optional<Item> new_item{};
|
||||
|
||||
std::variant<UserState, SystemState, ErrorCodeState, ProgressBarState, std::monostate> state{
|
||||
std::monostate{}};
|
||||
|
||||
public:
|
||||
explicit SaveDialogState(const OrbisSaveDataDialogParam& param);
|
||||
|
||||
SaveDialogState() = default;
|
||||
|
||||
[[nodiscard]] SaveDataDialogMode GetMode() const {
|
||||
return mode;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
[[nodiscard]] T& GetState() {
|
||||
return std::get<T>(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
|
|
@ -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;
|
248
src/core/libraries/save_data/save_backup.cpp
Normal file
248
src/core/libraries/save_data/save_backup.cpp
Normal file
|
@ -0,0 +1,248 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <deque>
|
||||
#include <mutex>
|
||||
#include <semaphore>
|
||||
|
||||
#include <magic_enum.hpp>
|
||||
|
||||
#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<BackupRequest> g_backup_queue;
|
||||
static std::deque<BackupRequest> 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<std::filesystem::path> 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<int>(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<BackupRequest> 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<float>(g_backup_progress) / 100.0f;
|
||||
}
|
||||
|
||||
void ClearProgress() {
|
||||
g_backup_progress = 0;
|
||||
}
|
||||
|
||||
} // namespace Libraries::SaveData::Backup
|
67
src/core/libraries/save_data/save_backup.h
Normal file
67
src/core/libraries/save_data/save_backup.h
Normal file
|
@ -0,0 +1,67 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <filesystem>
|
||||
#include <optional>
|
||||
|
||||
#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<BackupRequest> PopLastEvent();
|
||||
|
||||
void PushBackupEvent(BackupRequest&& req);
|
||||
|
||||
float GetProgress();
|
||||
|
||||
void ClearProgress();
|
||||
|
||||
} // namespace Backup
|
||||
|
||||
} // namespace Libraries::SaveData
|
228
src/core/libraries/save_data/save_instance.cpp
Normal file
228
src/core/libraries/save_data/save_instance.cpp
Normal file
|
@ -0,0 +1,228 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <iostream>
|
||||
|
||||
#include <magic_enum.hpp>
|
||||
|
||||
#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<Core::FileSys::MntPoints>::Instance();
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
// clang-format off
|
||||
static const std::unordered_map<std::string, std::string> 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
|
138
src/core/libraries/save_data/save_instance.h
Normal file
138
src/core/libraries/save_data/save_instance.h
Normal file
|
@ -0,0 +1,138 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <filesystem>
|
||||
|
||||
#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
|
287
src/core/libraries/save_data/save_memory.cpp
Normal file
287
src/core/libraries/save_data/save_memory.cpp
Normal file
|
@ -0,0 +1,287 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "save_memory.h"
|
||||
|
||||
#include <condition_variable>
|
||||
#include <filesystem>
|
||||
#include <mutex>
|
||||
#include <utility>
|
||||
#include <fmt/format.h>
|
||||
|
||||
#include <core/libraries/system/msgdialog_ui.h>
|
||||
|
||||
#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<Core::FileSys::MntPoints>::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<u8> g_save_memory;
|
||||
|
||||
static std::filesystem::path g_icon_path;
|
||||
static std::vector<u8> 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<u8>(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<u8> 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<u8>(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<u8>(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<u8>(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<u8> 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
|
49
src/core/libraries/save_data/save_memory.h
Normal file
49
src/core/libraries/save_data/save_memory.h
Normal file
|
@ -0,0 +1,49 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <span>
|
||||
#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<u8> 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
|
File diff suppressed because it is too large
Load diff
|
@ -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<OrbisSaveDataTitleIdDataSize> data;
|
||||
std::array<char, 6> _pad;
|
||||
};
|
||||
|
||||
struct OrbisSaveDataDirName {
|
||||
char data[ORBIS_SAVE_DATA_DIRNAME_DATA_MAXSIZE];
|
||||
Common::CString<OrbisSaveDataDirnameDataMaxsize> 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<OrbisSaveDataTitleMaxsize> title;
|
||||
Common::CString<OrbisSaveDataSubtitleMaxsize> subTitle;
|
||||
Common::CString<OrbisSaveDataDetailMaxsize> detail;
|
||||
u32 userParam;
|
||||
int : 32;
|
||||
time_t mtime;
|
||||
u8 reserved[32];
|
||||
std::array<u8, 32> _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();
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <thread>
|
||||
|
||||
#include <imgui.h>
|
||||
#include "common/assert.h"
|
||||
#include "imgui/imgui_std.h"
|
||||
|
@ -31,18 +33,6 @@ struct {
|
|||
};
|
||||
static_assert(std::size(user_button_texts) == static_cast<int>(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<MsgDialogState::UserState>();
|
||||
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<u32>(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;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <variant>
|
||||
|
||||
#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
|
|
@ -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
|
|
@ -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
|
|
@ -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;
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <imgui.h>
|
||||
#include <pthread.h>
|
||||
|
||||
#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<std::chrono::microseconds>(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();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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<PSF>::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<PlaygoFile>::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<Frontend::WindowSDL>(
|
||||
Config::getScreenWidth(), Config::getScreenHeight(), controller, window_title);
|
||||
|
|
|
@ -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) {}
|
||||
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) {}
|
|
@ -12,10 +12,6 @@ public:
|
|||
static void RemoveLayer(Layer* layer);
|
||||
|
||||
virtual void Draw() = 0;
|
||||
|
||||
virtual bool ShouldGrabGamepad() {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace ImGui
|
|
@ -3,12 +3,25 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include <cmath>
|
||||
#include <imgui.h>
|
||||
|
||||
#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
|
||||
|
|
45
src/imgui/imgui_texture.h
Normal file
45
src/imgui/imgui_texture.h
Normal file
|
@ -0,0 +1,45 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <filesystem>
|
||||
#include <imgui.h>
|
||||
|
||||
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<u8> 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
|
|
@ -2,16 +2,121 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <imgui.h>
|
||||
|
||||
#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<FrameInfo, FRAME_BUFFER_SIZE> 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();
|
||||
}
|
||||
|
|
|
@ -11,7 +11,6 @@ class RendererVulkan;
|
|||
namespace ImGui::Layers {
|
||||
|
||||
class VideoInfo : public Layer {
|
||||
bool m_show = false;
|
||||
::Vulkan::RendererVulkan* renderer{};
|
||||
|
||||
public:
|
||||
|
|
32
src/imgui/renderer/CMakeLists.txt
Normal file
32
src/imgui/renderer/CMakeLists.txt
Normal file
|
@ -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 $<TARGET_FILE:Dear_ImGui_FontEmbed> -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)
|
BIN
src/imgui/renderer/fonts/NotoSansJP-Regular.ttf
Normal file
BIN
src/imgui/renderer/fonts/NotoSansJP-Regular.ttf
Normal file
Binary file not shown.
|
@ -3,15 +3,20 @@
|
|||
|
||||
#include <SDL3/SDL_events.h>
|
||||
#include <imgui.h>
|
||||
|
||||
#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<ImWchar> 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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -4,6 +4,8 @@
|
|||
// Based on imgui_impl_vulkan.cpp from Dear ImGui repository
|
||||
|
||||
#include <cstdio>
|
||||
#include <mutex>
|
||||
|
||||
#include <imgui.h>
|
||||
|
||||
#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
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
243
src/imgui/renderer/texture_manager.cpp
Normal file
243
src/imgui/renderer/texture_manager.cpp
Normal file
|
@ -0,0 +1,243 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <deque>
|
||||
#include <utility>
|
||||
|
||||
#include <externals/stb_image.h>
|
||||
|
||||
#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<u8> 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<u8> 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<Job> g_job_list;
|
||||
|
||||
static std::mutex g_upload_mtx;
|
||||
static std::deque<UploadJob> 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<u8>(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<u8> 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
|
30
src/imgui/renderer/texture_manager.h
Normal file
30
src/imgui/renderer/texture_manager.h
Normal file
|
@ -0,0 +1,30 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <filesystem>
|
||||
#include <vector>
|
||||
|
||||
#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<u8> data, Inner* core);
|
||||
|
||||
void DecodePngFile(std::filesystem::path path, Inner* core);
|
||||
|
||||
void Submit();
|
||||
|
||||
}; // namespace ImGui::Core::TextureManager
|
|
@ -10,6 +10,11 @@ int main(int argc, char* argv[]) {
|
|||
fmt::print("Usage: {} <elf or eboot.bin path>\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];
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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());
|
||||
});
|
||||
}
|
||||
|
|
|
@ -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")) {
|
||||
|
|
|
@ -33,7 +33,6 @@ private:
|
|||
PKGHeader pkgheader;
|
||||
PKGEntry entry;
|
||||
PSFHeader header;
|
||||
PSFEntry psfentry;
|
||||
char pkgTitleID[9];
|
||||
std::vector<u8> pkg;
|
||||
u64 pkgSize = 0;
|
||||
|
|
|
@ -4,6 +4,8 @@
|
|||
#include <QCompleter>
|
||||
#include <QDirIterator>
|
||||
|
||||
#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<const QString> 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"));
|
||||
|
|
|
@ -21,7 +21,7 @@
|
|||
<message>
|
||||
<location filename="../about_dialog.ui" line="99"/>
|
||||
<source>This software should not be used to play games you have not legally obtained.</source>
|
||||
<translation>Este software no debe utilizarse para jugar juegos que no hayas obtenido legalmente.</translation>
|
||||
<translation>Este software no debe utilizarse para jugar juegos que hayas obtenido ilegalmente.</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
|
@ -118,7 +118,7 @@
|
|||
<message>
|
||||
<location filename="../gui_context_menus.h" line="61"/>
|
||||
<source>Copy Serial</source>
|
||||
<translation>Copiar serial</translation>
|
||||
<translation>Copiar número de serie</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui_context_menus.h" line="62"/>
|
||||
|
@ -296,7 +296,7 @@
|
|||
<message>
|
||||
<location filename="../main_window_ui.h" line="355"/>
|
||||
<source>Settings</source>
|
||||
<translation>Configuraciones</translation>
|
||||
<translation>Configuración</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../main_window_ui.h" line="356"/>
|
||||
|
@ -341,7 +341,7 @@
|
|||
<message>
|
||||
<location filename="../main_window_ui.h" line="364"/>
|
||||
<source>toolBar</source>
|
||||
<translation>barra de herramientas</translation>
|
||||
<translation>Barra de herramientas</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
|
@ -365,7 +365,7 @@
|
|||
<message>
|
||||
<location filename="../settings_dialog.ui" line="29"/>
|
||||
<source>Settings</source>
|
||||
<translation>Configuraciones</translation>
|
||||
<translation>Configuración</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settings_dialog.ui" line="67"/>
|
||||
|
@ -538,7 +538,7 @@
|
|||
<message>
|
||||
<location filename="../main_window.cpp" line="392"/>
|
||||
<source>All Patches available for all games have been downloaded.</source>
|
||||
<translation>Todos los parches disponibles para todos los juegos han sido descargados.</translation>
|
||||
<translation>Todos los parches disponibles han sido descargados para todos los juegos.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../main_window.cpp" line="549"/>
|
||||
|
@ -648,7 +648,7 @@
|
|||
<message>
|
||||
<location filename="../main_window.cpp" line="725"/>
|
||||
<source>File doesn't appear to be a valid PKG file</source>
|
||||
<translation>El archivo no parece ser un archivo PKG válido</translation>
|
||||
<translation>El archivo parece no ser un archivo PKG válido</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
|
@ -671,7 +671,7 @@
|
|||
<message>
|
||||
<location filename="../cheats_patches.cpp" line="79"/>
|
||||
<source>Serial: </source>
|
||||
<translation>Serie: </translation>
|
||||
<translation>Número de serie: </translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../cheats_patches.cpp" line="83"/>
|
||||
|
@ -711,7 +711,7 @@
|
|||
<message>
|
||||
<location filename="../cheats_patches.cpp" line="170"/>
|
||||
<source>You can delete the cheats you don't want after downloading them.</source>
|
||||
<translation>Puedes eliminar los trucos que no quieras después de descargarlos.</translation>
|
||||
<translation>Puedes eliminar los trucos que no quieras una vez descargados.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../cheats_patches.cpp" line="178"/>
|
||||
|
@ -741,7 +741,7 @@
|
|||
<message>
|
||||
<location filename="../cheats_patches.cpp" line="257"/>
|
||||
<source>Patches</source>
|
||||
<translation>Parche</translation>
|
||||
<translation>Parches</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../cheats_patches.cpp" line="278"/>
|
||||
|
@ -761,7 +761,7 @@
|
|||
<message>
|
||||
<location filename="../cheats_patches.cpp" line="316"/>
|
||||
<source>No patch file found for the current serial.</source>
|
||||
<translation>No se encontró ningún archivo de parche para la serie actual.</translation>
|
||||
<translation>No se encontró ningún archivo de parche para el número de serie actual.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../cheats_patches.cpp" line="323"/>
|
||||
|
@ -937,7 +937,7 @@
|
|||
<message>
|
||||
<location filename="../game_list_frame.cpp" line="34"/>
|
||||
<source>Icon</source>
|
||||
<translation>Ícono</translation>
|
||||
<translation>Icono</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../game_list_frame.cpp" line="34"/>
|
||||
|
@ -947,7 +947,7 @@
|
|||
<message>
|
||||
<location filename="../game_list_frame.cpp" line="34"/>
|
||||
<source>Serial</source>
|
||||
<translation>Serie</translation>
|
||||
<translation>Numero de serie</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../game_list_frame.cpp" line="34"/>
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -265,7 +265,7 @@ void PatchPhiNodes(const IR::Program& program, EmitContext& ctx) {
|
|||
} // Anonymous namespace
|
||||
|
||||
std::vector<u32> 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);
|
||||
|
|
|
@ -4,12 +4,13 @@
|
|||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
#include "shader_recompiler/backend/bindings.h"
|
||||
#include "shader_recompiler/ir/program.h"
|
||||
#include "shader_recompiler/profile.h"
|
||||
|
||||
namespace Shader::Backend::SPIRV {
|
||||
|
||||
[[nodiscard]] std::vector<u32> EmitSPIRV(const Profile& profile, const RuntimeInfo& runtime_info,
|
||||
const IR::Program& program, u32& binding);
|
||||
const IR::Program& program, Bindings& binding);
|
||||
|
||||
} // namespace Shader::Backend::SPIRV
|
||||
|
|
|
@ -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<size_t>(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) {
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
#include <array>
|
||||
#include <sirit/sirit.h>
|
||||
|
||||
#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<BufferDefinition, 16> buffers;
|
||||
boost::container::small_vector<TextureBufferDefinition, 8> texture_buffers;
|
||||
boost::container::small_vector<TextureDefinition, 8> images;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -1032,6 +1032,7 @@ void GcnDecodeContext::decodeInstructionMIMG(uint64_t hexInstruction) {
|
|||
|
||||
m_instruction.control.mimg = *reinterpret_cast<InstControlMIMG*>(&hexInstruction);
|
||||
m_instruction.control.mimg.mod = getMimgModifier(m_instruction.opcode);
|
||||
ASSERT(m_instruction.control.mimg.r128 == 0);
|
||||
}
|
||||
|
||||
void GcnDecodeContext::decodeInstructionDS(uint64_t hexInstruction) {
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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<IR::F64>(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);
|
||||
|
|
|
@ -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 <typename T = IR::U32>
|
||||
|
@ -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);
|
||||
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -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
|
||||
|
|
|
@ -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<IR::U32>(addr_reg++) : IR::U32{};
|
||||
const IR::F32 bias =
|
||||
flags.test(MimgModifier::LodBias) ? ir.GetVectorReg<IR::F32>(addr_reg++) : IR::F32{};
|
||||
const IR::F32 dref =
|
||||
flags.test(MimgModifier::Pcf) ? ir.GetVectorReg<IR::F32>(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<IR::F32>(addr_reg - 4), ir.GetVectorReg<IR::F32>(addr_reg - 3),
|
||||
ir.GetVectorReg<IR::F32>(addr_reg - 2), ir.GetVectorReg<IR::F32>(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<IR::F32>(addr_reg), ir.GetVectorReg<IR::F32>(addr_reg + 1),
|
||||
ir.GetVectorReg<IR::F32>(addr_reg + 2), ir.GetVectorReg<IR::F32>(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<IR::F32>(addr_reg++) : IR::F32{};
|
||||
const IR::F32 dref =
|
||||
flags.test(MimgModifier::Pcf) ? ir.GetVectorReg<IR::F32>(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<IR::F32>(addr_reg), ir.GetVectorReg<IR::F32>(addr_reg + 1),
|
||||
ir.GetVectorReg<IR::F32>(addr_reg + 2), ir.GetVectorReg<IR::F32>(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<IR::F32, 4> 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<IR::F32>(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<IR::F32>(addr_reg), ir.GetVectorReg<IR::F32>(addr_reg + 1),
|
||||
ir.GetVectorReg<IR::F32>(addr_reg + 2), ir.GetVectorReg<IR::F32>(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<IR::F32, 4> 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<IR::F32>(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<IR::U32>(addr_reg++) : IR::U32{};
|
||||
const IR::F32 bias =
|
||||
flags.test(MimgModifier::LodBias) ? ir.GetVectorReg<IR::F32>(addr_reg++) : IR::F32{};
|
||||
const IR::F32 dref =
|
||||
flags.test(MimgModifier::Pcf) ? ir.GetVectorReg<IR::F32>(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<IR::F32>(addr_reg - 4), ir.GetVectorReg<IR::F32>(addr_reg - 3),
|
||||
ir.GetVectorReg<IR::F32>(addr_reg - 2), ir.GetVectorReg<IR::F32>(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<IR::F32>(addr_reg), ir.GetVectorReg<IR::F32>(addr_reg + 1),
|
||||
ir.GetVectorReg<IR::F32>(addr_reg + 2), ir.GetVectorReg<IR::F32>(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<IR::F32>(addr_reg++) : IR::F32{};
|
||||
const IR::F32 dref =
|
||||
flags.test(MimgModifier::Pcf) ? ir.GetVectorReg<IR::F32>(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<IR::F32>(addr_reg), ir.GetVectorReg<IR::F32>(addr_reg + 1),
|
||||
ir.GetVectorReg<IR::F32>(addr_reg + 2), ir.GetVectorReg<IR::F32>(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<IR::F32>(addr_reg), ir.GetVectorReg<IR::F32>(addr_reg + 1),
|
||||
ir.GetVectorReg<IR::F32>(addr_reg + 2), ir.GetVectorReg<IR::F32>(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
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue