mirror of
https://github.com/shadps4-emu/shadPS4.git
synced 2025-12-16 03:58:56 +00:00
Merge remote-tracking branch 'upstream/main' into gc2
This commit is contained in:
commit
ec383342f6
3
.gitmodules
vendored
3
.gitmodules
vendored
@ -117,3 +117,6 @@
|
||||
path = externals/sdl3_mixer
|
||||
url = https://github.com/libsdl-org/SDL_mixer
|
||||
shallow = true
|
||||
[submodule "externals/miniz"]
|
||||
path = externals/miniz
|
||||
url = https://github.com/richgel999/miniz
|
||||
|
||||
@ -228,8 +228,10 @@ find_package(half 1.12.0 MODULE)
|
||||
find_package(magic_enum 0.9.7 CONFIG)
|
||||
find_package(PNG 1.6 MODULE)
|
||||
find_package(RenderDoc 1.6.0 MODULE)
|
||||
find_package(SDL3 3.1.2 CONFIG)
|
||||
find_package(SDL3_mixer 2.8.1 CONFIG)
|
||||
if (SDL3_mixer_FOUND)
|
||||
find_package(SDL3 3.1.2 CONFIG)
|
||||
endif()
|
||||
find_package(stb MODULE)
|
||||
find_package(toml11 4.2.0 CONFIG)
|
||||
find_package(tsl-robin-map 1.3.0 CONFIG)
|
||||
@ -510,7 +512,7 @@ set(PAD_LIB src/core/libraries/pad/pad.cpp
|
||||
src/core/libraries/pad/pad_errors.h
|
||||
)
|
||||
|
||||
set(SYSTEM_GESTURE_LIB
|
||||
set(SYSTEM_GESTURE_LIB
|
||||
src/core/libraries/system_gesture/system_gesture.cpp
|
||||
src/core/libraries/system_gesture/system_gesture.h
|
||||
)
|
||||
@ -554,6 +556,8 @@ set(FIBER_LIB src/core/libraries/fiber/fiber_context.s
|
||||
src/core/libraries/fiber/fiber_error.h
|
||||
)
|
||||
|
||||
set_source_files_properties(src/core/libraries/fiber/fiber_context.s PROPERTIES COMPILE_OPTIONS -Wno-unused-command-line-argument)
|
||||
|
||||
set(VDEC_LIB src/core/libraries/videodec/videodec2_impl.cpp
|
||||
src/core/libraries/videodec/videodec2_impl.h
|
||||
src/core/libraries/videodec/videodec2.cpp
|
||||
@ -569,6 +573,8 @@ set(VDEC_LIB src/core/libraries/videodec/videodec2_impl.cpp
|
||||
set(NP_LIBS src/core/libraries/np/np_error.h
|
||||
src/core/libraries/np/np_common.cpp
|
||||
src/core/libraries/np/np_common.h
|
||||
src/core/libraries/np/np_commerce.cpp
|
||||
src/core/libraries/np/np_commerce.h
|
||||
src/core/libraries/np/np_manager.cpp
|
||||
src/core/libraries/np/np_manager.h
|
||||
src/core/libraries/np/np_score.cpp
|
||||
@ -689,7 +695,6 @@ set(COMMON src/common/logging/backend.cpp
|
||||
src/common/lru_cache.h
|
||||
src/common/error.cpp
|
||||
src/common/error.h
|
||||
src/common/scope_exit.h
|
||||
src/common/fixed_value.h
|
||||
src/common/func_traits.h
|
||||
src/common/native_clock.cpp
|
||||
@ -703,6 +708,8 @@ set(COMMON src/common/logging/backend.cpp
|
||||
src/common/rdtsc.h
|
||||
src/common/recursive_lock.cpp
|
||||
src/common/recursive_lock.h
|
||||
src/common/scope_exit.h
|
||||
src/common/serdes.h
|
||||
src/common/sha1.h
|
||||
src/common/shared_first_mutex.h
|
||||
src/common/signal_context.h
|
||||
@ -982,6 +989,8 @@ set(VIDEO_CORE src/video_core/amdgpu/cb_db_extent.h
|
||||
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_pipeline_serialization.cpp
|
||||
src/video_core/renderer_vulkan/vk_pipeline_serialization.h
|
||||
src/video_core/renderer_vulkan/vk_platform.cpp
|
||||
src/video_core/renderer_vulkan/vk_platform.h
|
||||
src/video_core/renderer_vulkan/vk_presenter.cpp
|
||||
@ -1019,6 +1028,8 @@ set(VIDEO_CORE src/video_core/amdgpu/cb_db_extent.h
|
||||
src/video_core/texture_cache/tile_manager.cpp
|
||||
src/video_core/texture_cache/tile_manager.h
|
||||
src/video_core/texture_cache/types.h
|
||||
src/video_core/cache_storage.cpp
|
||||
src/video_core/cache_storage.h
|
||||
src/video_core/page_manager.cpp
|
||||
src/video_core/page_manager.h
|
||||
src/video_core/multi_level_page_table.h
|
||||
@ -1073,7 +1084,8 @@ add_executable(shadps4
|
||||
create_target_directory_groups(shadps4)
|
||||
|
||||
target_link_libraries(shadps4 PRIVATE magic_enum::magic_enum fmt::fmt toml11::toml11 tsl::robin_map xbyak::xbyak Tracy::TracyClient RenderDoc::API FFmpeg::ffmpeg Dear_ImGui gcn half::half ZLIB::ZLIB PNG::PNG)
|
||||
target_link_libraries(shadps4 PRIVATE Boost::headers GPUOpen::VulkanMemoryAllocator LibAtrac9 sirit Vulkan::Headers xxHash::xxhash Zydis::Zydis glslang::glslang SDL3::SDL3 SDL3_mixer::SDL3_mixer pugixml::pugixml stb::headers libusb::usb lfreist-hwinfo::hwinfo nlohmann_json::nlohmann_json)
|
||||
target_link_libraries(shadps4 PRIVATE Boost::headers GPUOpen::VulkanMemoryAllocator LibAtrac9 sirit Vulkan::Headers xxHash::xxhash Zydis::Zydis glslang::glslang SDL3::SDL3 SDL3_mixer::SDL3_mixer pugixml::pugixml)
|
||||
target_link_libraries(shadps4 PRIVATE stb::headers libusb::usb lfreist-hwinfo::hwinfo nlohmann_json::nlohmann_json miniz)
|
||||
|
||||
target_compile_definitions(shadps4 PRIVATE IMGUI_USER_CONFIG="imgui/imgui_config.h")
|
||||
target_compile_definitions(Dear_ImGui PRIVATE IMGUI_USER_CONFIG="${PROJECT_SOURCE_DIR}/src/imgui/imgui_config.h")
|
||||
@ -1122,22 +1134,22 @@ if (APPLE)
|
||||
endif()
|
||||
|
||||
if (WIN32)
|
||||
target_link_libraries(shadps4 PRIVATE mincore wepoll)
|
||||
target_link_libraries(shadps4 PRIVATE mincore wepoll wbemuuid)
|
||||
|
||||
if (MSVC)
|
||||
# MSVC likes putting opinions on what people can use, disable:
|
||||
add_definitions(-D_CRT_SECURE_NO_WARNINGS -D_CRT_NONSTDC_NO_DEPRECATE -D_SCL_SECURE_NO_WARNINGS)
|
||||
add_compile_definitions(_CRT_SECURE_NO_WARNINGS _CRT_NONSTDC_NO_DEPRECATE _SCL_SECURE_NO_WARNINGS)
|
||||
endif()
|
||||
|
||||
add_definitions(-DNOMINMAX -DWIN32_LEAN_AND_MEAN)
|
||||
add_compile_definitions(NOMINMAX WIN32_LEAN_AND_MEAN)
|
||||
|
||||
if (MSVC)
|
||||
# Needed for conflicts with time.h of windows.h
|
||||
add_definitions(-D_TIMESPEC_DEFINED)
|
||||
add_compile_definitions(_TIMESPEC_DEFINED)
|
||||
endif()
|
||||
|
||||
# Target Windows 10 RS5
|
||||
add_definitions(-DNTDDI_VERSION=0x0A000006 -D_WIN32_WINNT=0x0A00 -DWINVER=0x0A00)
|
||||
add_compile_definitions(NTDDI_VERSION=0x0A000006 _WIN32_WINNT=0x0A00 WINVER=0x0A00)
|
||||
|
||||
if (MSVC)
|
||||
target_link_libraries(shadps4 PRIVATE clang_rt.builtins-x86_64.lib)
|
||||
@ -1169,7 +1181,7 @@ if (WIN32)
|
||||
target_sources(shadps4 PRIVATE src/shadps4.rc)
|
||||
endif()
|
||||
|
||||
add_definitions(-DBOOST_ASIO_STANDALONE)
|
||||
add_compile_definitions(BOOST_ASIO_STANDALONE)
|
||||
|
||||
target_include_directories(shadps4 PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
|
||||
|
||||
8
dist/net.shadps4.shadPS4.metainfo.xml
vendored
8
dist/net.shadps4.shadPS4.metainfo.xml
vendored
@ -18,19 +18,19 @@
|
||||
<screenshots>
|
||||
<screenshot type="default">
|
||||
<image type="source" translate="no" >https://cdn.jsdelivr.net/gh/shadps4-emu/shadps4@main/documents/Screenshots/1.png</image>
|
||||
<caption>Bloodborne</caption>
|
||||
<caption>Bloodborne by From Software</caption>
|
||||
</screenshot>
|
||||
<screenshot>
|
||||
<image type="source" translate="no">https://cdn.jsdelivr.net/gh/shadps4-emu/shadps4@main/documents/Screenshots/2.png</image>
|
||||
<caption>Hatsune Miku: Project DIVA Future Tone</caption>
|
||||
<caption>Hatsune Miku Project DIVA Future Tone by SEGA</caption>
|
||||
</screenshot>
|
||||
<screenshot>
|
||||
<image type="source" translate="no">https://cdn.jsdelivr.net/gh/shadps4-emu/shadps4@main/documents/Screenshots/3.png</image>
|
||||
<caption>Yakuza 0</caption>
|
||||
<caption>Yakuza 0 by SEGA</caption>
|
||||
</screenshot>
|
||||
<screenshot>
|
||||
<image type="source" translate="no">https://cdn.jsdelivr.net/gh/shadps4-emu/shadps4@main/documents/Screenshots/4.png</image>
|
||||
<caption>Persona 4 Golden</caption>
|
||||
<caption>DRIVECLUB™ by Evolution Studios</caption>
|
||||
</screenshot>
|
||||
</screenshots>
|
||||
<categories>
|
||||
|
||||
5
externals/CMakeLists.txt
vendored
5
externals/CMakeLists.txt
vendored
@ -152,7 +152,7 @@ endif()
|
||||
# sirit
|
||||
add_subdirectory(sirit)
|
||||
if (WIN32)
|
||||
target_compile_options(sirit PUBLIC "-Wno-error=unused-command-line-argument")
|
||||
target_compile_options(sirit PRIVATE "-Wno-error=unused-command-line-argument")
|
||||
endif()
|
||||
|
||||
# half
|
||||
@ -261,3 +261,6 @@ endif()
|
||||
#nlohmann json
|
||||
set(JSON_BuildTests OFF CACHE INTERNAL "")
|
||||
add_subdirectory(json)
|
||||
|
||||
# miniz
|
||||
add_subdirectory(miniz)
|
||||
|
||||
1
externals/miniz
vendored
Submodule
1
externals/miniz
vendored
Submodule
@ -0,0 +1 @@
|
||||
Subproject commit 174573d60290f447c13a2b1b3405de2b96e27d6c
|
||||
@ -177,7 +177,7 @@ static ConfigEntry<bool> isFullscreen(false);
|
||||
static ConfigEntry<string> fullscreenMode("Windowed");
|
||||
static ConfigEntry<string> presentMode("Mailbox");
|
||||
static ConfigEntry<bool> isHDRAllowed(false);
|
||||
static ConfigEntry<bool> fsrEnabled(true);
|
||||
static ConfigEntry<bool> fsrEnabled(false);
|
||||
static ConfigEntry<bool> rcasEnabled(true);
|
||||
static ConfigEntry<int> rcasAttenuation(250);
|
||||
|
||||
@ -191,6 +191,8 @@ static ConfigEntry<bool> vkCrashDiagnostic(false);
|
||||
static ConfigEntry<bool> vkHostMarkers(false);
|
||||
static ConfigEntry<bool> vkGuestMarkers(false);
|
||||
static ConfigEntry<bool> rdocEnable(false);
|
||||
static ConfigEntry<bool> pipelineCacheEnable(false);
|
||||
static ConfigEntry<bool> pipelineCacheArchive(false);
|
||||
|
||||
// Debug
|
||||
static ConfigEntry<bool> isDebugDump(false);
|
||||
@ -452,6 +454,14 @@ bool isRdocEnabled() {
|
||||
return rdocEnable.get();
|
||||
}
|
||||
|
||||
bool isPipelineCacheEnabled() {
|
||||
return pipelineCacheEnable.get();
|
||||
}
|
||||
|
||||
bool isPipelineCacheArchived() {
|
||||
return pipelineCacheArchive.get();
|
||||
}
|
||||
|
||||
bool fpsColor() {
|
||||
return isFpsColor.get();
|
||||
}
|
||||
@ -603,6 +613,14 @@ void setRdocEnabled(bool enable, bool is_game_specific) {
|
||||
rdocEnable.set(enable, is_game_specific);
|
||||
}
|
||||
|
||||
void setPipelineCacheEnabled(bool enable, bool is_game_specific) {
|
||||
pipelineCacheEnable.set(enable, is_game_specific);
|
||||
}
|
||||
|
||||
void setPipelineCacheArchived(bool enable, bool is_game_specific) {
|
||||
pipelineCacheArchive.set(enable, is_game_specific);
|
||||
}
|
||||
|
||||
void setVblankFreq(u32 value, bool is_game_specific) {
|
||||
vblankFrequency.set(value, is_game_specific);
|
||||
}
|
||||
@ -939,6 +957,8 @@ void load(const std::filesystem::path& path, bool is_game_specific) {
|
||||
vkHostMarkers.setFromToml(vk, "hostMarkers", is_game_specific);
|
||||
vkGuestMarkers.setFromToml(vk, "guestMarkers", is_game_specific);
|
||||
rdocEnable.setFromToml(vk, "rdocEnable", is_game_specific);
|
||||
pipelineCacheEnable.setFromToml(vk, "pipelineCacheEnable", is_game_specific);
|
||||
pipelineCacheArchive.setFromToml(vk, "pipelineCacheArchive", is_game_specific);
|
||||
}
|
||||
|
||||
string current_version = {};
|
||||
@ -1107,6 +1127,8 @@ void save(const std::filesystem::path& path, bool is_game_specific) {
|
||||
vkHostMarkers.setTomlValue(data, "Vulkan", "hostMarkers", is_game_specific);
|
||||
vkGuestMarkers.setTomlValue(data, "Vulkan", "guestMarkers", is_game_specific);
|
||||
rdocEnable.setTomlValue(data, "Vulkan", "rdocEnable", is_game_specific);
|
||||
pipelineCacheEnable.setTomlValue(data, "Vulkan", "pipelineCacheEnable", is_game_specific);
|
||||
pipelineCacheArchive.setTomlValue(data, "Vulkan", "pipelineCacheArchive", is_game_specific);
|
||||
|
||||
isDebugDump.setTomlValue(data, "Debug", "DebugDump", is_game_specific);
|
||||
isShaderDebug.setTomlValue(data, "Debug", "CollectShader", is_game_specific);
|
||||
@ -1237,6 +1259,8 @@ void setDefaultValues(bool is_game_specific) {
|
||||
vkHostMarkers.set(false, is_game_specific);
|
||||
vkGuestMarkers.set(false, is_game_specific);
|
||||
rdocEnable.set(false, is_game_specific);
|
||||
pipelineCacheEnable.set(false, is_game_specific);
|
||||
pipelineCacheArchive.set(false, is_game_specific);
|
||||
|
||||
// GS - Debug
|
||||
isDebugDump.set(false, is_game_specific);
|
||||
@ -1286,6 +1310,7 @@ hotkey_pause = f9
|
||||
hotkey_reload_inputs = f8
|
||||
hotkey_toggle_mouse_to_joystick = f7
|
||||
hotkey_toggle_mouse_to_gyro = f6
|
||||
hotkey_toggle_mouse_to_touchpad = delete
|
||||
hotkey_quit = lctrl, lshift, end
|
||||
)";
|
||||
}
|
||||
|
||||
@ -94,7 +94,11 @@ void setVkGuestMarkersEnabled(bool enable, bool is_game_specific = false);
|
||||
bool getEnableDiscordRPC();
|
||||
void setEnableDiscordRPC(bool enable);
|
||||
bool isRdocEnabled();
|
||||
bool isPipelineCacheEnabled();
|
||||
bool isPipelineCacheArchived();
|
||||
void setRdocEnabled(bool enable, bool is_game_specific = false);
|
||||
void setPipelineCacheEnabled(bool enable, bool is_game_specific = false);
|
||||
void setPipelineCacheArchived(bool enable, bool is_game_specific = false);
|
||||
std::string getLogType();
|
||||
void setLogType(const std::string& type, bool is_game_specific = false);
|
||||
std::string getLogFilter();
|
||||
|
||||
@ -104,6 +104,7 @@ bool ParseFilterRule(Filter& instance, Iterator begin, Iterator end) {
|
||||
SUB(Lib, Move) \
|
||||
SUB(Lib, NpAuth) \
|
||||
SUB(Lib, NpCommon) \
|
||||
SUB(Lib, NpCommerce) \
|
||||
SUB(Lib, NpManager) \
|
||||
SUB(Lib, NpScore) \
|
||||
SUB(Lib, NpTrophy) \
|
||||
|
||||
@ -70,6 +70,7 @@ enum class Class : u8 {
|
||||
Lib_Http2, ///< The LibSceHttp2 implementation.
|
||||
Lib_SysModule, ///< The LibSceSysModule implementation
|
||||
Lib_NpCommon, ///< The LibSceNpCommon implementation
|
||||
Lib_NpCommerce, ///< The LibSceNpCommerce implementation
|
||||
Lib_NpAuth, ///< The LibSceNpAuth implementation
|
||||
Lib_NpManager, ///< The LibSceNpManager implementation
|
||||
Lib_NpScore, ///< The LibSceNpScore implementation
|
||||
|
||||
@ -127,6 +127,7 @@ static auto UserPaths = [] {
|
||||
create_path(PathType::MetaDataDir, user_dir / METADATA_DIR);
|
||||
create_path(PathType::CustomTrophy, user_dir / CUSTOM_TROPHY);
|
||||
create_path(PathType::CustomConfigs, user_dir / CUSTOM_CONFIGS);
|
||||
create_path(PathType::CacheDir, user_dir / CACHE_DIR);
|
||||
|
||||
std::ofstream notice_file(user_dir / CUSTOM_TROPHY / "Notice.txt");
|
||||
if (notice_file.is_open()) {
|
||||
|
||||
@ -24,6 +24,7 @@ enum class PathType {
|
||||
MetaDataDir, // Where game metadata (e.g. trophies and menu backgrounds) is stored.
|
||||
CustomTrophy, // Where custom files for trophies are stored.
|
||||
CustomConfigs, // Where custom files for different games are stored.
|
||||
CacheDir, // Where pipeline and shader cache is stored.
|
||||
};
|
||||
|
||||
constexpr auto PORTABLE_DIR = "user";
|
||||
@ -42,6 +43,7 @@ constexpr auto PATCHES_DIR = "patches";
|
||||
constexpr auto METADATA_DIR = "game_data";
|
||||
constexpr auto CUSTOM_TROPHY = "custom_trophy";
|
||||
constexpr auto CUSTOM_CONFIGS = "custom_configs";
|
||||
constexpr auto CACHE_DIR = "cache";
|
||||
|
||||
// Filenames
|
||||
constexpr auto LOG_FILE = "shad_log.txt";
|
||||
|
||||
140
src/common/serdes.h
Normal file
140
src/common/serdes.h
Normal file
@ -0,0 +1,140 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/types.h"
|
||||
|
||||
#include <cstddef>
|
||||
|
||||
namespace Serialization {
|
||||
|
||||
template <typename T>
|
||||
concept Container = requires(T t) {
|
||||
typename T::iterator;
|
||||
{ t.begin() } -> std::same_as<typename T::iterator>;
|
||||
{ t.end() } -> std::same_as<typename T::iterator>;
|
||||
{ t.size() } -> std::convertible_to<std::size_t>;
|
||||
};
|
||||
|
||||
struct Archive {
|
||||
void Alloc(size_t size) {
|
||||
container.resize(size);
|
||||
}
|
||||
|
||||
void Grow(size_t size) {
|
||||
container.resize(container.size() + size);
|
||||
}
|
||||
|
||||
void Merge(const Archive& ar) {
|
||||
container.insert(container.end(), ar.container.cbegin(), ar.container.cend());
|
||||
offset = container.size();
|
||||
}
|
||||
|
||||
[[nodiscard]] size_t SizeBytes() const {
|
||||
return container.size();
|
||||
}
|
||||
|
||||
u8* CurrPtr() {
|
||||
return container.data() + offset;
|
||||
}
|
||||
|
||||
void Advance(size_t size) {
|
||||
ASSERT(offset + size <= container.size());
|
||||
offset += size;
|
||||
}
|
||||
|
||||
std::vector<u8>&& TakeOff() {
|
||||
offset = 0;
|
||||
return std::move(container);
|
||||
}
|
||||
|
||||
[[nodiscard]] bool IsEoS() const {
|
||||
return offset >= container.size();
|
||||
}
|
||||
|
||||
Archive() = default;
|
||||
explicit Archive(std::vector<u8>&& v) : container{v} {}
|
||||
|
||||
private:
|
||||
u32 offset{};
|
||||
std::vector<u8> container{};
|
||||
|
||||
friend struct Writer;
|
||||
friend struct Reader;
|
||||
};
|
||||
|
||||
struct Writer {
|
||||
template <typename T>
|
||||
void Write(const T* ptr, size_t size) {
|
||||
if (ar.offset + size >= ar.container.size()) {
|
||||
ar.Grow(size);
|
||||
}
|
||||
std::memcpy(ar.CurrPtr(), reinterpret_cast<const void*>(ptr), size);
|
||||
ar.Advance(size);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
requires(!Container<T>)
|
||||
void Write(const T& value) {
|
||||
const auto size = sizeof(value);
|
||||
Write(&value, size);
|
||||
}
|
||||
|
||||
void Write(const auto& v) {
|
||||
Write(v.size());
|
||||
for (const auto& elem : v) {
|
||||
Write(elem);
|
||||
}
|
||||
}
|
||||
|
||||
void Write(const std::string& s) {
|
||||
Write(s.size());
|
||||
Write(s.c_str(), s.size());
|
||||
}
|
||||
|
||||
Writer() = delete;
|
||||
explicit Writer(Archive& ar_) : ar{ar_} {}
|
||||
|
||||
Archive& ar;
|
||||
};
|
||||
|
||||
struct Reader {
|
||||
template <typename T>
|
||||
void Read(T* ptr, size_t size) {
|
||||
ASSERT(ar.offset + size <= ar.container.size());
|
||||
std::memcpy(reinterpret_cast<void*>(ptr), ar.CurrPtr(), size);
|
||||
ar.Advance(size);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
requires(!Container<T>)
|
||||
void Read(T& value) {
|
||||
const auto size = sizeof(value);
|
||||
Read(&value, size);
|
||||
}
|
||||
|
||||
void Read(auto& v) {
|
||||
size_t num_elements{};
|
||||
Read(num_elements);
|
||||
for (int i = 0; i < num_elements; ++i) {
|
||||
v.emplace_back();
|
||||
Read(v.back());
|
||||
}
|
||||
}
|
||||
|
||||
void Read(std::string& s) {
|
||||
size_t length{};
|
||||
Read(length);
|
||||
s.resize(length);
|
||||
Read(s.data(), length);
|
||||
}
|
||||
|
||||
Reader() = delete;
|
||||
explicit Reader(Archive& ar_) : ar{ar_} {}
|
||||
|
||||
Archive& ar;
|
||||
};
|
||||
|
||||
} // namespace Serialization
|
||||
@ -36,6 +36,7 @@ bool PSF::Open(const std::filesystem::path& filepath) {
|
||||
}
|
||||
|
||||
const u64 psfSize = file.GetSize();
|
||||
ASSERT_MSG(psfSize != 0, "SFO file at {} is empty!", filepath.string());
|
||||
std::vector<u8> psf(psfSize);
|
||||
file.Seek(0);
|
||||
file.Read(psf);
|
||||
|
||||
@ -36,6 +36,18 @@ public:
|
||||
return ORBIS_KERNEL_ERROR_EBADF;
|
||||
}
|
||||
|
||||
virtual s64 write(const void* buf, u64 nbytes) {
|
||||
return ORBIS_KERNEL_ERROR_EBADF;
|
||||
}
|
||||
|
||||
virtual s64 writev(const Libraries::Kernel::OrbisKernelIovec* iov, s32 iovcnt) {
|
||||
return ORBIS_KERNEL_ERROR_EBADF;
|
||||
}
|
||||
|
||||
virtual s64 pwritev(const Libraries::Kernel::OrbisKernelIovec* iov, s32 iovcnt, s64 offset) {
|
||||
return ORBIS_KERNEL_ERROR_EBADF;
|
||||
}
|
||||
|
||||
virtual s64 lseek(s64 offset, s32 whence) {
|
||||
return ORBIS_KERNEL_ERROR_EBADF;
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "fiber.h"
|
||||
|
||||
@ -311,6 +311,9 @@ s64 PS4_SYSV_ABI write(s32 fd, const void* buf, u64 nbytes) {
|
||||
} else if (file->type == Core::FileSys::FileType::Socket) {
|
||||
// Socket functions handle errnos internally.
|
||||
return file->socket->SendPacket(buf, nbytes, 0, nullptr, 0);
|
||||
} else if (file->type == Core::FileSys::FileType::Directory) {
|
||||
*__Error() = POSIX_EBADF;
|
||||
return -1;
|
||||
}
|
||||
|
||||
return file->f.WriteRaw<u8>(buf, nbytes);
|
||||
@ -405,7 +408,11 @@ s64 PS4_SYSV_ABI writev(s32 fd, const OrbisKernelIovec* iov, s32 iovcnt) {
|
||||
return -1;
|
||||
}
|
||||
return result;
|
||||
} else if (file->type == Core::FileSys::FileType::Directory) {
|
||||
*__Error() = POSIX_EBADF;
|
||||
return -1;
|
||||
}
|
||||
|
||||
s64 total_written = 0;
|
||||
for (s32 i = 0; i < iovcnt; i++) {
|
||||
total_written += file->f.WriteRaw<u8>(iov[i].iov_base, iov[i].iov_len);
|
||||
@ -1047,7 +1054,11 @@ s64 PS4_SYSV_ABI posix_pwritev(s32 fd, const OrbisKernelIovec* iov, s32 iovcnt,
|
||||
return -1;
|
||||
}
|
||||
return result;
|
||||
} else if (file->type == Core::FileSys::FileType::Directory) {
|
||||
*__Error() = POSIX_EBADF;
|
||||
return -1;
|
||||
}
|
||||
|
||||
const s64 pos = file->f.Tell();
|
||||
SCOPE_EXIT {
|
||||
file->f.Seek(pos);
|
||||
|
||||
@ -42,6 +42,16 @@ s32 PS4_SYSV_ABI sceKernelGetCompiledSdkVersion(s32* ver) {
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceKernelGetCpumode() {
|
||||
LOG_DEBUG(Lib_Kernel, "called");
|
||||
auto& attrs = Common::ElfInfo::Instance().GetPSFAttributes();
|
||||
u32 is_cpu6 = attrs.six_cpu_mode.Value();
|
||||
u32 is_cpu7 = attrs.seven_cpu_mode.Value();
|
||||
if (is_cpu6 == 1 && is_cpu7 == 1) {
|
||||
return 2;
|
||||
}
|
||||
if (is_cpu7 == 1) {
|
||||
return 5;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
@ -663,6 +663,10 @@ void RegisterThread(Core::Loader::SymbolsResolver* sym) {
|
||||
LIB_FUNCTION("Z4QosVuAsA0", "libkernel", 1, "libkernel", posix_pthread_once);
|
||||
LIB_FUNCTION("EotR8a3ASf4", "libkernel", 1, "libkernel", posix_pthread_self);
|
||||
LIB_FUNCTION("OxhIB8LB-PQ", "libkernel", 1, "libkernel", posix_pthread_create);
|
||||
LIB_FUNCTION("lZzFeSxPl08", "libkernel", 1, "libkernel", posix_pthread_setcancelstate);
|
||||
LIB_FUNCTION("CBNtXOoef-E", "libkernel", 1, "libkernel", posix_sched_get_priority_max);
|
||||
LIB_FUNCTION("m0iS6jNsXds", "libkernel", 1, "libkernel", posix_sched_get_priority_min);
|
||||
LIB_FUNCTION("Xs9hdiD7sAA", "libkernel", 1, "libkernel", posix_pthread_setschedparam);
|
||||
LIB_FUNCTION("+U1R4WtXvoc", "libkernel", 1, "libkernel", posix_pthread_detach);
|
||||
LIB_FUNCTION("7Xl257M4VNI", "libkernel", 1, "libkernel", posix_pthread_equal);
|
||||
LIB_FUNCTION("h9CcP3J0oVM", "libkernel", 1, "libkernel", posix_pthread_join);
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <ctime>
|
||||
#include <thread>
|
||||
|
||||
#include "common/assert.h"
|
||||
@ -485,25 +486,41 @@ Common::NativeClock* GetClock() {
|
||||
s32 PS4_SYSV_ABI sceKernelConvertUtcToLocaltime(time_t time, time_t* local_time,
|
||||
struct OrbisTimesec* st, u64* dst_sec) {
|
||||
LOG_TRACE(Kernel, "Called");
|
||||
#ifdef _WIN32
|
||||
TIME_ZONE_INFORMATION tz{};
|
||||
DWORD res = GetTimeZoneInformation(&tz);
|
||||
*local_time = time - tz.Bias;
|
||||
|
||||
if (st != nullptr) {
|
||||
st->t = time;
|
||||
st->west_sec = -tz.Bias * 60;
|
||||
st->dst_sec = res == TIME_ZONE_ID_DAYLIGHT ? -_dstbias : 0;
|
||||
}
|
||||
|
||||
if (dst_sec != nullptr) {
|
||||
*dst_sec = res == TIME_ZONE_ID_DAYLIGHT ? -_dstbias : 0;
|
||||
}
|
||||
#else
|
||||
#ifdef __APPLE__
|
||||
// std::chrono::current_zone() not available yet.
|
||||
const auto* time_zone = date::current_zone();
|
||||
#else
|
||||
const auto* time_zone = std::chrono::current_zone();
|
||||
#endif
|
||||
#endif // __APPLE__
|
||||
auto info = time_zone->get_info(std::chrono::system_clock::now());
|
||||
|
||||
*local_time = info.offset.count() + info.save.count() * 60 + time;
|
||||
|
||||
if (st != nullptr) {
|
||||
st->t = time;
|
||||
st->west_sec = info.offset.count() * 60;
|
||||
st->west_sec = info.offset.count();
|
||||
st->dst_sec = info.save.count() * 60;
|
||||
}
|
||||
|
||||
if (dst_sec != nullptr) {
|
||||
*dst_sec = info.save.count() * 60;
|
||||
}
|
||||
#endif // _WIN32
|
||||
|
||||
return ORBIS_OK;
|
||||
}
|
||||
@ -565,4 +582,4 @@ void RegisterTime(Core::Loader::SymbolsResolver* sym) {
|
||||
LIB_FUNCTION("-o5uEDpN+oY", "libkernel", 1, "libkernel", sceKernelConvertUtcToLocaltime);
|
||||
}
|
||||
|
||||
} // namespace Libraries::Kernel
|
||||
} // namespace Libraries::Kernel
|
||||
|
||||
@ -32,6 +32,7 @@
|
||||
#include "core/libraries/network/ssl.h"
|
||||
#include "core/libraries/network/ssl2.h"
|
||||
#include "core/libraries/np/np_auth.h"
|
||||
#include "core/libraries/np/np_commerce.h"
|
||||
#include "core/libraries/np/np_common.h"
|
||||
#include "core/libraries/np/np_manager.h"
|
||||
#include "core/libraries/np/np_party.h"
|
||||
@ -93,6 +94,7 @@ void InitHLELibs(Core::Loader::SymbolsResolver* sym) {
|
||||
Libraries::SysModule::RegisterLib(sym);
|
||||
Libraries::Posix::RegisterLib(sym);
|
||||
Libraries::AudioIn::RegisterLib(sym);
|
||||
Libraries::Np::NpCommerce::RegisterLib(sym);
|
||||
Libraries::Np::NpCommon::RegisterLib(sym);
|
||||
Libraries::Np::NpManager::RegisterLib(sym);
|
||||
Libraries::Np::NpScore::RegisterLib(sym);
|
||||
|
||||
@ -712,8 +712,61 @@ int PS4_SYSV_ABI sceHttpUriCopy() {
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceHttpUriEscape() {
|
||||
LOG_ERROR(Lib_Http, "(STUBBED) called");
|
||||
int PS4_SYSV_ABI sceHttpUriEscape(char* out, u64* require, u64 prepare, const char* in) {
|
||||
LOG_TRACE(Lib_Http, "called");
|
||||
|
||||
if (!in) {
|
||||
LOG_ERROR(Lib_Http, "Invalid input string");
|
||||
return ORBIS_HTTP_ERROR_INVALID_VALUE;
|
||||
}
|
||||
|
||||
auto IsUnreserved = [](unsigned char c) -> bool {
|
||||
return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') ||
|
||||
c == '-' || c == '_' || c == '.' || c == '~';
|
||||
};
|
||||
|
||||
u64 needed = 0;
|
||||
const char* src = in;
|
||||
while (*src) {
|
||||
unsigned char c = static_cast<unsigned char>(*src);
|
||||
if (IsUnreserved(c)) {
|
||||
needed++;
|
||||
} else {
|
||||
needed += 3; // %XX format
|
||||
}
|
||||
src++;
|
||||
}
|
||||
needed++; // null terminator
|
||||
|
||||
if (require) {
|
||||
*require = needed;
|
||||
}
|
||||
|
||||
if (!out) {
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
if (prepare < needed) {
|
||||
LOG_ERROR(Lib_Http, "Buffer too small: need {} but only {} available", needed, prepare);
|
||||
return ORBIS_HTTP_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
static const char hex_chars[] = "0123456789ABCDEF";
|
||||
src = in;
|
||||
char* dst = out;
|
||||
while (*src) {
|
||||
unsigned char c = static_cast<unsigned char>(*src);
|
||||
if (IsUnreserved(c)) {
|
||||
*dst++ = *src;
|
||||
} else {
|
||||
*dst++ = '%';
|
||||
*dst++ = hex_chars[(c >> 4) & 0x0F];
|
||||
*dst++ = hex_chars[c & 0x0F];
|
||||
}
|
||||
src++;
|
||||
}
|
||||
*dst = '\0';
|
||||
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
@ -1072,12 +1125,163 @@ int PS4_SYSV_ABI sceHttpUriParse(OrbisHttpUriElement* out, const char* srcUri, v
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceHttpUriSweepPath(char* dst, const char* src, u64 srcSize) {
|
||||
LOG_ERROR(Lib_Http, "(STUBBED) called");
|
||||
LOG_TRACE(Lib_Http, "called");
|
||||
|
||||
if (!dst || !src) {
|
||||
LOG_ERROR(Lib_Http, "Invalid parameters");
|
||||
return ORBIS_HTTP_ERROR_INVALID_VALUE;
|
||||
}
|
||||
|
||||
if (srcSize == 0) {
|
||||
dst[0] = '\0';
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
u64 len = 0;
|
||||
while (len < srcSize && src[len] != '\0') {
|
||||
len++;
|
||||
}
|
||||
|
||||
for (u64 i = 0; i < len; i++) {
|
||||
dst[i] = src[i];
|
||||
}
|
||||
dst[len] = '\0';
|
||||
|
||||
char* read = dst;
|
||||
char* write = dst;
|
||||
|
||||
while (*read) {
|
||||
if (read[0] == '.' && read[1] == '.' && read[2] == '/') {
|
||||
read += 3;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (read[0] == '.' && read[1] == '/') {
|
||||
read += 2;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (read[0] == '/' && read[1] == '.' && read[2] == '/') {
|
||||
read += 2;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (read[0] == '/' && read[1] == '.' && read[2] == '\0') {
|
||||
if (write == dst) {
|
||||
*write++ = '/';
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
bool is_dotdot_mid = (read[0] == '/' && read[1] == '.' && read[2] == '.' && read[3] == '/');
|
||||
bool is_dotdot_end =
|
||||
(read[0] == '/' && read[1] == '.' && read[2] == '.' && read[3] == '\0');
|
||||
|
||||
if (is_dotdot_mid || is_dotdot_end) {
|
||||
if (write > dst) {
|
||||
if (*(write - 1) == '/') {
|
||||
write--;
|
||||
}
|
||||
while (write > dst && *(write - 1) != '/') {
|
||||
write--;
|
||||
}
|
||||
|
||||
if (is_dotdot_mid && write > dst) {
|
||||
write--;
|
||||
}
|
||||
}
|
||||
|
||||
if (is_dotdot_mid) {
|
||||
read += 3;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if ((read[0] == '.' && read[1] == '\0') ||
|
||||
(read[0] == '.' && read[1] == '.' && read[2] == '\0')) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (read[0] == '/') {
|
||||
*write++ = *read++;
|
||||
}
|
||||
while (*read && *read != '/') {
|
||||
*write++ = *read++;
|
||||
}
|
||||
}
|
||||
|
||||
*write = '\0';
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceHttpUriUnescape(char* out, u64* require, u64 prepare, const char* in) {
|
||||
LOG_ERROR(Lib_Http, "(STUBBED) called");
|
||||
LOG_TRACE(Lib_Http, "called");
|
||||
|
||||
if (!in) {
|
||||
LOG_ERROR(Lib_Http, "Invalid input string");
|
||||
return ORBIS_HTTP_ERROR_INVALID_VALUE;
|
||||
}
|
||||
|
||||
// Locale-independent hex digit check
|
||||
auto IsHex = [](char c) -> bool {
|
||||
return (c >= '0' && c <= '9') || (c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f');
|
||||
};
|
||||
|
||||
// Convert hex char to int value
|
||||
auto HexToInt = [](char c) -> int {
|
||||
if (c >= '0' && c <= '9')
|
||||
return c - '0';
|
||||
if (c >= 'A' && c <= 'F')
|
||||
return c - 'A' + 10;
|
||||
if (c >= 'a' && c <= 'f')
|
||||
return c - 'a' + 10;
|
||||
return 0;
|
||||
};
|
||||
|
||||
// Check for valid percent-encoded sequence (%XX)
|
||||
auto IsValidPercentSequence = [&](const char* s) -> bool {
|
||||
return s[0] == '%' && s[1] != '\0' && s[2] != '\0' && IsHex(s[1]) && IsHex(s[2]);
|
||||
};
|
||||
|
||||
u64 needed = 0;
|
||||
const char* src = in;
|
||||
while (*src) {
|
||||
if (IsValidPercentSequence(src)) {
|
||||
src += 3;
|
||||
} else {
|
||||
src++;
|
||||
}
|
||||
needed++;
|
||||
}
|
||||
needed++; // null terminator
|
||||
|
||||
if (require) {
|
||||
*require = needed;
|
||||
}
|
||||
|
||||
if (!out) {
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
if (prepare < needed) {
|
||||
LOG_ERROR(Lib_Http, "Buffer too small: need {} but only {} available", needed, prepare);
|
||||
return ORBIS_HTTP_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
src = in;
|
||||
char* dst = out;
|
||||
while (*src) {
|
||||
if (IsValidPercentSequence(src)) {
|
||||
*dst++ = static_cast<char>((HexToInt(src[1]) << 4) | HexToInt(src[2]));
|
||||
src += 3;
|
||||
} else {
|
||||
*dst++ = *src++;
|
||||
}
|
||||
}
|
||||
*dst = '\0';
|
||||
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
|
||||
@ -148,7 +148,7 @@ int PS4_SYSV_ABI sceHttpUnsetEpoll();
|
||||
int PS4_SYSV_ABI sceHttpUriBuild(char* out, u64* require, u64 prepare,
|
||||
const OrbisHttpUriElement* srcElement, u32 option);
|
||||
int PS4_SYSV_ABI sceHttpUriCopy();
|
||||
int PS4_SYSV_ABI sceHttpUriEscape();
|
||||
int PS4_SYSV_ABI sceHttpUriEscape(char* out, u64* require, u64 prepare, const char* in);
|
||||
int PS4_SYSV_ABI sceHttpUriMerge(char* mergedUrl, char* url, char* relativeUri, u64* require,
|
||||
u64 prepare, u32 option);
|
||||
int PS4_SYSV_ABI sceHttpUriParse(OrbisHttpUriElement* out, const char* srcUri, void* pool,
|
||||
|
||||
129
src/core/libraries/np/np_commerce.cpp
Normal file
129
src/core/libraries/np/np_commerce.cpp
Normal file
@ -0,0 +1,129 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 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/commondialog.h"
|
||||
|
||||
namespace Libraries::Np::NpCommerce {
|
||||
|
||||
using CommonDialog::Error;
|
||||
using CommonDialog::Result;
|
||||
using CommonDialog::Status;
|
||||
|
||||
static Status g_dialog_status = Status::NONE;
|
||||
static Result g_dialog_result = Result::OK;
|
||||
|
||||
s32 PS4_SYSV_ABI sceNpCommerceDialogClose() {
|
||||
LOG_INFO(Lib_NpCommerce, "called");
|
||||
if (g_dialog_status == Status::NONE) {
|
||||
return static_cast<s32>(Error::NOT_INITIALIZED);
|
||||
}
|
||||
if (g_dialog_status != Status::FINISHED) {
|
||||
return static_cast<s32>(Error::NOT_FINISHED);
|
||||
}
|
||||
g_dialog_status = Status::INITIALIZED;
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceNpCommerceDialogGetResult(s32* result) {
|
||||
LOG_INFO(Lib_NpCommerce, "called");
|
||||
if (result == nullptr) {
|
||||
return static_cast<s32>(Error::ARG_NULL);
|
||||
}
|
||||
if (g_dialog_status != Status::FINISHED) {
|
||||
return static_cast<s32>(Error::NOT_FINISHED);
|
||||
}
|
||||
*result = static_cast<s32>(g_dialog_result);
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
s8 PS4_SYSV_ABI sceNpCommerceDialogGetStatus() {
|
||||
LOG_DEBUG(Lib_NpCommerce, "called, status = {}", static_cast<u32>(g_dialog_status));
|
||||
return static_cast<s8>(g_dialog_status);
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceNpCommerceDialogInitialize() {
|
||||
LOG_INFO(Lib_NpCommerce, "called");
|
||||
if (g_dialog_status != Status::NONE) {
|
||||
return static_cast<s32>(Error::ALREADY_INITIALIZED);
|
||||
}
|
||||
g_dialog_status = Status::INITIALIZED;
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceNpCommerceDialogInitializeInternal() {
|
||||
LOG_INFO(Lib_NpCommerce, "called");
|
||||
return sceNpCommerceDialogInitialize();
|
||||
}
|
||||
|
||||
s16 PS4_SYSV_ABI sceNpCommerceDialogOpen(s64 check) {
|
||||
LOG_INFO(Lib_NpCommerce, "called, check = {}", check);
|
||||
if (g_dialog_status != Status::INITIALIZED) {
|
||||
LOG_WARNING(Lib_NpCommerce, "Dialog not initialized");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
g_dialog_status = Status::FINISHED;
|
||||
g_dialog_result = Result::USER_CANCELED;
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceNpCommerceDialogTerminate() {
|
||||
LOG_INFO(Lib_NpCommerce, "called");
|
||||
if (g_dialog_status == Status::NONE) {
|
||||
return static_cast<s32>(Error::NOT_INITIALIZED);
|
||||
}
|
||||
if (g_dialog_status == Status::RUNNING) {
|
||||
return static_cast<s32>(Error::NOT_FINISHED);
|
||||
}
|
||||
g_dialog_status = Status::NONE;
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceNpCommerceDialogUpdateStatus() {
|
||||
LOG_DEBUG(Lib_NpCommerce, "called, status = {}", static_cast<u32>(g_dialog_status));
|
||||
return static_cast<s32>(g_dialog_status);
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceNpCommerceHidePsStoreIcon() {
|
||||
LOG_ERROR(Lib_NpCommerce, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceNpCommerceSetPsStoreIconLayout(s32 layout) {
|
||||
LOG_ERROR(Lib_NpCommerce, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceNpCommerceShowPsStoreIcon(s16 icon) {
|
||||
LOG_ERROR(Lib_NpCommerce, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
void RegisterLib(Core::Loader::SymbolsResolver* sym) {
|
||||
LIB_FUNCTION("NU3ckGHMFXo", "libSceNpCommerce", 1, "libSceNpCommerce",
|
||||
sceNpCommerceDialogClose);
|
||||
LIB_FUNCTION("r42bWcQbtZY", "libSceNpCommerce", 1, "libSceNpCommerce",
|
||||
sceNpCommerceDialogGetResult);
|
||||
LIB_FUNCTION("CCbC+lqqvF0", "libSceNpCommerce", 1, "libSceNpCommerce",
|
||||
sceNpCommerceDialogGetStatus);
|
||||
LIB_FUNCTION("0aR2aWmQal4", "libSceNpCommerce", 1, "libSceNpCommerce",
|
||||
sceNpCommerceDialogInitialize);
|
||||
LIB_FUNCTION("9ZiLXAGG5rg", "libSceNpCommerce", 1, "libSceNpCommerce",
|
||||
sceNpCommerceDialogInitializeInternal);
|
||||
LIB_FUNCTION("DfSCDRA3EjY", "libSceNpCommerce", 1, "libSceNpCommerce", sceNpCommerceDialogOpen);
|
||||
LIB_FUNCTION("m-I92Ab50W8", "libSceNpCommerce", 1, "libSceNpCommerce",
|
||||
sceNpCommerceDialogTerminate);
|
||||
LIB_FUNCTION("LR5cwFMMCVE", "libSceNpCommerce", 1, "libSceNpCommerce",
|
||||
sceNpCommerceDialogUpdateStatus);
|
||||
LIB_FUNCTION("dsqCVsNM0Zg", "libSceNpCommerce", 1, "libSceNpCommerce",
|
||||
sceNpCommerceHidePsStoreIcon);
|
||||
LIB_FUNCTION("uKTDW8hk-ts", "libSceNpCommerce", 1, "libSceNpCommerce",
|
||||
sceNpCommerceSetPsStoreIconLayout);
|
||||
LIB_FUNCTION("DHmwsa6S8Tc", "libSceNpCommerce", 1, "libSceNpCommerce",
|
||||
sceNpCommerceShowPsStoreIcon);
|
||||
};
|
||||
|
||||
} // namespace Libraries::Np::NpCommerce
|
||||
16
src/core/libraries/np/np_commerce.h
Normal file
16
src/core/libraries/np/np_commerce.h
Normal file
@ -0,0 +1,16 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "common/types.h"
|
||||
|
||||
namespace Core::Loader {
|
||||
class SymbolsResolver;
|
||||
}
|
||||
|
||||
namespace Libraries::Np::NpCommerce {
|
||||
|
||||
void RegisterLib(Core::Loader::SymbolsResolver* sym);
|
||||
|
||||
} // namespace Libraries::Np::NpCommerce
|
||||
@ -1,6 +1,7 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <cstdlib>
|
||||
#include "common/config.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/singleton.h"
|
||||
@ -1874,6 +1875,10 @@ int PS4_SYSV_ABI sceSystemServiceLoadExec(const char* path, const char* argv[])
|
||||
auto emu = Common::Singleton<Core::Emulator>::Instance();
|
||||
auto mnt = Common::Singleton<Core::FileSys::MntPoints>::Instance();
|
||||
auto hostPath = mnt->GetHostPath(std::string_view(path));
|
||||
if (hostPath.empty()) {
|
||||
LOG_INFO(Lib_SystemService, "Restart called with invalid file '{}', exiting.", path);
|
||||
std::quick_exit(0);
|
||||
}
|
||||
std::vector<std::string> args;
|
||||
if (argv != nullptr) {
|
||||
for (const char** ptr = argv; *ptr != nullptr; ptr++) {
|
||||
|
||||
@ -587,6 +587,10 @@ s32 MemoryManager::MapFile(void** out_addr, VAddr virtual_addr, u64 size, Memory
|
||||
// On real hardware, GPU file mmaps cause a full system crash due to an internal error.
|
||||
ASSERT_MSG(false, "Files cannot be mapped to GPU memory");
|
||||
}
|
||||
if (True(prot & MemoryProt::CpuExec)) {
|
||||
// On real hardware, execute permissions are silently removed.
|
||||
prot &= ~MemoryProt::CpuExec;
|
||||
}
|
||||
|
||||
// Add virtual memory area
|
||||
auto& new_vma = CarveVMA(mapped_addr, size)->second;
|
||||
@ -793,10 +797,9 @@ s32 MemoryManager::QueryProtection(VAddr addr, void** start, void** end, u32* pr
|
||||
s64 MemoryManager::ProtectBytes(VAddr addr, VirtualMemoryArea& vma_base, u64 size,
|
||||
MemoryProt prot) {
|
||||
const auto start_in_vma = addr - vma_base.base;
|
||||
const auto adjusted_size =
|
||||
vma_base.size - start_in_vma < size ? vma_base.size - start_in_vma : size;
|
||||
const auto adjusted_size = std::min<u64>(vma_base.size - start_in_vma, size);
|
||||
|
||||
if (vma_base.type == VMAType::Free) {
|
||||
if (vma_base.type == VMAType::Free || vma_base.type == VMAType::PoolReserved) {
|
||||
// On PS4, protecting freed memory does nothing.
|
||||
return adjusted_size;
|
||||
}
|
||||
@ -828,8 +831,9 @@ s64 MemoryManager::ProtectBytes(VAddr addr, VirtualMemoryArea& vma_base, u64 siz
|
||||
perms |= Core::MemoryPermission::ReadWrite;
|
||||
}
|
||||
|
||||
if (vma_base.type == VMAType::Direct || vma_base.type == VMAType::Pooled) {
|
||||
// On PS4, execute permissions are hidden from direct memory mappings.
|
||||
if (vma_base.type == VMAType::Direct || vma_base.type == VMAType::Pooled ||
|
||||
vma_base.type == VMAType::File) {
|
||||
// On PS4, execute permissions are hidden from direct memory and file mappings.
|
||||
// Tests show that execute permissions still apply, so handle this after reading perms.
|
||||
prot &= ~MemoryProt::CpuExec;
|
||||
}
|
||||
@ -837,6 +841,12 @@ s64 MemoryManager::ProtectBytes(VAddr addr, VirtualMemoryArea& vma_base, u64 siz
|
||||
// Change protection
|
||||
vma_base.prot = prot;
|
||||
|
||||
if (vma_base.type == VMAType::Reserved) {
|
||||
// On PS4, protections change vma_map, but don't apply.
|
||||
// Return early to avoid protecting memory that isn't mapped in address space.
|
||||
return adjusted_size;
|
||||
}
|
||||
|
||||
impl.Protect(addr, size, perms);
|
||||
|
||||
return adjusted_size;
|
||||
@ -853,22 +863,20 @@ s32 MemoryManager::Protect(VAddr addr, u64 size, MemoryProt prot) {
|
||||
// Ensure the range to modify is valid
|
||||
ASSERT_MSG(IsValidMapping(addr, size), "Attempted to access invalid address {:#x}", addr);
|
||||
|
||||
// Validate protection flags
|
||||
constexpr static MemoryProt valid_flags =
|
||||
MemoryProt::NoAccess | MemoryProt::CpuRead | MemoryProt::CpuWrite | MemoryProt::CpuExec |
|
||||
MemoryProt::GpuRead | MemoryProt::GpuWrite | MemoryProt::GpuReadWrite;
|
||||
|
||||
MemoryProt invalid_flags = prot & ~valid_flags;
|
||||
if (invalid_flags != MemoryProt::NoAccess) {
|
||||
LOG_ERROR(Kernel_Vmm, "Invalid protection flags");
|
||||
return ORBIS_KERNEL_ERROR_EINVAL;
|
||||
}
|
||||
// Appropriately restrict flags.
|
||||
constexpr static MemoryProt flag_mask =
|
||||
MemoryProt::CpuReadWrite | MemoryProt::CpuExec | MemoryProt::GpuReadWrite;
|
||||
MemoryProt valid_flags = prot & flag_mask;
|
||||
|
||||
// Protect all VMAs between addr and addr + size.
|
||||
s64 protected_bytes = 0;
|
||||
while (protected_bytes < size) {
|
||||
auto it = FindVMA(addr + protected_bytes);
|
||||
auto& vma_base = it->second;
|
||||
if (vma_base.base > addr + protected_bytes) {
|
||||
// Account for potential gaps in memory map.
|
||||
protected_bytes += vma_base.base - (addr + protected_bytes);
|
||||
}
|
||||
auto result = ProtectBytes(addr + protected_bytes, vma_base, size - protected_bytes, prot);
|
||||
if (result < 0) {
|
||||
// ProtectBytes returned an error, return it
|
||||
@ -904,13 +912,21 @@ s32 MemoryManager::VirtualQuery(VAddr addr, s32 flags,
|
||||
const auto& vma = it->second;
|
||||
info->start = vma.base;
|
||||
info->end = vma.base + vma.size;
|
||||
info->offset = vma.type == VMAType::Flexible ? 0 : vma.phys_base;
|
||||
info->offset = 0;
|
||||
info->protection = static_cast<s32>(vma.prot);
|
||||
info->is_flexible = vma.type == VMAType::Flexible ? 1 : 0;
|
||||
info->is_direct = vma.type == VMAType::Direct ? 1 : 0;
|
||||
info->is_stack = vma.type == VMAType::Stack ? 1 : 0;
|
||||
info->is_pooled = vma.type == VMAType::PoolReserved || vma.type == VMAType::Pooled ? 1 : 0;
|
||||
info->is_committed = vma.IsMapped() ? 1 : 0;
|
||||
if (vma.type == VMAType::Direct || vma.type == VMAType::Pooled) {
|
||||
// Offset is only assigned for direct and pooled mappings.
|
||||
info->offset = vma.phys_base;
|
||||
}
|
||||
if (vma.type == VMAType::Reserved || vma.type == VMAType::PoolReserved) {
|
||||
// Protection is hidden from reserved mappings.
|
||||
info->protection = 0;
|
||||
}
|
||||
|
||||
strncpy(info->name, vma.name.data(), ::Libraries::Kernel::ORBIS_KERNEL_MAXIMUM_NAME_LENGTH);
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <mutex>
|
||||
@ -165,10 +165,7 @@ void SetTcbBase(void* image_address) {
|
||||
}
|
||||
|
||||
Tcb* GetTcbBase() {
|
||||
void* tcb = nullptr;
|
||||
const int ret = syscall(SYS_arch_prctl, ARCH_GET_GS, &tcb);
|
||||
ASSERT_MSG(ret == 0, "Failed to get GS base: errno {}", errno);
|
||||
return static_cast<Tcb*>(tcb);
|
||||
return Libraries::Kernel::g_curthread->tcb;
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
@ -1,10 +1,13 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstring>
|
||||
#include "common/types.h"
|
||||
#ifdef _WIN32
|
||||
#include <malloc.h>
|
||||
#endif
|
||||
|
||||
namespace Xbyak {
|
||||
class CodeGenerator;
|
||||
|
||||
@ -42,6 +42,7 @@
|
||||
#include "core/linker.h"
|
||||
#include "core/memory.h"
|
||||
#include "emulator.h"
|
||||
#include "video_core/cache_storage.h"
|
||||
#include "video_core/renderdoc.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
@ -58,10 +59,11 @@ Frontend::WindowSDL* g_window = nullptr;
|
||||
namespace Core {
|
||||
|
||||
Emulator::Emulator() {
|
||||
// Initialize NT API functions and set high priority
|
||||
// Initialize NT API functions, set high priority and disable WER
|
||||
#ifdef _WIN32
|
||||
Common::NtApi::Initialize();
|
||||
SetPriorityClass(GetCurrentProcess(), ABOVE_NORMAL_PRIORITY_CLASS);
|
||||
SetErrorMode(SetErrorMode(0) | SEM_NOGPFAULTERRORBOX);
|
||||
// need to init this in order for winsock2 to work
|
||||
WORD versionWanted = MAKEWORD(2, 2);
|
||||
WSADATA wsaData;
|
||||
@ -386,6 +388,7 @@ void Emulator::Run(std::filesystem::path file, std::vector<std::string> args,
|
||||
}
|
||||
|
||||
UpdatePlayTime(id);
|
||||
Storage::DataBase::Instance().Close();
|
||||
|
||||
std::quick_exit(0);
|
||||
}
|
||||
|
||||
@ -106,6 +106,7 @@ auto output_array = std::array{
|
||||
ControllerOutput(HOTKEY_RELOAD_INPUTS),
|
||||
ControllerOutput(HOTKEY_TOGGLE_MOUSE_TO_JOYSTICK),
|
||||
ControllerOutput(HOTKEY_TOGGLE_MOUSE_TO_GYRO),
|
||||
ControllerOutput(HOTKEY_TOGGLE_MOUSE_TO_TOUCHPAD),
|
||||
ControllerOutput(HOTKEY_RENDERDOC),
|
||||
|
||||
ControllerOutput(SDL_GAMEPAD_BUTTON_INVALID, SDL_GAMEPAD_AXIS_INVALID),
|
||||
@ -579,6 +580,9 @@ void ControllerOutput::FinalizeUpdate() {
|
||||
case HOTKEY_TOGGLE_MOUSE_TO_GYRO:
|
||||
PushSDLEvent(SDL_EVENT_MOUSE_TO_GYRO);
|
||||
break;
|
||||
case HOTKEY_TOGGLE_MOUSE_TO_TOUCHPAD:
|
||||
PushSDLEvent(SDL_EVENT_MOUSE_TO_TOUCHPAD);
|
||||
break;
|
||||
case HOTKEY_RENDERDOC:
|
||||
PushSDLEvent(SDL_EVENT_RDOC_CAPTURE);
|
||||
break;
|
||||
@ -773,6 +777,9 @@ void ActivateOutputsFromInputs() {
|
||||
it.ResetUpdate();
|
||||
}
|
||||
|
||||
// Check for input blockers
|
||||
ApplyMouseInputBlockers();
|
||||
|
||||
// Iterate over all inputs, and update their respecive outputs accordingly
|
||||
for (auto& it : connections) {
|
||||
it.output->AddUpdate(it.ProcessBinding());
|
||||
|
||||
@ -34,9 +34,10 @@
|
||||
#define SDL_EVENT_RELOAD_INPUTS SDL_EVENT_USER + 5
|
||||
#define SDL_EVENT_MOUSE_TO_JOYSTICK SDL_EVENT_USER + 6
|
||||
#define SDL_EVENT_MOUSE_TO_GYRO SDL_EVENT_USER + 7
|
||||
#define SDL_EVENT_RDOC_CAPTURE SDL_EVENT_USER + 8
|
||||
#define SDL_EVENT_QUIT_DIALOG SDL_EVENT_USER + 9
|
||||
#define SDL_EVENT_MOUSE_WHEEL_OFF SDL_EVENT_USER + 10
|
||||
#define SDL_EVENT_MOUSE_TO_TOUCHPAD SDL_EVENT_USER + 8
|
||||
#define SDL_EVENT_RDOC_CAPTURE SDL_EVENT_USER + 9
|
||||
#define SDL_EVENT_QUIT_DIALOG SDL_EVENT_USER + 10
|
||||
#define SDL_EVENT_MOUSE_WHEEL_OFF SDL_EVENT_USER + 11
|
||||
|
||||
#define LEFTJOYSTICK_HALFMODE 0x00010000
|
||||
#define RIGHTJOYSTICK_HALFMODE 0x00020000
|
||||
@ -52,7 +53,8 @@
|
||||
#define HOTKEY_RELOAD_INPUTS 0xf0000005
|
||||
#define HOTKEY_TOGGLE_MOUSE_TO_JOYSTICK 0xf0000006
|
||||
#define HOTKEY_TOGGLE_MOUSE_TO_GYRO 0xf0000007
|
||||
#define HOTKEY_RENDERDOC 0xf0000008
|
||||
#define HOTKEY_TOGGLE_MOUSE_TO_TOUCHPAD 0xf0000008
|
||||
#define HOTKEY_RENDERDOC 0xf0000009
|
||||
|
||||
#define SDL_UNMAPPED UINT32_MAX - 1
|
||||
|
||||
@ -141,6 +143,7 @@ const std::map<std::string, u32> string_to_cbutton_map = {
|
||||
{"hotkey_reload_inputs", HOTKEY_RELOAD_INPUTS},
|
||||
{"hotkey_toggle_mouse_to_joystick", HOTKEY_TOGGLE_MOUSE_TO_JOYSTICK},
|
||||
{"hotkey_toggle_mouse_to_gyro", HOTKEY_TOGGLE_MOUSE_TO_GYRO},
|
||||
{"hotkey_toggle_mouse_to_touchpad", HOTKEY_TOGGLE_MOUSE_TO_TOUCHPAD},
|
||||
{"hotkey_renderdoc_capture", HOTKEY_RENDERDOC},
|
||||
};
|
||||
|
||||
|
||||
@ -6,12 +6,19 @@
|
||||
#include "common/assert.h"
|
||||
#include "common/types.h"
|
||||
#include "input/controller.h"
|
||||
#include "input/input_handler.h"
|
||||
#include "input_mouse.h"
|
||||
|
||||
#include <common/singleton.h>
|
||||
#include <emulator.h>
|
||||
#include "SDL3/SDL.h"
|
||||
|
||||
extern Frontend::WindowSDL* g_window;
|
||||
|
||||
namespace Input {
|
||||
|
||||
extern std::list<std::pair<InputEvent, bool>> pressed_keys;
|
||||
|
||||
int mouse_joystick_binding = 0;
|
||||
float mouse_deadzone_offset = 0.5, mouse_speed = 1, mouse_speed_offset = 0.1250;
|
||||
bool mouse_gyro_roll_mode = false;
|
||||
@ -80,7 +87,6 @@ void EmulateJoystick(GameController* controller, u32 interval) {
|
||||
|
||||
constexpr float constant_down_accel[3] = {0.0f, 10.0f, 0.0f};
|
||||
void EmulateGyro(GameController* controller, u32 interval) {
|
||||
// LOG_INFO(Input, "todo gyro");
|
||||
float d_x = 0, d_y = 0;
|
||||
SDL_GetRelativeMouseState(&d_x, &d_y);
|
||||
controller->Acceleration(1, constant_down_accel);
|
||||
@ -92,6 +98,31 @@ void EmulateGyro(GameController* controller, u32 interval) {
|
||||
controller->Gyro(1, gyro_from_mouse);
|
||||
}
|
||||
|
||||
void EmulateTouchpad(GameController* controller, u32 interval) {
|
||||
float x, y;
|
||||
SDL_MouseButtonFlags mouse_buttons = SDL_GetMouseState(&x, &y);
|
||||
controller->SetTouchpadState(0, (mouse_buttons & SDL_BUTTON_LMASK) != 0,
|
||||
std::clamp(x / g_window->GetWidth(), 0.0f, 1.0f),
|
||||
std::clamp(y / g_window->GetHeight(), 0.0f, 1.0f));
|
||||
controller->CheckButton(0, Libraries::Pad::OrbisPadButtonDataOffset::TouchPad,
|
||||
(mouse_buttons & SDL_BUTTON_RMASK) != 0);
|
||||
}
|
||||
|
||||
void ApplyMouseInputBlockers() {
|
||||
switch (mouse_mode) {
|
||||
case MouseMode::Touchpad:
|
||||
for (auto& k : pressed_keys) {
|
||||
if (k.first.input.sdl_id == SDL_BUTTON_LEFT ||
|
||||
k.first.input.sdl_id == SDL_BUTTON_RIGHT) {
|
||||
k.second = true;
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Uint32 MousePolling(void* param, Uint32 id, Uint32 interval) {
|
||||
auto* controller = (GameController*)param;
|
||||
switch (mouse_mode) {
|
||||
@ -101,6 +132,9 @@ Uint32 MousePolling(void* param, Uint32 id, Uint32 interval) {
|
||||
case MouseMode::Gyro:
|
||||
EmulateGyro(controller, interval);
|
||||
break;
|
||||
case MouseMode::Touchpad:
|
||||
EmulateTouchpad(controller, interval);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
|
||||
@ -12,6 +12,7 @@ enum MouseMode {
|
||||
Off = 0,
|
||||
Joystick,
|
||||
Gyro,
|
||||
Touchpad,
|
||||
};
|
||||
|
||||
bool ToggleMouseModeTo(MouseMode m);
|
||||
@ -22,6 +23,8 @@ void SetMouseGyroRollMode(bool mode);
|
||||
void EmulateJoystick(GameController* controller, u32 interval);
|
||||
void EmulateGyro(GameController* controller, u32 interval);
|
||||
|
||||
void ApplyMouseInputBlockers();
|
||||
|
||||
// Polls the mouse for changes
|
||||
Uint32 MousePolling(void* param, Uint32 id, Uint32 interval);
|
||||
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <SDL3/SDL_messagebox.h>
|
||||
#include "functional"
|
||||
#include "iostream"
|
||||
#include "string"
|
||||
@ -182,6 +183,10 @@ int main(int argc, char* argv[]) {
|
||||
}}};
|
||||
|
||||
if (argc == 1) {
|
||||
if (!SDL_ShowSimpleMessageBox(
|
||||
SDL_MESSAGEBOX_INFORMATION, "shadPS4",
|
||||
"This is a CLI application. Please use the QTLauncher for a GUI.", nullptr))
|
||||
std::cerr << "Could not display SDL message box! Error: " << SDL_GetError() << "\n";
|
||||
int dummy = 0; // one does not simply pass 0 directly
|
||||
arg_map.at("-h")(dummy);
|
||||
return -1;
|
||||
|
||||
@ -457,6 +457,11 @@ void WindowSDL::WaitEvent() {
|
||||
SDL_SetWindowRelativeMouseMode(this->GetSDLWindow(),
|
||||
Input::ToggleMouseModeTo(Input::MouseMode::Gyro));
|
||||
break;
|
||||
case SDL_EVENT_MOUSE_TO_TOUCHPAD:
|
||||
SDL_SetWindowRelativeMouseMode(this->GetSDLWindow(),
|
||||
Input::ToggleMouseModeTo(Input::MouseMode::Touchpad));
|
||||
SDL_SetWindowRelativeMouseMode(this->GetSDLWindow(), false);
|
||||
break;
|
||||
case SDL_EVENT_RDOC_CAPTURE:
|
||||
VideoCore::TriggerCapture();
|
||||
break;
|
||||
|
||||
@ -51,7 +51,7 @@ std::optional<FetchShaderData> ParseFetchShader(const Shader::Info& info) {
|
||||
}
|
||||
|
||||
const auto* code = GetFetchShaderCode(info, info.fetch_shader_sgpr_base);
|
||||
FetchShaderData data{.code = code};
|
||||
FetchShaderData data{};
|
||||
GcnCodeSlice code_slice(code, code + std::numeric_limits<u32>::max());
|
||||
GcnDecodeContext decoder;
|
||||
|
||||
|
||||
@ -8,6 +8,10 @@
|
||||
#include "common/types.h"
|
||||
#include "shader_recompiler/info.h"
|
||||
|
||||
namespace Serialization {
|
||||
struct Archive;
|
||||
}
|
||||
|
||||
namespace Shader::Gcn {
|
||||
|
||||
struct VertexAttribute {
|
||||
@ -50,7 +54,6 @@ struct VertexAttribute {
|
||||
};
|
||||
|
||||
struct FetchShaderData {
|
||||
const u32* code;
|
||||
u32 size = 0;
|
||||
std::vector<VertexAttribute> attributes;
|
||||
s8 vertex_offset_sgpr = -1; ///< SGPR of vertex offset from VADDR
|
||||
@ -60,6 +63,9 @@ struct FetchShaderData {
|
||||
return attributes == other.attributes && vertex_offset_sgpr == other.vertex_offset_sgpr &&
|
||||
instance_offset_sgpr == other.instance_offset_sgpr;
|
||||
}
|
||||
|
||||
void Serialize(Serialization::Archive& ar) const;
|
||||
bool Deserialize(Serialization::Archive& buffer);
|
||||
};
|
||||
|
||||
const u32* GetFetchShaderCode(const Info& info, u32 sgpr_base);
|
||||
|
||||
@ -1837,11 +1837,17 @@ constexpr std::array<InstFormat, 71> InstructionFormatVOP1 = {{
|
||||
// 22 = V_CVT_F64_U32
|
||||
{InstClass::VectorConv, InstCategory::VectorALU, 1, 1, ScalarType::Uint32, ScalarType::Float64},
|
||||
// 23 = V_TRUNC_F64
|
||||
{InstClass::VectorConv, InstCategory::VectorALU, 1, 1, ScalarType::Float64,
|
||||
{InstClass::VectorFpRound64, InstCategory::VectorALU, 1, 1, ScalarType::Float64,
|
||||
ScalarType::Float64},
|
||||
// 24 = V_CEIL_F64
|
||||
{InstClass::VectorFpRound64, InstCategory::VectorALU, 1, 1, ScalarType::Float64,
|
||||
ScalarType::Float64},
|
||||
// 25 = V_RNDNE_F64
|
||||
{InstClass::VectorFpRound64, InstCategory::VectorALU, 1, 1, ScalarType::Float64,
|
||||
ScalarType::Float64},
|
||||
// 26 = V_FLOOR_F64
|
||||
{InstClass::VectorFpRound64, InstCategory::VectorALU, 1, 1, ScalarType::Float64,
|
||||
ScalarType::Float64},
|
||||
{},
|
||||
{},
|
||||
{},
|
||||
{},
|
||||
{},
|
||||
{},
|
||||
|
||||
@ -596,9 +596,8 @@ public:
|
||||
IR::AbstractSyntaxList& syntax_list_, std::span<const GcnInst> inst_list_,
|
||||
Info& info_, const RuntimeInfo& runtime_info_, const Profile& profile_)
|
||||
: stmt_pool{stmt_pool_}, inst_pool{inst_pool_}, block_pool{block_pool_},
|
||||
syntax_list{syntax_list_}, inst_list{inst_list_}, info{info_},
|
||||
runtime_info{runtime_info_}, profile{profile_},
|
||||
translator{info_, runtime_info_, profile_} {
|
||||
syntax_list{syntax_list_}, inst_list{inst_list_}, runtime_info{runtime_info_},
|
||||
profile{profile_}, translator{info_, runtime_info_, profile_} {
|
||||
Visit(root_stmt, nullptr, nullptr);
|
||||
|
||||
IR::Block* first_block = syntax_list.front().data.block;
|
||||
@ -782,7 +781,7 @@ private:
|
||||
}
|
||||
}
|
||||
|
||||
IR::Block* MergeBlock(Statement& parent, Statement& stmt) {
|
||||
IR::Block* MergeBlock(Statement& parent, Statement& stmt) const {
|
||||
Statement* merge_stmt{TryFindForwardBlock(stmt)};
|
||||
if (!merge_stmt) {
|
||||
// Create a merge block we can visit later
|
||||
@ -798,7 +797,6 @@ private:
|
||||
IR::AbstractSyntaxList& syntax_list;
|
||||
const Block dummy_flow_block{.is_dummy = true};
|
||||
std::span<const GcnInst> inst_list;
|
||||
Info& info;
|
||||
const RuntimeInfo& runtime_info;
|
||||
const Profile& profile;
|
||||
Translator translator;
|
||||
|
||||
@ -560,7 +560,8 @@ void Translator::EmitFetch(const GcnInst& inst) {
|
||||
}
|
||||
const auto filename = fmt::format("vs_{:#018x}.fetch.bin", info.pgm_hash);
|
||||
const auto file = IOFile{dump_dir / filename, FileAccessMode::Create};
|
||||
file.WriteRaw<u8>(fetch_data->code, fetch_data->size);
|
||||
const auto* code = GetFetchShaderCode(info, code_sgpr_base);
|
||||
file.WriteRaw<u8>(code, fetch_data->size);
|
||||
}
|
||||
|
||||
for (const auto& attrib : fetch_data->attributes) {
|
||||
|
||||
@ -201,6 +201,7 @@ public:
|
||||
void V_CVT_F32_F64(const GcnInst& inst);
|
||||
void V_CVT_F64_F32(const GcnInst& inst);
|
||||
void V_CVT_F32_UBYTE(u32 index, const GcnInst& inst);
|
||||
void V_FLOOR_F64(const GcnInst& inst);
|
||||
void V_FRACT_F32(const GcnInst& inst);
|
||||
void V_TRUNC_F32(const GcnInst& inst);
|
||||
void V_CEIL_F32(const GcnInst& inst);
|
||||
|
||||
@ -142,6 +142,8 @@ void Translator::EmitVectorAlu(const GcnInst& inst) {
|
||||
return V_CVT_F32_UBYTE(2, inst);
|
||||
case Opcode::V_CVT_F32_UBYTE3:
|
||||
return V_CVT_F32_UBYTE(3, inst);
|
||||
case Opcode::V_FLOOR_F64:
|
||||
return V_FLOOR_F64(inst);
|
||||
case Opcode::V_FRACT_F32:
|
||||
return V_FRACT_F32(inst);
|
||||
case Opcode::V_TRUNC_F32:
|
||||
@ -806,6 +808,11 @@ void Translator::V_CVT_F32_UBYTE(u32 index, const GcnInst& inst) {
|
||||
SetDst(inst.dst[0], ir.ConvertUToF(32, 32, byte));
|
||||
}
|
||||
|
||||
void Translator::V_FLOOR_F64(const GcnInst& inst) {
|
||||
const IR::F64 src0{GetSrc64<IR::F64>(inst.src[0])};
|
||||
SetDst64(inst.dst[0], ir.FPFloor(src0));
|
||||
}
|
||||
|
||||
void Translator::V_FRACT_F32(const GcnInst& inst) {
|
||||
const IR::F32 src0{GetSrc<IR::F32>(inst.src[0])};
|
||||
SetDst(inst.dst[0], ir.FPFract(src0));
|
||||
@ -1043,20 +1050,25 @@ void Translator::V_CMP_U32(ConditionOp op, bool is_signed, bool set_exec, const
|
||||
}
|
||||
|
||||
void Translator::V_CMP_U64(ConditionOp op, bool is_signed, bool set_exec, const GcnInst& inst) {
|
||||
const IR::U64 src0{GetSrc64(inst.src[0])};
|
||||
const IR::U64 src1{GetSrc64(inst.src[1])};
|
||||
ASSERT(inst.src[1].field == OperandField::ConstZero);
|
||||
const IR::U1 src0 = [&] {
|
||||
switch (inst.src[0].field) {
|
||||
case OperandField::ScalarGPR:
|
||||
return ir.GetThreadBitScalarReg(IR::ScalarReg(inst.src[0].code));
|
||||
case OperandField::VccLo:
|
||||
return ir.GetVcc();
|
||||
default:
|
||||
UNREACHABLE_MSG("src0 = {}", u32(inst.src[0].field));
|
||||
}
|
||||
}();
|
||||
const IR::U1 result = [&] {
|
||||
switch (op) {
|
||||
case ConditionOp::EQ:
|
||||
return ir.IEqual(src0, src1);
|
||||
return ir.LogicalNot(src0);
|
||||
case ConditionOp::LG: // NE
|
||||
return ir.INotEqual(src0, src1);
|
||||
return src0;
|
||||
case ConditionOp::GT:
|
||||
if (src1.IsImmediate() && src1.U64() == 0) {
|
||||
ASSERT(inst.src[0].field == OperandField::ScalarGPR);
|
||||
return ir.GroupAny(ir.GetThreadBitScalarReg(IR::ScalarReg(inst.src[0].code)));
|
||||
}
|
||||
return ir.IGreaterThan(src0, src1, is_signed);
|
||||
return ir.GroupAny(ir.GetThreadBitScalarReg(IR::ScalarReg(inst.src[0].code)));
|
||||
default:
|
||||
UNREACHABLE_MSG("Unsupported V_CMP_U64 condition operation: {}", u32(op));
|
||||
}
|
||||
|
||||
@ -19,6 +19,10 @@
|
||||
#include "shader_recompiler/resource.h"
|
||||
#include "shader_recompiler/runtime_info.h"
|
||||
|
||||
namespace Serialization {
|
||||
struct Archive;
|
||||
}
|
||||
|
||||
namespace Shader {
|
||||
|
||||
enum class Qualifier : u8 {
|
||||
@ -34,7 +38,49 @@ enum class Qualifier : u8 {
|
||||
/**
|
||||
* Contains general information generated by the shader recompiler for an input program.
|
||||
*/
|
||||
struct Info {
|
||||
struct InfoPersistent {
|
||||
BufferResourceList buffers;
|
||||
ImageResourceList images;
|
||||
SamplerResourceList samplers;
|
||||
FMaskResourceList fmasks;
|
||||
|
||||
struct UserDataMask {
|
||||
void Set(IR::ScalarReg reg) noexcept {
|
||||
mask |= 1 << static_cast<u32>(reg);
|
||||
}
|
||||
|
||||
u32 Index(IR::ScalarReg reg) const noexcept {
|
||||
const u32 reg_mask = (1 << static_cast<u32>(reg)) - 1;
|
||||
return std::popcount(mask & reg_mask);
|
||||
}
|
||||
|
||||
u32 NumRegs() const noexcept {
|
||||
return std::popcount(mask);
|
||||
}
|
||||
|
||||
u32 mask;
|
||||
};
|
||||
UserDataMask ud_mask{};
|
||||
u32 fetch_shader_sgpr_base{};
|
||||
|
||||
u64 pgm_hash{};
|
||||
|
||||
s32 tess_consts_dword_offset = -1;
|
||||
IR::ScalarReg tess_consts_ptr_base = IR::ScalarReg::Max;
|
||||
Stage stage;
|
||||
LogicalStage l_stage;
|
||||
|
||||
u8 mrt_mask{};
|
||||
bool has_fetch_shader{};
|
||||
bool has_bitwise_xor{};
|
||||
bool uses_dma{};
|
||||
|
||||
InfoPersistent() = default;
|
||||
InfoPersistent(Stage stage_, LogicalStage l_stage_, u64 pgm_hash_)
|
||||
: stage{stage_}, l_stage{l_stage_}, pgm_hash{pgm_hash_} {}
|
||||
};
|
||||
|
||||
struct Info : InfoPersistent {
|
||||
struct AttributeFlags {
|
||||
bool Get(IR::Attribute attrib, u32 comp = 0) const {
|
||||
return flags[Index(attrib)] & (1 << comp);
|
||||
@ -58,56 +104,32 @@ struct Info {
|
||||
|
||||
std::array<u8, IR::NumAttributes> flags;
|
||||
};
|
||||
AttributeFlags loads{};
|
||||
AttributeFlags stores{};
|
||||
|
||||
struct UserDataMask {
|
||||
void Set(IR::ScalarReg reg) noexcept {
|
||||
mask |= 1 << static_cast<u32>(reg);
|
||||
}
|
||||
|
||||
u32 Index(IR::ScalarReg reg) const noexcept {
|
||||
const u32 reg_mask = (1 << static_cast<u32>(reg)) - 1;
|
||||
return std::popcount(mask & reg_mask);
|
||||
}
|
||||
|
||||
u32 NumRegs() const noexcept {
|
||||
return std::popcount(mask);
|
||||
}
|
||||
|
||||
u32 mask;
|
||||
enum class ReadConstType {
|
||||
None = 0,
|
||||
Immediate = 1 << 0,
|
||||
Dynamic = 1 << 1,
|
||||
};
|
||||
UserDataMask ud_mask{};
|
||||
|
||||
CopyShaderData gs_copy_data;
|
||||
u32 uses_patches{};
|
||||
|
||||
BufferResourceList buffers;
|
||||
ImageResourceList images;
|
||||
SamplerResourceList samplers;
|
||||
FMaskResourceList fmasks;
|
||||
|
||||
PersistentSrtInfo srt_info;
|
||||
std::vector<u32> flattened_ud_buf;
|
||||
|
||||
struct Interpolation {
|
||||
Qualifier primary;
|
||||
Qualifier auxiliary;
|
||||
};
|
||||
std::array<Interpolation, IR::NumParams> fs_interpolation{};
|
||||
|
||||
IR::ScalarReg tess_consts_ptr_base = IR::ScalarReg::Max;
|
||||
s32 tess_consts_dword_offset = -1;
|
||||
|
||||
std::span<const u32> user_data;
|
||||
Stage stage;
|
||||
LogicalStage l_stage;
|
||||
std::vector<u32> flattened_ud_buf;
|
||||
PersistentSrtInfo srt_info;
|
||||
|
||||
AttributeFlags loads{};
|
||||
AttributeFlags stores{};
|
||||
|
||||
ReadConstType readconst_types{};
|
||||
CopyShaderData gs_copy_data;
|
||||
u32 uses_patches{};
|
||||
|
||||
u64 pgm_hash{};
|
||||
VAddr pgm_base;
|
||||
bool has_storage_images{};
|
||||
bool has_discard{};
|
||||
bool has_bitwise_xor{};
|
||||
bool has_image_gather{};
|
||||
bool has_image_query{};
|
||||
bool uses_buffer_atomic_float_min_max{};
|
||||
@ -125,20 +147,12 @@ struct Info {
|
||||
bool stores_tess_level_outer{};
|
||||
bool stores_tess_level_inner{};
|
||||
bool translation_failed{};
|
||||
u8 mrt_mask{0u};
|
||||
bool has_fetch_shader{false};
|
||||
u32 fetch_shader_sgpr_base{0u};
|
||||
|
||||
enum class ReadConstType {
|
||||
None = 0,
|
||||
Immediate = 1 << 0,
|
||||
Dynamic = 1 << 1,
|
||||
};
|
||||
ReadConstType readconst_types{};
|
||||
bool uses_dma{};
|
||||
std::array<Interpolation, IR::NumParams> fs_interpolation{};
|
||||
|
||||
explicit Info(Stage stage_, LogicalStage l_stage_, ShaderParams params)
|
||||
: stage{stage_}, l_stage{l_stage_}, pgm_hash{params.hash}, pgm_base{params.Base()},
|
||||
Info() = default;
|
||||
Info(Stage stage_, LogicalStage l_stage_, ShaderParams params)
|
||||
: InfoPersistent(stage_, l_stage_, params.hash), pgm_base{params.Base()},
|
||||
user_data{params.user_data} {}
|
||||
|
||||
template <typename T>
|
||||
@ -192,6 +206,9 @@ struct Info {
|
||||
reinterpret_cast<TessellationDataConstantBuffer*>(tess_constants_addr),
|
||||
sizeof(tess_constants));
|
||||
}
|
||||
|
||||
void Serialize(Serialization::Archive& ar) const;
|
||||
bool Deserialize(Serialization::Archive& ar);
|
||||
};
|
||||
DECLARE_ENUM_FLAG_OPERATORS(Info::ReadConstType);
|
||||
|
||||
|
||||
@ -28,6 +28,17 @@ using namespace Xbyak::util;
|
||||
static Xbyak::CodeGenerator g_srt_codegen(32_MB);
|
||||
static const u8* g_srt_codegen_start = nullptr;
|
||||
|
||||
namespace Shader {
|
||||
|
||||
PFN_SrtWalker RegisterWalkerCode(const u8* ptr, size_t size) {
|
||||
const auto func_addr = (PFN_SrtWalker)g_srt_codegen.getCurr();
|
||||
g_srt_codegen.db(ptr, size);
|
||||
g_srt_codegen.ready();
|
||||
return func_addr;
|
||||
}
|
||||
|
||||
} // namespace Shader
|
||||
|
||||
namespace {
|
||||
|
||||
static void DumpSrtProgram(const Shader::Info& info, const u8* code, size_t codesize) {
|
||||
@ -215,9 +226,12 @@ static void GenerateSrtProgram(Info& info, PassInfo& pass_info) {
|
||||
c.ret();
|
||||
c.ready();
|
||||
|
||||
info.srt_info.walker_func_size =
|
||||
c.getCurr() - reinterpret_cast<const u8*>(info.srt_info.walker_func);
|
||||
|
||||
if (Config::dumpShaders()) {
|
||||
size_t codesize = c.getCurr() - reinterpret_cast<const u8*>(info.srt_info.walker_func);
|
||||
DumpSrtProgram(info, reinterpret_cast<const u8*>(info.srt_info.walker_func), codesize);
|
||||
DumpSrtProgram(info, reinterpret_cast<const u8*>(info.srt_info.walker_func),
|
||||
info.srt_info.walker_func_size);
|
||||
}
|
||||
|
||||
info.srt_info.flattened_bufsize_dw = pass_info.dst_off_dw;
|
||||
|
||||
@ -363,7 +363,7 @@ static IR::F32 ReadTessControlPointAttribute(IR::U32 addr, const u32 stride, IR:
|
||||
|
||||
} // namespace
|
||||
|
||||
void HullShaderTransform(IR::Program& program, RuntimeInfo& runtime_info) {
|
||||
void HullShaderTransform(IR::Program& program, const RuntimeInfo& runtime_info) {
|
||||
const Info& info = program.info;
|
||||
|
||||
for (IR::Block* block : program.blocks) {
|
||||
@ -561,8 +561,8 @@ void HullShaderTransform(IR::Program& program, RuntimeInfo& runtime_info) {
|
||||
}
|
||||
}
|
||||
|
||||
void DomainShaderTransform(IR::Program& program, RuntimeInfo& runtime_info) {
|
||||
Info& info = program.info;
|
||||
void DomainShaderTransform(const IR::Program& program, const RuntimeInfo& runtime_info) {
|
||||
const Info& info = program.info;
|
||||
|
||||
for (IR::Block* block : program.blocks) {
|
||||
for (IR::Inst& inst : block->Instructions()) {
|
||||
|
||||
@ -24,8 +24,8 @@ void LowerBufferFormatToRaw(IR::Program& program);
|
||||
void LowerFp64ToFp32(IR::Program& program);
|
||||
void RingAccessElimination(const IR::Program& program, const RuntimeInfo& runtime_info);
|
||||
void TessellationPreprocess(IR::Program& program, RuntimeInfo& runtime_info);
|
||||
void HullShaderTransform(IR::Program& program, RuntimeInfo& runtime_info);
|
||||
void DomainShaderTransform(IR::Program& program, RuntimeInfo& runtime_info);
|
||||
void HullShaderTransform(IR::Program& program, const RuntimeInfo& runtime_info);
|
||||
void DomainShaderTransform(const IR::Program& program, const RuntimeInfo& runtime_info);
|
||||
void SharedMemoryBarrierPass(IR::Program& program, const RuntimeInfo& runtime_info,
|
||||
const Profile& profile);
|
||||
void SharedMemorySimplifyPass(IR::Program& program, const Profile& profile);
|
||||
|
||||
@ -498,7 +498,8 @@ void PatchBufferSharp(IR::Block& block, IR::Inst& inst, Info& info, Descriptors&
|
||||
// buffer_load_format_xyz v[8:10], v1, s[32:35], 0 ...
|
||||
// is used to define an inline buffer resource
|
||||
std::array<u64, 2> raw;
|
||||
raw[0] = info.pgm_base + (handle->Arg(0).U32() | u64(handle->Arg(1).U32()) << 32);
|
||||
// Keep relative address, we'll do fixup of the address at buffer fetch later
|
||||
raw[0] = (handle->Arg(0).U32() | u64(handle->Arg(1).U32()) << 32);
|
||||
raw[1] = handle->Arg(2).U32() | u64(handle->Arg(3).U32()) << 32;
|
||||
const auto buffer = std::bit_cast<AmdGpu::Buffer>(raw);
|
||||
buffer_binding = descriptors.Add(BufferResource{
|
||||
|
||||
@ -7,9 +7,14 @@
|
||||
#include <boost/container/small_vector.hpp>
|
||||
#include "common/types.h"
|
||||
|
||||
namespace Serialization {
|
||||
struct Archive;
|
||||
}
|
||||
|
||||
namespace Shader {
|
||||
|
||||
using PFN_SrtWalker = void PS4_SYSV_ABI (*)(const u32* /*user_data*/, u32* /*flat_dst*/);
|
||||
PFN_SrtWalker RegisterWalkerCode(const u8* ptr, size_t size);
|
||||
|
||||
struct PersistentSrtInfo {
|
||||
// Special case when fetch shader uses step rates.
|
||||
@ -20,7 +25,11 @@ struct PersistentSrtInfo {
|
||||
};
|
||||
|
||||
PFN_SrtWalker walker_func{};
|
||||
size_t walker_func_size{};
|
||||
u32 flattened_bufsize_dw = 16; // NumUserDataRegs
|
||||
|
||||
void Serialize(Serialization::Archive& ar) const;
|
||||
bool Deserialize(Serialization::Archive& ar);
|
||||
};
|
||||
|
||||
} // namespace Shader
|
||||
|
||||
@ -8,6 +8,10 @@
|
||||
namespace Shader {
|
||||
|
||||
struct Profile {
|
||||
u64 max_ubo_size{};
|
||||
u32 max_viewport_width{};
|
||||
u32 max_viewport_height{};
|
||||
u32 max_shared_memory_size{};
|
||||
u32 supported_spirv{0x00010000};
|
||||
u32 subgroup_size{};
|
||||
bool support_int8{};
|
||||
@ -37,10 +41,7 @@ struct Profile {
|
||||
bool needs_lds_barriers{};
|
||||
bool needs_buffer_offsets{};
|
||||
bool needs_unorm_fixup{};
|
||||
u64 max_ubo_size{};
|
||||
u32 max_viewport_width{};
|
||||
u32 max_viewport_height{};
|
||||
u32 max_shared_memory_size{};
|
||||
bool _pad0{};
|
||||
};
|
||||
|
||||
} // namespace Shader
|
||||
|
||||
@ -29,7 +29,7 @@ IR::BlockList GenerateBlocks(const IR::AbstractSyntaxList& syntax_list) {
|
||||
return blocks;
|
||||
}
|
||||
|
||||
IR::Program TranslateProgram(std::span<const u32> code, Pools& pools, Info& info,
|
||||
IR::Program TranslateProgram(const std::span<const u32>& code, Pools& pools, Info& info,
|
||||
RuntimeInfo& runtime_info, const Profile& profile) {
|
||||
// Ensure first instruction is expected.
|
||||
constexpr u32 token_mov_vcchi = 0xBEEB03FF;
|
||||
@ -55,8 +55,8 @@ IR::Program TranslateProgram(std::span<const u32> code, Pools& pools, Info& info
|
||||
Gcn::CFG cfg{gcn_block_pool, program.ins_list};
|
||||
|
||||
// Structurize control flow graph and create program.
|
||||
program.syntax_list = Shader::Gcn::BuildASL(pools.inst_pool, pools.block_pool, cfg,
|
||||
program.info, runtime_info, profile);
|
||||
program.syntax_list =
|
||||
Shader::Gcn::BuildASL(pools.inst_pool, pools.block_pool, cfg, info, runtime_info, profile);
|
||||
program.blocks = GenerateBlocks(program.syntax_list);
|
||||
program.post_order_blocks = Shader::IR::PostOrder(program.syntax_list.front());
|
||||
|
||||
|
||||
@ -27,7 +27,8 @@ struct Pools {
|
||||
}
|
||||
};
|
||||
|
||||
[[nodiscard]] IR::Program TranslateProgram(std::span<const u32> code, Pools& pools, Info& info,
|
||||
RuntimeInfo& runtime_info, const Profile& profile);
|
||||
[[nodiscard]] IR::Program TranslateProgram(const std::span<const u32>& code, Pools& pools,
|
||||
Info& info, RuntimeInfo& runtime_info,
|
||||
const Profile& profile);
|
||||
|
||||
} // namespace Shader
|
||||
|
||||
@ -53,8 +53,15 @@ struct BufferResource {
|
||||
}
|
||||
|
||||
constexpr AmdGpu::Buffer GetSharp(const auto& info) const noexcept {
|
||||
const auto buffer =
|
||||
inline_cbuf ? inline_cbuf : info.template ReadUdSharp<AmdGpu::Buffer>(sharp_idx);
|
||||
AmdGpu::Buffer buffer{};
|
||||
if (inline_cbuf) {
|
||||
buffer = inline_cbuf;
|
||||
if (inline_cbuf.base_address > 1) {
|
||||
buffer.base_address += info.pgm_base; // address fixup
|
||||
}
|
||||
} else {
|
||||
buffer = info.template ReadUdSharp<AmdGpu::Buffer>(sharp_idx);
|
||||
}
|
||||
if (!buffer.Valid()) {
|
||||
LOG_DEBUG(Render, "Encountered invalid buffer sharp");
|
||||
return AmdGpu::Buffer::Null();
|
||||
|
||||
@ -159,7 +159,8 @@ struct GeometryRuntimeInfo {
|
||||
return num_outputs == other.num_outputs && outputs == other.outputs && num_invocations &&
|
||||
other.num_invocations && output_vertices == other.output_vertices &&
|
||||
in_primitive == other.in_primitive &&
|
||||
std::ranges::equal(out_primitive, other.out_primitive);
|
||||
std::ranges::equal(out_primitive, other.out_primitive) &&
|
||||
vs_copy_hash == other.vs_copy_hash;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@ -79,8 +79,8 @@ struct SamplerSpecialization {
|
||||
struct StageSpecialization {
|
||||
static constexpr size_t MaxStageResources = 128;
|
||||
|
||||
const Shader::Info* info;
|
||||
RuntimeInfo runtime_info;
|
||||
const Info* info{};
|
||||
RuntimeInfo runtime_info{};
|
||||
std::bitset<MaxStageResources> bitset{};
|
||||
std::optional<Gcn::FetchShaderData> fetch_shader_data{};
|
||||
boost::container::small_vector<VsAttribSpecialization, 32> vs_attribs;
|
||||
@ -90,6 +90,7 @@ struct StageSpecialization {
|
||||
boost::container::small_vector<SamplerSpecialization, 16> samplers;
|
||||
Backend::Bindings start{};
|
||||
|
||||
StageSpecialization() = default;
|
||||
StageSpecialization(const Info& info_, RuntimeInfo runtime_info_, const Profile& profile_,
|
||||
Backend::Bindings start_)
|
||||
: info{&info_}, runtime_info{runtime_info_}, start{start_} {
|
||||
@ -158,7 +159,7 @@ struct StageSpecialization {
|
||||
// Initialize runtime_info fields that rely on analysis in tessellation passes
|
||||
if (info->l_stage == LogicalStage::TessellationControl ||
|
||||
info->l_stage == LogicalStage::TessellationEval) {
|
||||
Shader::TessellationDataConstantBuffer tess_constants;
|
||||
TessellationDataConstantBuffer tess_constants{};
|
||||
info->ReadTessConstantBuffer(tess_constants);
|
||||
if (info->l_stage == LogicalStage::TessellationControl) {
|
||||
runtime_info.hs_info.InitFromTessConstants(tess_constants);
|
||||
@ -192,21 +193,43 @@ struct StageSpecialization {
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] bool Valid() const {
|
||||
return info != nullptr;
|
||||
}
|
||||
|
||||
bool operator==(const StageSpecialization& other) const {
|
||||
if (start != other.start) {
|
||||
if (!Valid()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (vs_attribs != other.vs_attribs) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (runtime_info != other.runtime_info) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (fetch_shader_data != other.fetch_shader_data) {
|
||||
return false;
|
||||
}
|
||||
for (u32 i = 0; i < vs_attribs.size(); i++) {
|
||||
if (vs_attribs[i] != other.vs_attribs[i]) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (fmasks != other.fmasks) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// For VS which only generates geometry and doesn't have any inputs, its start
|
||||
// bindings still may change as they depend on previously processed FS. The check below
|
||||
// handles this case and prevents generation of redundant permutations. This is also safe
|
||||
// for other types of shaders with no bindings.
|
||||
if (bitset.none() && other.bitset.none()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (start != other.start) {
|
||||
return false;
|
||||
}
|
||||
|
||||
u32 binding{};
|
||||
for (u32 i = 0; i < buffers.size(); i++) {
|
||||
if (other.bitset[binding++] && buffers[i] != other.buffers[i]) {
|
||||
@ -218,11 +241,7 @@ struct StageSpecialization {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
for (u32 i = 0; i < fmasks.size(); i++) {
|
||||
if (other.bitset[binding++] && fmasks[i] != other.fmasks[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
for (u32 i = 0; i < samplers.size(); i++) {
|
||||
if (samplers[i] != other.samplers[i]) {
|
||||
return false;
|
||||
@ -230,6 +249,9 @@ struct StageSpecialization {
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void Serialize(Serialization::Archive& ar) const;
|
||||
bool Deserialize(Serialization::Archive& ar);
|
||||
};
|
||||
|
||||
} // namespace Shader
|
||||
|
||||
@ -655,8 +655,8 @@ Liverpool::Task Liverpool::ProcessGraphics(std::span<const u32> dcb, std::span<c
|
||||
break;
|
||||
}
|
||||
if (dma_data->src_sel == DmaDataSrc::Data && dma_data->dst_sel == DmaDataDst::Gds) {
|
||||
rasterizer->InlineData(dma_data->dst_addr_lo, &dma_data->data, sizeof(u32),
|
||||
true);
|
||||
rasterizer->FillBuffer(dma_data->dst_addr_lo, dma_data->NumBytes(),
|
||||
dma_data->data, true);
|
||||
} else if ((dma_data->src_sel == DmaDataSrc::Memory ||
|
||||
dma_data->src_sel == DmaDataSrc::MemoryUsingL2) &&
|
||||
dma_data->dst_sel == DmaDataDst::Gds) {
|
||||
@ -665,8 +665,8 @@ Liverpool::Task Liverpool::ProcessGraphics(std::span<const u32> dcb, std::span<c
|
||||
} else if (dma_data->src_sel == DmaDataSrc::Data &&
|
||||
(dma_data->dst_sel == DmaDataDst::Memory ||
|
||||
dma_data->dst_sel == DmaDataDst::MemoryUsingL2)) {
|
||||
rasterizer->InlineData(dma_data->DstAddress<VAddr>(), &dma_data->data,
|
||||
sizeof(u32), false);
|
||||
rasterizer->FillBuffer(dma_data->DstAddress<VAddr>(), dma_data->NumBytes(),
|
||||
dma_data->data, false);
|
||||
} else if (dma_data->src_sel == DmaDataSrc::Gds &&
|
||||
(dma_data->dst_sel == DmaDataDst::Memory ||
|
||||
dma_data->dst_sel == DmaDataDst::MemoryUsingL2)) {
|
||||
@ -898,7 +898,8 @@ Liverpool::Task Liverpool::ProcessCompute(std::span<const u32> acb, u32 vqid) {
|
||||
break;
|
||||
}
|
||||
if (dma_data->src_sel == DmaDataSrc::Data && dma_data->dst_sel == DmaDataDst::Gds) {
|
||||
rasterizer->InlineData(dma_data->dst_addr_lo, &dma_data->data, sizeof(u32), true);
|
||||
rasterizer->FillBuffer(dma_data->dst_addr_lo, dma_data->NumBytes(), dma_data->data,
|
||||
true);
|
||||
} else if ((dma_data->src_sel == DmaDataSrc::Memory ||
|
||||
dma_data->src_sel == DmaDataSrc::MemoryUsingL2) &&
|
||||
dma_data->dst_sel == DmaDataDst::Gds) {
|
||||
@ -907,8 +908,8 @@ Liverpool::Task Liverpool::ProcessCompute(std::span<const u32> acb, u32 vqid) {
|
||||
} else if (dma_data->src_sel == DmaDataSrc::Data &&
|
||||
(dma_data->dst_sel == DmaDataDst::Memory ||
|
||||
dma_data->dst_sel == DmaDataDst::MemoryUsingL2)) {
|
||||
rasterizer->InlineData(dma_data->DstAddress<VAddr>(), &dma_data->data, sizeof(u32),
|
||||
false);
|
||||
rasterizer->FillBuffer(dma_data->DstAddress<VAddr>(), dma_data->NumBytes(),
|
||||
dma_data->data, false);
|
||||
} else if (dma_data->src_sel == DmaDataSrc::Gds &&
|
||||
(dma_data->dst_sel == DmaDataDst::Memory ||
|
||||
dma_data->dst_sel == DmaDataDst::MemoryUsingL2)) {
|
||||
|
||||
@ -79,10 +79,10 @@ enum class NumberFormat : u32 {
|
||||
Ubscaled = 13,
|
||||
};
|
||||
|
||||
enum class NumberClass {
|
||||
Float,
|
||||
Sint,
|
||||
Uint,
|
||||
enum class NumberClass : u8 {
|
||||
Float = 0,
|
||||
Sint = 1,
|
||||
Uint = 2,
|
||||
};
|
||||
|
||||
enum class CompSwizzle : u8 {
|
||||
|
||||
@ -323,14 +323,13 @@ void BufferCache::BindIndexBuffer(u32 index_offset) {
|
||||
cmdbuf.bindIndexBuffer(vk_buffer->Handle(), offset, index_type);
|
||||
}
|
||||
|
||||
void BufferCache::InlineData(VAddr address, const void* value, u32 num_bytes, bool is_gds) {
|
||||
void BufferCache::FillBuffer(VAddr address, u32 num_bytes, u32 value, bool is_gds) {
|
||||
ASSERT_MSG(address % 4 == 0, "GDS offset must be dword aligned");
|
||||
if (!is_gds) {
|
||||
if (!memory->TryWriteBacking(std::bit_cast<void*>(address), value, num_bytes)) {
|
||||
std::memcpy(std::bit_cast<void*>(address), value, num_bytes);
|
||||
return;
|
||||
}
|
||||
if (!IsRegionRegistered(address, num_bytes)) {
|
||||
texture_cache.ClearMeta(address);
|
||||
if (!IsRegionGpuModified(address, num_bytes)) {
|
||||
u32* buffer = std::bit_cast<u32*>(address);
|
||||
std::fill(buffer, buffer + num_bytes / sizeof(u32), value);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@ -338,10 +337,10 @@ void BufferCache::InlineData(VAddr address, const void* value, u32 num_bytes, bo
|
||||
if (is_gds) {
|
||||
return &gds_buffer;
|
||||
}
|
||||
const BufferId buffer_id = FindBuffer(address, num_bytes);
|
||||
return &slot_buffers[buffer_id];
|
||||
const auto [buffer, offset] = ObtainBuffer(address, num_bytes, true);
|
||||
return buffer;
|
||||
}();
|
||||
InlineDataBuffer(*buffer, address, value, num_bytes);
|
||||
buffer->Fill(buffer->Offset(address), num_bytes, value);
|
||||
}
|
||||
|
||||
void BufferCache::CopyBuffer(VAddr dst, VAddr src, u32 num_bytes, bool dst_gds, bool src_gds) {
|
||||
@ -853,49 +852,6 @@ void BufferCache::SynchronizeBuffersInRange(VAddr device_addr, u64 size) {
|
||||
});
|
||||
}
|
||||
|
||||
void BufferCache::InlineDataBuffer(Buffer& buffer, VAddr address, const void* value,
|
||||
u32 num_bytes) {
|
||||
scheduler.EndRendering();
|
||||
const auto cmdbuf = scheduler.CommandBuffer();
|
||||
const vk::BufferMemoryBarrier2 pre_barrier = {
|
||||
.srcStageMask = vk::PipelineStageFlagBits2::eAllCommands,
|
||||
.srcAccessMask = vk::AccessFlagBits2::eMemoryRead,
|
||||
.dstStageMask = vk::PipelineStageFlagBits2::eTransfer,
|
||||
.dstAccessMask = vk::AccessFlagBits2::eTransferWrite,
|
||||
.buffer = buffer.Handle(),
|
||||
.offset = buffer.Offset(address),
|
||||
.size = num_bytes,
|
||||
};
|
||||
const vk::BufferMemoryBarrier2 post_barrier = {
|
||||
.srcStageMask = vk::PipelineStageFlagBits2::eTransfer,
|
||||
.srcAccessMask = vk::AccessFlagBits2::eTransferWrite,
|
||||
.dstStageMask = vk::PipelineStageFlagBits2::eAllCommands,
|
||||
.dstAccessMask = vk::AccessFlagBits2::eMemoryRead,
|
||||
.buffer = buffer.Handle(),
|
||||
.offset = buffer.Offset(address),
|
||||
.size = num_bytes,
|
||||
};
|
||||
cmdbuf.pipelineBarrier2(vk::DependencyInfo{
|
||||
.dependencyFlags = vk::DependencyFlagBits::eByRegion,
|
||||
.bufferMemoryBarrierCount = 1,
|
||||
.pBufferMemoryBarriers = &pre_barrier,
|
||||
});
|
||||
// vkCmdUpdateBuffer can only copy up to 65536 bytes at a time.
|
||||
static constexpr u32 UpdateBufferMaxSize = 65536;
|
||||
const auto dst_offset = buffer.Offset(address);
|
||||
for (u32 offset = 0; offset < num_bytes; offset += UpdateBufferMaxSize) {
|
||||
const auto* update_src = static_cast<const u8*>(value) + offset;
|
||||
const auto update_dst = dst_offset + offset;
|
||||
const auto update_size = std::min(num_bytes - offset, UpdateBufferMaxSize);
|
||||
cmdbuf.updateBuffer(buffer.Handle(), update_dst, update_size, update_src);
|
||||
}
|
||||
cmdbuf.pipelineBarrier2(vk::DependencyInfo{
|
||||
.dependencyFlags = vk::DependencyFlagBits::eByRegion,
|
||||
.bufferMemoryBarrierCount = 1,
|
||||
.pBufferMemoryBarriers = &post_barrier,
|
||||
});
|
||||
}
|
||||
|
||||
void BufferCache::WriteDataBuffer(Buffer& buffer, VAddr address, const void* value, u32 num_bytes) {
|
||||
vk::BufferCopy copy = {
|
||||
.srcOffset = 0,
|
||||
|
||||
@ -132,7 +132,7 @@ public:
|
||||
void BindIndexBuffer(u32 index_offset);
|
||||
|
||||
/// Writes a value to GPU buffer. (uses command buffer to temporarily store the data)
|
||||
void InlineData(VAddr address, const void* value, u32 num_bytes, bool is_gds);
|
||||
void FillBuffer(VAddr address, u32 num_bytes, u32 value, bool is_gds);
|
||||
|
||||
/// Performs buffer to buffer data copy on the GPU.
|
||||
void CopyBuffer(VAddr dst, VAddr src, u32 num_bytes, bool dst_gds, bool src_gds);
|
||||
@ -210,8 +210,6 @@ private:
|
||||
|
||||
bool SynchronizeBufferFromImage(Buffer& buffer, VAddr device_addr, u32 size);
|
||||
|
||||
void InlineDataBuffer(Buffer& buffer, VAddr address, const void* value, u32 num_bytes);
|
||||
|
||||
void WriteDataBuffer(Buffer& buffer, VAddr address, const void* value, u32 num_bytes);
|
||||
|
||||
void TouchBuffer(const Buffer& buffer);
|
||||
|
||||
264
src/video_core/cache_storage.cpp
Normal file
264
src/video_core/cache_storage.cpp
Normal file
@ -0,0 +1,264 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "common/config.h"
|
||||
#include "common/elf_info.h"
|
||||
#include "common/io_file.h"
|
||||
#include "common/polyfill_thread.h"
|
||||
#include "common/thread.h"
|
||||
|
||||
#include "video_core/cache_storage.h"
|
||||
#include "video_core/renderer_vulkan/vk_instance.h"
|
||||
#include "video_core/renderer_vulkan/vk_pipeline_cache.h"
|
||||
|
||||
#include <miniz.h>
|
||||
|
||||
#include <condition_variable>
|
||||
#include <functional>
|
||||
#include <future>
|
||||
#include <mutex>
|
||||
#include <queue>
|
||||
|
||||
namespace {
|
||||
|
||||
std::mutex submit_mutex{};
|
||||
u32 num_requests{};
|
||||
std::condition_variable_any request_cv{};
|
||||
std::queue<std::packaged_task<void()>> req_queue{};
|
||||
std::mutex m_request{};
|
||||
|
||||
mz_zip_archive zip_ar{};
|
||||
bool ar_is_read_only{true};
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace Storage {
|
||||
|
||||
void ProcessIO(const std::stop_token& stoken) {
|
||||
Common::SetCurrentThreadName("shadPS4:PipelineCacheIO");
|
||||
|
||||
while (!stoken.stop_requested()) {
|
||||
{
|
||||
std::unique_lock lk{submit_mutex};
|
||||
Common::CondvarWait(request_cv, lk, stoken, [&] { return num_requests; });
|
||||
}
|
||||
|
||||
if (stoken.stop_requested()) {
|
||||
break;
|
||||
}
|
||||
|
||||
while (num_requests) {
|
||||
std::packaged_task<void()> request{};
|
||||
{
|
||||
std::scoped_lock lock{m_request};
|
||||
if (req_queue.empty()) {
|
||||
continue;
|
||||
}
|
||||
request = std::move(req_queue.front());
|
||||
req_queue.pop();
|
||||
}
|
||||
|
||||
if (request.valid()) {
|
||||
request();
|
||||
request.get_future().wait();
|
||||
}
|
||||
|
||||
--num_requests;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
constexpr std::string GetBlobFileExtension(BlobType type) {
|
||||
switch (type) {
|
||||
case BlobType::ShaderMeta: {
|
||||
return "meta";
|
||||
}
|
||||
case BlobType::ShaderBinary: {
|
||||
return "spv";
|
||||
}
|
||||
case BlobType::PipelineKey: {
|
||||
return "key";
|
||||
}
|
||||
case BlobType::ShaderProfile: {
|
||||
return "bin";
|
||||
}
|
||||
default:
|
||||
UNREACHABLE();
|
||||
}
|
||||
}
|
||||
|
||||
void DataBase::Open() {
|
||||
if (opened) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto& game_info = Common::ElfInfo::Instance();
|
||||
|
||||
using namespace Common::FS;
|
||||
if (Config::isPipelineCacheArchived()) {
|
||||
mz_zip_zero_struct(&zip_ar);
|
||||
|
||||
cache_path = GetUserPath(PathType::CacheDir) /
|
||||
std::filesystem::path{game_info.GameSerial()}.replace_extension(".zip");
|
||||
|
||||
if (!mz_zip_reader_init_file(&zip_ar, cache_path.string().c_str(),
|
||||
MZ_ZIP_FLAG_READ_ALLOW_WRITING) ||
|
||||
!mz_zip_validate_archive(&zip_ar, 0)) {
|
||||
LOG_INFO(Render, "Cache archive {} is not found or archive is corrupted",
|
||||
cache_path.string().c_str());
|
||||
mz_zip_reader_end(&zip_ar);
|
||||
mz_zip_writer_init_file(&zip_ar, cache_path.string().c_str(), 0);
|
||||
}
|
||||
} else {
|
||||
cache_path = GetUserPath(PathType::CacheDir) / game_info.GameSerial();
|
||||
if (!std::filesystem::exists(cache_path)) {
|
||||
std::filesystem::create_directories(cache_path);
|
||||
}
|
||||
}
|
||||
|
||||
io_worker = std::jthread{ProcessIO};
|
||||
opened = true;
|
||||
}
|
||||
|
||||
void DataBase::Close() {
|
||||
if (!IsOpened()) {
|
||||
return;
|
||||
}
|
||||
|
||||
io_worker.request_stop();
|
||||
io_worker.join();
|
||||
|
||||
if (Config::isPipelineCacheArchived()) {
|
||||
mz_zip_writer_finalize_archive(&zip_ar);
|
||||
mz_zip_writer_end(&zip_ar);
|
||||
}
|
||||
|
||||
LOG_INFO(Render, "Cache dumped");
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
bool WriteVector(const BlobType type, std::filesystem::path&& path_, std::vector<T>&& v) {
|
||||
{
|
||||
auto request = std::packaged_task<void()>{[=]() {
|
||||
auto path{path_};
|
||||
path.replace_extension(GetBlobFileExtension(type));
|
||||
if (Config::isPipelineCacheArchived()) {
|
||||
ASSERT_MSG(!ar_is_read_only,
|
||||
"The archive is read-only. Did you forget to call `FinishPreload`?");
|
||||
if (!mz_zip_writer_add_mem(&zip_ar, path.string().c_str(), v.data(),
|
||||
v.size() * sizeof(T), MZ_BEST_COMPRESSION)) {
|
||||
LOG_ERROR(Render, "Failed to add {} to the archive", path.string().c_str());
|
||||
}
|
||||
} else {
|
||||
using namespace Common::FS;
|
||||
const auto file = IOFile{path, FileAccessMode::Create};
|
||||
file.Write(v);
|
||||
}
|
||||
}};
|
||||
std::scoped_lock lock{m_request};
|
||||
req_queue.emplace(std::move(request));
|
||||
}
|
||||
|
||||
std::scoped_lock lk{submit_mutex};
|
||||
++num_requests;
|
||||
request_cv.notify_one();
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void LoadVector(BlobType type, std::filesystem::path& path, std::vector<T>& v) {
|
||||
using namespace Common::FS;
|
||||
path.replace_extension(GetBlobFileExtension(type));
|
||||
if (Config::isPipelineCacheArchived()) {
|
||||
int index{-1};
|
||||
index = mz_zip_reader_locate_file(&zip_ar, path.string().c_str(), nullptr, 0);
|
||||
if (index < 0) {
|
||||
LOG_WARNING(Render, "File {} is not found in the archive", path.string().c_str());
|
||||
return;
|
||||
}
|
||||
mz_zip_archive_file_stat stat{};
|
||||
mz_zip_reader_file_stat(&zip_ar, index, &stat);
|
||||
v.resize(stat.m_uncomp_size / sizeof(T));
|
||||
mz_zip_reader_extract_to_mem(&zip_ar, index, v.data(), stat.m_uncomp_size, 0);
|
||||
} else {
|
||||
const auto file = IOFile{path, FileAccessMode::Read};
|
||||
v.resize(file.GetSize() / sizeof(T));
|
||||
file.Read(v);
|
||||
}
|
||||
}
|
||||
|
||||
bool DataBase::Save(BlobType type, const std::string& name, std::vector<u8>&& data) {
|
||||
if (!opened) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto path = Config::isPipelineCacheArchived() ? std::filesystem::path{name} : cache_path / name;
|
||||
return WriteVector(type, std::move(path), std::move(data));
|
||||
}
|
||||
|
||||
bool DataBase::Save(BlobType type, const std::string& name, std::vector<u32>&& data) {
|
||||
if (!opened) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto path = Config::isPipelineCacheArchived() ? std::filesystem::path{name} : cache_path / name;
|
||||
return WriteVector(type, std::move(path), std::move(data));
|
||||
}
|
||||
|
||||
void DataBase::Load(BlobType type, const std::string& name, std::vector<u8>& data) {
|
||||
if (!opened) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto path = Config::isPipelineCacheArchived() ? std::filesystem::path{name} : cache_path / name;
|
||||
return LoadVector(type, path, data);
|
||||
}
|
||||
|
||||
void DataBase::Load(BlobType type, const std::string& name, std::vector<u32>& data) {
|
||||
if (!opened) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto path = Config::isPipelineCacheArchived() ? std::filesystem::path{name} : cache_path / name;
|
||||
return LoadVector(type, path, data);
|
||||
}
|
||||
|
||||
void DataBase::ForEachBlob(BlobType type, const std::function<void(std::vector<u8>&& data)>& func) {
|
||||
const auto& ext = GetBlobFileExtension(type);
|
||||
if (Config::isPipelineCacheArchived()) {
|
||||
const auto num_files = mz_zip_reader_get_num_files(&zip_ar);
|
||||
for (int index = 0; index < num_files; ++index) {
|
||||
std::array<char, MZ_ZIP_MAX_ARCHIVE_FILENAME_SIZE> file_name{};
|
||||
file_name.fill(0);
|
||||
mz_zip_reader_get_filename(&zip_ar, index, file_name.data(), file_name.size());
|
||||
if (std::string{file_name.data()}.ends_with(ext)) {
|
||||
mz_zip_archive_file_stat stat{};
|
||||
mz_zip_reader_file_stat(&zip_ar, index, &stat);
|
||||
std::vector<u8> data(stat.m_uncomp_size);
|
||||
mz_zip_reader_extract_to_mem(&zip_ar, index, data.data(), data.size(), 0);
|
||||
func(std::move(data));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (const auto& file_name : std::filesystem::directory_iterator{cache_path}) {
|
||||
if (file_name.path().extension().string().ends_with(ext)) {
|
||||
using namespace Common::FS;
|
||||
const auto& file = IOFile{file_name, FileAccessMode::Read};
|
||||
if (file.IsOpen()) {
|
||||
std::vector<u8> data(file.GetSize());
|
||||
file.Read(data);
|
||||
func(std::move(data));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DataBase::FinishPreload() {
|
||||
if (Config::isPipelineCacheArchived()) {
|
||||
mz_zip_writer_init_from_reader(&zip_ar, cache_path.string().c_str());
|
||||
ar_is_read_only = false;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Storage
|
||||
50
src/video_core/cache_storage.h
Normal file
50
src/video_core/cache_storage.h
Normal file
@ -0,0 +1,50 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "common/path_util.h"
|
||||
#include "common/singleton.h"
|
||||
#include "common/types.h"
|
||||
|
||||
#include <functional>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
namespace Storage {
|
||||
|
||||
enum class BlobType : u32 {
|
||||
ShaderMeta,
|
||||
ShaderBinary,
|
||||
PipelineKey,
|
||||
ShaderProfile,
|
||||
};
|
||||
|
||||
class DataBase {
|
||||
public:
|
||||
static DataBase& Instance() {
|
||||
return *Common::Singleton<DataBase>::Instance();
|
||||
}
|
||||
|
||||
void Open();
|
||||
void Close();
|
||||
[[nodiscard]] bool IsOpened() const {
|
||||
return opened;
|
||||
}
|
||||
void FinishPreload();
|
||||
|
||||
bool Save(BlobType type, const std::string& name, std::vector<u8>&& data);
|
||||
bool Save(BlobType type, const std::string& name, std::vector<u32>&& data);
|
||||
|
||||
void Load(BlobType type, const std::string& name, std::vector<u8>& data);
|
||||
void Load(BlobType type, const std::string& name, std::vector<u32>& data);
|
||||
|
||||
void ForEachBlob(BlobType type, const std::function<void(std::vector<u8>&& data)>& func);
|
||||
|
||||
private:
|
||||
std::jthread io_worker{};
|
||||
std::filesystem::path cache_path{};
|
||||
bool opened{};
|
||||
};
|
||||
|
||||
} // namespace Storage
|
||||
@ -13,7 +13,8 @@ namespace Vulkan {
|
||||
ComputePipeline::ComputePipeline(const Instance& instance, Scheduler& scheduler,
|
||||
DescriptorHeap& desc_heap, const Shader::Profile& profile,
|
||||
vk::PipelineCache pipeline_cache, ComputePipelineKey compute_key_,
|
||||
const Shader::Info& info_, vk::ShaderModule module)
|
||||
const Shader::Info& info_, vk::ShaderModule module,
|
||||
SerializationSupport& sdata, bool preloading /*=false*/)
|
||||
: Pipeline{instance, scheduler, desc_heap, profile, pipeline_cache, true},
|
||||
compute_key{compute_key_} {
|
||||
auto& info = stages[int(Shader::LogicalStage::Compute)];
|
||||
@ -29,7 +30,11 @@ ComputePipeline::ComputePipeline(const Instance& instance, Scheduler& scheduler,
|
||||
u32 binding{};
|
||||
boost::container::small_vector<vk::DescriptorSetLayoutBinding, 32> bindings;
|
||||
for (const auto& buffer : info->buffers) {
|
||||
const auto sharp = buffer.GetSharp(*info);
|
||||
// During deserialization, we don't have access to the UD to fetch sharp data. To address
|
||||
// this properly we need to track shaprs or portion of them in `sdata`, but since we're
|
||||
// interested only in "is storage" flag (which is not even effective atm), we can take a
|
||||
// shortcut there.
|
||||
const auto sharp = preloading ? AmdGpu::Buffer{} : buffer.GetSharp(*info);
|
||||
bindings.push_back({
|
||||
.binding = binding++,
|
||||
.descriptorType = buffer.IsStorage(sharp) ? vk::DescriptorType::eStorageBuffer
|
||||
|
||||
@ -11,6 +11,10 @@ class BufferCache;
|
||||
class TextureCache;
|
||||
} // namespace VideoCore
|
||||
|
||||
namespace Serialization {
|
||||
struct Archive;
|
||||
}
|
||||
|
||||
namespace Vulkan {
|
||||
|
||||
class Instance;
|
||||
@ -26,14 +30,24 @@ struct ComputePipelineKey {
|
||||
friend bool operator!=(const ComputePipelineKey& lhs, const ComputePipelineKey& rhs) {
|
||||
return !(lhs == rhs);
|
||||
}
|
||||
|
||||
void Serialize(Serialization::Archive& ar) const;
|
||||
bool Deserialize(Serialization::Archive& ar);
|
||||
};
|
||||
|
||||
class ComputePipeline : public Pipeline {
|
||||
public:
|
||||
struct SerializationSupport {
|
||||
u32 dummy{};
|
||||
|
||||
void Serialize(Serialization::Archive& ar) const;
|
||||
bool Deserialize(Serialization::Archive& ar);
|
||||
};
|
||||
|
||||
ComputePipeline(const Instance& instance, Scheduler& scheduler, DescriptorHeap& desc_heap,
|
||||
const Shader::Profile& profile, vk::PipelineCache pipeline_cache,
|
||||
ComputePipelineKey compute_key, const Shader::Info& info,
|
||||
vk::ShaderModule module);
|
||||
vk::ShaderModule module, SerializationSupport& sdata, bool preloading);
|
||||
~ComputePipeline();
|
||||
|
||||
private:
|
||||
|
||||
@ -41,12 +41,12 @@ GraphicsPipeline::GraphicsPipeline(
|
||||
vk::PipelineCache pipeline_cache, std::span<const Shader::Info*, MaxShaderStages> infos,
|
||||
std::span<const Shader::RuntimeInfo, MaxShaderStages> runtime_infos,
|
||||
std::optional<const Shader::Gcn::FetchShaderData> fetch_shader_,
|
||||
std::span<const vk::ShaderModule> modules)
|
||||
std::span<const vk::ShaderModule> modules, SerializationSupport& sdata, bool preloading)
|
||||
: Pipeline{instance, scheduler, desc_heap, profile, pipeline_cache}, key{key_},
|
||||
fetch_shader{std::move(fetch_shader_)} {
|
||||
const vk::Device device = instance.GetDevice();
|
||||
std::ranges::copy(infos, stages.begin());
|
||||
BuildDescSetLayout();
|
||||
BuildDescSetLayout(preloading);
|
||||
const auto debug_str = GetDebugString();
|
||||
|
||||
const vk::PushConstantRange push_constants = {
|
||||
@ -68,41 +68,35 @@ GraphicsPipeline::GraphicsPipeline(
|
||||
pipeline_layout = std::move(layout);
|
||||
SetObjectName(device, *pipeline_layout, "Graphics PipelineLayout {}", debug_str);
|
||||
|
||||
VertexInputs<vk::VertexInputAttributeDescription> vertex_attributes;
|
||||
VertexInputs<vk::VertexInputBindingDescription> vertex_bindings;
|
||||
VertexInputs<vk::VertexInputBindingDivisorDescriptionEXT> divisors;
|
||||
VertexInputs<AmdGpu::Buffer> guest_buffers;
|
||||
if (!instance.IsVertexInputDynamicState()) {
|
||||
const auto& vs_info = runtime_infos[u32(Shader::LogicalStage::Vertex)].vs_info;
|
||||
GetVertexInputs(vertex_attributes, vertex_bindings, divisors, guest_buffers,
|
||||
vs_info.step_rate_0, vs_info.step_rate_1);
|
||||
if (!preloading) {
|
||||
VertexInputs<AmdGpu::Buffer> guest_buffers;
|
||||
if (!instance.IsVertexInputDynamicState()) {
|
||||
const auto& vs_info = runtime_infos[u32(Shader::LogicalStage::Vertex)].vs_info;
|
||||
GetVertexInputs(sdata.vertex_attributes, sdata.vertex_bindings, sdata.divisors,
|
||||
guest_buffers, vs_info.step_rate_0, vs_info.step_rate_1);
|
||||
}
|
||||
}
|
||||
|
||||
const vk::PipelineVertexInputDivisorStateCreateInfo divisor_state = {
|
||||
.vertexBindingDivisorCount = static_cast<u32>(divisors.size()),
|
||||
.pVertexBindingDivisors = divisors.data(),
|
||||
.vertexBindingDivisorCount = static_cast<u32>(sdata.divisors.size()),
|
||||
.pVertexBindingDivisors = sdata.divisors.data(),
|
||||
};
|
||||
|
||||
const vk::PipelineVertexInputStateCreateInfo vertex_input_info = {
|
||||
.pNext = divisors.empty() ? nullptr : &divisor_state,
|
||||
.vertexBindingDescriptionCount = static_cast<u32>(vertex_bindings.size()),
|
||||
.pVertexBindingDescriptions = vertex_bindings.data(),
|
||||
.vertexAttributeDescriptionCount = static_cast<u32>(vertex_attributes.size()),
|
||||
.pVertexAttributeDescriptions = vertex_attributes.data(),
|
||||
.pNext = sdata.divisors.empty() ? nullptr : &divisor_state,
|
||||
.vertexBindingDescriptionCount = static_cast<u32>(sdata.vertex_bindings.size()),
|
||||
.pVertexBindingDescriptions = sdata.vertex_bindings.data(),
|
||||
.vertexAttributeDescriptionCount = static_cast<u32>(sdata.vertex_attributes.size()),
|
||||
.pVertexAttributeDescriptions = sdata.vertex_attributes.data(),
|
||||
};
|
||||
|
||||
const auto topology = LiverpoolToVK::PrimitiveType(key.prim_type);
|
||||
const vk::PipelineInputAssemblyStateCreateInfo input_assembly = {
|
||||
.topology = topology,
|
||||
// Avoid warning spam on all pipelines about unsupported restart disable, if not supported.
|
||||
// However, must be false for list topologies to avoid validation errors.
|
||||
.primitiveRestartEnable =
|
||||
!instance.IsPrimitiveRestartDisableSupported() && !IsPrimitiveTopologyList(topology),
|
||||
};
|
||||
|
||||
const bool is_rect_list = key.prim_type == AmdGpu::PrimitiveType::RectList;
|
||||
const bool is_quad_list = key.prim_type == AmdGpu::PrimitiveType::QuadList;
|
||||
const auto& fs_info = runtime_infos[u32(Shader::LogicalStage::Fragment)].fs_info;
|
||||
const vk::PipelineTessellationStateCreateInfo tessellation_state = {
|
||||
.patchControlPoints = is_rect_list ? 3U : (is_quad_list ? 4U : key.patch_control_points),
|
||||
};
|
||||
@ -132,12 +126,15 @@ GraphicsPipeline::GraphicsPipeline(
|
||||
raster_chain.unlink<vk::PipelineRasterizationDepthClipStateCreateInfoEXT>();
|
||||
}
|
||||
|
||||
const vk::PipelineMultisampleStateCreateInfo multisampling = {
|
||||
.rasterizationSamples = LiverpoolToVK::NumSamples(
|
||||
key.num_samples, instance.GetColorSampleCounts() & instance.GetDepthSampleCounts()),
|
||||
.sampleShadingEnable =
|
||||
fs_info.addr_flags.persp_sample_ena || fs_info.addr_flags.linear_sample_ena,
|
||||
};
|
||||
if (!preloading) {
|
||||
const auto& fs_info = runtime_infos[u32(Shader::LogicalStage::Fragment)].fs_info;
|
||||
sdata.multisampling = {
|
||||
.rasterizationSamples = LiverpoolToVK::NumSamples(
|
||||
key.num_samples, instance.GetColorSampleCounts() & instance.GetDepthSampleCounts()),
|
||||
.sampleShadingEnable =
|
||||
fs_info.addr_flags.persp_sample_ena || fs_info.addr_flags.linear_sample_ena,
|
||||
};
|
||||
}
|
||||
|
||||
const vk::PipelineViewportDepthClipControlCreateInfoEXT clip_control = {
|
||||
.negativeOneToOne = key.clip_space == AmdGpu::ClipSpace::MinusWToW,
|
||||
@ -156,12 +153,9 @@ GraphicsPipeline::GraphicsPipeline(
|
||||
vk::DynamicState::eStencilCompareMask, vk::DynamicState::eStencilWriteMask,
|
||||
vk::DynamicState::eStencilOp, vk::DynamicState::eCullMode,
|
||||
vk::DynamicState::eFrontFace, vk::DynamicState::eRasterizerDiscardEnable,
|
||||
vk::DynamicState::eLineWidth,
|
||||
vk::DynamicState::eLineWidth, vk::DynamicState::ePrimitiveRestartEnable,
|
||||
};
|
||||
|
||||
if (instance.IsPrimitiveRestartDisableSupported()) {
|
||||
dynamic_states.push_back(vk::DynamicState::ePrimitiveRestartEnable);
|
||||
}
|
||||
if (instance.IsDepthBoundsSupported()) {
|
||||
dynamic_states.push_back(vk::DynamicState::eDepthBoundsTestEnable);
|
||||
dynamic_states.push_back(vk::DynamicState::eDepthBounds);
|
||||
@ -171,7 +165,7 @@ GraphicsPipeline::GraphicsPipeline(
|
||||
}
|
||||
if (instance.IsVertexInputDynamicState()) {
|
||||
dynamic_states.push_back(vk::DynamicState::eVertexInputEXT);
|
||||
} else if (!vertex_bindings.empty()) {
|
||||
} else if (!sdata.vertex_bindings.empty()) {
|
||||
dynamic_states.push_back(vk::DynamicState::eVertexInputBindingStride);
|
||||
}
|
||||
|
||||
@ -207,10 +201,13 @@ GraphicsPipeline::GraphicsPipeline(
|
||||
});
|
||||
} else if (is_rect_list || is_quad_list) {
|
||||
const auto type = is_quad_list ? AuxShaderType::QuadListTCS : AuxShaderType::RectListTCS;
|
||||
auto tcs = Shader::Backend::SPIRV::EmitAuxilaryTessShader(type, fs_info);
|
||||
if (!preloading) {
|
||||
const auto& fs_info = runtime_infos[u32(Shader::LogicalStage::Fragment)].fs_info;
|
||||
sdata.tcs = Shader::Backend::SPIRV::EmitAuxilaryTessShader(type, fs_info);
|
||||
}
|
||||
shader_stages.emplace_back(vk::PipelineShaderStageCreateInfo{
|
||||
.stage = vk::ShaderStageFlagBits::eTessellationControl,
|
||||
.module = CompileSPV(tcs, instance.GetDevice()),
|
||||
.module = CompileSPV(sdata.tcs, instance.GetDevice()),
|
||||
.pName = "main",
|
||||
});
|
||||
}
|
||||
@ -222,11 +219,14 @@ GraphicsPipeline::GraphicsPipeline(
|
||||
.pName = "main",
|
||||
});
|
||||
} else if (is_rect_list || is_quad_list) {
|
||||
auto tes =
|
||||
Shader::Backend::SPIRV::EmitAuxilaryTessShader(AuxShaderType::PassthroughTES, fs_info);
|
||||
if (!preloading) {
|
||||
const auto& fs_info = runtime_infos[u32(Shader::LogicalStage::Fragment)].fs_info;
|
||||
sdata.tes = Shader::Backend::SPIRV::EmitAuxilaryTessShader(
|
||||
AuxShaderType::PassthroughTES, fs_info);
|
||||
}
|
||||
shader_stages.emplace_back(vk::PipelineShaderStageCreateInfo{
|
||||
.stage = vk::ShaderStageFlagBits::eTessellationEvaluation,
|
||||
.module = CompileSPV(tes, instance.GetDevice()),
|
||||
.module = CompileSPV(sdata.tes, instance.GetDevice()),
|
||||
.pName = "main",
|
||||
});
|
||||
}
|
||||
@ -367,7 +367,7 @@ GraphicsPipeline::GraphicsPipeline(
|
||||
.pTessellationState = &tessellation_state,
|
||||
.pViewportState = &viewport_info,
|
||||
.pRasterizationState = &raster_chain.get(),
|
||||
.pMultisampleState = &multisampling,
|
||||
.pMultisampleState = &sdata.multisampling,
|
||||
.pColorBlendState = &color_blending,
|
||||
.pDynamicState = &dynamic_info,
|
||||
.layout = *pipeline_layout,
|
||||
@ -435,7 +435,7 @@ template void GraphicsPipeline::GetVertexInputs(
|
||||
VertexInputs<vk::VertexInputBindingDivisorDescriptionEXT>& divisors,
|
||||
VertexInputs<AmdGpu::Buffer>& guest_buffers, u32 step_rate_0, u32 step_rate_1) const;
|
||||
|
||||
void GraphicsPipeline::BuildDescSetLayout() {
|
||||
void GraphicsPipeline::BuildDescSetLayout(bool preloading) {
|
||||
boost::container::small_vector<vk::DescriptorSetLayoutBinding, 32> bindings;
|
||||
u32 binding{};
|
||||
|
||||
@ -445,7 +445,9 @@ void GraphicsPipeline::BuildDescSetLayout() {
|
||||
}
|
||||
const auto stage_bit = LogicalStageToStageBit[u32(stage->l_stage)];
|
||||
for (const auto& buffer : stage->buffers) {
|
||||
const auto sharp = buffer.GetSharp(*stage);
|
||||
const auto sharp =
|
||||
preloading ? AmdGpu::Buffer{}
|
||||
: buffer.GetSharp(*stage); // See for the comment in compute PL creation
|
||||
bindings.push_back({
|
||||
.binding = binding++,
|
||||
.descriptorType = buffer.IsStorage(sharp) ? vk::DescriptorType::eStorageBuffer
|
||||
|
||||
@ -63,17 +63,33 @@ struct GraphicsPipelineKey {
|
||||
bool operator==(const GraphicsPipelineKey& key) const noexcept {
|
||||
return std::memcmp(this, &key, sizeof(key)) == 0;
|
||||
}
|
||||
|
||||
void Serialize(Serialization::Archive& ar) const;
|
||||
bool Deserialize(Serialization::Archive& ar);
|
||||
};
|
||||
|
||||
class GraphicsPipeline : public Pipeline {
|
||||
public:
|
||||
struct SerializationSupport {
|
||||
VertexInputs<vk::VertexInputAttributeDescription> vertex_attributes{};
|
||||
VertexInputs<vk::VertexInputBindingDescription> vertex_bindings{};
|
||||
VertexInputs<vk::VertexInputBindingDivisorDescriptionEXT> divisors{};
|
||||
vk::PipelineMultisampleStateCreateInfo multisampling{};
|
||||
std::vector<u32> tcs{};
|
||||
std::vector<u32> tes{};
|
||||
|
||||
void Serialize(Serialization::Archive& ar) const;
|
||||
bool Deserialize(Serialization::Archive& ar);
|
||||
};
|
||||
|
||||
GraphicsPipeline(const Instance& instance, Scheduler& scheduler, DescriptorHeap& desc_heap,
|
||||
const Shader::Profile& profile, const GraphicsPipelineKey& key,
|
||||
vk::PipelineCache pipeline_cache,
|
||||
std::span<const Shader::Info*, MaxShaderStages> stages,
|
||||
std::span<const Shader::RuntimeInfo, MaxShaderStages> runtime_infos,
|
||||
std::optional<const Shader::Gcn::FetchShaderData> fetch_shader,
|
||||
std::span<const vk::ShaderModule> modules);
|
||||
std::span<const vk::ShaderModule> modules, SerializationSupport& sdata,
|
||||
bool preloading);
|
||||
~GraphicsPipeline();
|
||||
|
||||
const std::optional<const Shader::Gcn::FetchShaderData>& GetFetchShader() const noexcept {
|
||||
@ -92,7 +108,7 @@ public:
|
||||
u32 step_rate_1) const;
|
||||
|
||||
private:
|
||||
void BuildDescSetLayout();
|
||||
void BuildDescSetLayout(bool preloading);
|
||||
|
||||
private:
|
||||
GraphicsPipelineKey key;
|
||||
|
||||
@ -411,11 +411,6 @@ public:
|
||||
properties.limits.framebufferStencilSampleCounts;
|
||||
}
|
||||
|
||||
/// Returns whether disabling primitive restart is supported.
|
||||
bool IsPrimitiveRestartDisableSupported() const {
|
||||
return driver_id != vk::DriverId::eMoltenvk;
|
||||
}
|
||||
|
||||
/// Returns true if logic ops are supported by the device.
|
||||
bool IsLogicOpSupported() const {
|
||||
return features.logicOp;
|
||||
|
||||
@ -13,9 +13,10 @@
|
||||
#include "shader_recompiler/recompiler.h"
|
||||
#include "shader_recompiler/runtime_info.h"
|
||||
#include "video_core/amdgpu/liverpool.h"
|
||||
#include "video_core/cache_storage.h"
|
||||
#include "video_core/renderer_vulkan/liverpool_to_vk.h"
|
||||
#include "video_core/renderer_vulkan/vk_instance.h"
|
||||
#include "video_core/renderer_vulkan/vk_pipeline_cache.h"
|
||||
#include "video_core/renderer_vulkan/vk_pipeline_serialization.h"
|
||||
#include "video_core/renderer_vulkan/vk_scheduler.h"
|
||||
#include "video_core/renderer_vulkan/vk_shader_util.h"
|
||||
|
||||
@ -223,6 +224,13 @@ PipelineCache::PipelineCache(const Instance& instance_, Scheduler& scheduler_,
|
||||
desc_heap{instance, scheduler.GetMasterSemaphore(), DescriptorHeapSizes} {
|
||||
const auto& vk12_props = instance.GetVk12Properties();
|
||||
profile = Shader::Profile{
|
||||
// When binding a UBO, we calculate its size considering the offset in the larger buffer
|
||||
// cache underlying resource. In some cases, it may produce sizes exceeding the system
|
||||
// maximum allowed UBO range, so we need to reduce the threshold to prevent issues.
|
||||
.max_ubo_size = instance.UniformMaxSize() - instance.UniformMinAlignment(),
|
||||
.max_viewport_width = instance.GetMaxViewportWidth(),
|
||||
.max_viewport_height = instance.GetMaxViewportHeight(),
|
||||
.max_shared_memory_size = instance.MaxComputeSharedMemorySize(),
|
||||
.supported_spirv = SpirvVersion1_6,
|
||||
.subgroup_size = instance.SubgroupSize(),
|
||||
.support_int8 = instance.IsShaderInt8Supported(),
|
||||
@ -258,14 +266,10 @@ PipelineCache::PipelineCache(const Instance& instance_, Scheduler& scheduler_,
|
||||
instance.GetDriverID() == vk::DriverId::eMoltenvk,
|
||||
.needs_buffer_offsets = instance.StorageMinAlignment() > 4,
|
||||
.needs_unorm_fixup = instance.GetDriverID() == vk::DriverId::eMoltenvk,
|
||||
// When binding a UBO, we calculate its size considering the offset in the larger buffer
|
||||
// cache underlying resource. In some cases, it may produce sizes exceeding the system
|
||||
// maximum allowed UBO range, so we need to reduce the threshold to prevent issues.
|
||||
.max_ubo_size = instance.UniformMaxSize() - instance.UniformMinAlignment(),
|
||||
.max_viewport_width = instance.GetMaxViewportWidth(),
|
||||
.max_viewport_height = instance.GetMaxViewportHeight(),
|
||||
.max_shared_memory_size = instance.MaxComputeSharedMemorySize(),
|
||||
};
|
||||
|
||||
WarmUp();
|
||||
|
||||
auto [cache_result, cache] = instance.GetDevice().createPipelineCacheUnique({});
|
||||
ASSERT_MSG(cache_result == vk::Result::eSuccess, "Failed to create pipeline cache: {}",
|
||||
vk::to_string(cache_result));
|
||||
@ -283,9 +287,14 @@ const GraphicsPipeline* PipelineCache::GetGraphicsPipeline() {
|
||||
const auto pipeline_hash = std::hash<GraphicsPipelineKey>{}(graphics_key);
|
||||
LOG_INFO(Render_Vulkan, "Compiling graphics pipeline {:#x}", pipeline_hash);
|
||||
|
||||
it.value() = std::make_unique<GraphicsPipeline>(instance, scheduler, desc_heap, profile,
|
||||
graphics_key, *pipeline_cache, infos,
|
||||
runtime_infos, fetch_shader, modules);
|
||||
GraphicsPipeline::SerializationSupport sdata{};
|
||||
it.value() = std::make_unique<GraphicsPipeline>(
|
||||
instance, scheduler, desc_heap, profile, graphics_key, *pipeline_cache, infos,
|
||||
runtime_infos, fetch_shader, modules, sdata, false);
|
||||
|
||||
RegisterPipelineData(graphics_key, pipeline_hash, sdata);
|
||||
++num_new_pipelines;
|
||||
|
||||
if (Config::collectShadersForDebug()) {
|
||||
for (auto stage = 0; stage < MaxShaderStages; ++stage) {
|
||||
if (infos[stage]) {
|
||||
@ -294,6 +303,7 @@ const GraphicsPipeline* PipelineCache::GetGraphicsPipeline() {
|
||||
}
|
||||
}
|
||||
}
|
||||
fetch_shader.reset();
|
||||
}
|
||||
return it->second.get();
|
||||
}
|
||||
@ -307,9 +317,13 @@ const ComputePipeline* PipelineCache::GetComputePipeline() {
|
||||
const auto pipeline_hash = std::hash<ComputePipelineKey>{}(compute_key);
|
||||
LOG_INFO(Render_Vulkan, "Compiling compute pipeline {:#x}", pipeline_hash);
|
||||
|
||||
it.value() =
|
||||
std::make_unique<ComputePipeline>(instance, scheduler, desc_heap, profile,
|
||||
*pipeline_cache, compute_key, *infos[0], modules[0]);
|
||||
ComputePipeline::SerializationSupport sdata{};
|
||||
it.value() = std::make_unique<ComputePipeline>(instance, scheduler, desc_heap, profile,
|
||||
*pipeline_cache, compute_key, *infos[0],
|
||||
modules[0], sdata, false);
|
||||
RegisterPipelineData(compute_key, sdata);
|
||||
++num_new_pipelines;
|
||||
|
||||
if (Config::collectShadersForDebug()) {
|
||||
auto& m = modules[0];
|
||||
module_related_pipelines[m].emplace_back(compute_key);
|
||||
@ -445,6 +459,7 @@ bool PipelineCache::RefreshGraphicsStages() {
|
||||
};
|
||||
|
||||
infos.fill(nullptr);
|
||||
modules.fill(nullptr);
|
||||
bind_stage(Stage::Fragment, LogicalStage::Fragment);
|
||||
|
||||
const auto* fs_info = infos[static_cast<u32>(LogicalStage::Fragment)];
|
||||
@ -515,7 +530,7 @@ bool PipelineCache::RefreshComputeKey() {
|
||||
}
|
||||
|
||||
vk::ShaderModule PipelineCache::CompileModule(Shader::Info& info, Shader::RuntimeInfo& runtime_info,
|
||||
std::span<const u32> code, size_t perm_idx,
|
||||
const std::span<const u32>& code, size_t perm_idx,
|
||||
Shader::Backend::Bindings& binding) {
|
||||
LOG_INFO(Render_Vulkan, "Compiling {} shader {:#x} {}", info.stage, info.pgm_hash,
|
||||
perm_idx != 0 ? "(permutation)" : "");
|
||||
@ -536,6 +551,8 @@ vk::ShaderModule PipelineCache::CompileModule(Shader::Info& info, Shader::Runtim
|
||||
module = CompileSPV(spv, instance.GetDevice());
|
||||
}
|
||||
|
||||
RegisterShaderBinary(std::move(spv), info.pgm_hash, perm_idx);
|
||||
|
||||
const auto name = GetShaderName(info.stage, info.pgm_hash, perm_idx);
|
||||
Vulkan::SetObjectName(instance.GetDevice(), module, name);
|
||||
if (Config::collectShadersForDebug()) {
|
||||
@ -546,7 +563,7 @@ vk::ShaderModule PipelineCache::CompileModule(Shader::Info& info, Shader::Runtim
|
||||
}
|
||||
|
||||
PipelineCache::Result PipelineCache::GetProgram(Stage stage, LogicalStage l_stage,
|
||||
Shader::ShaderParams params,
|
||||
const Shader::ShaderParams& params,
|
||||
Shader::Backend::Bindings& binding) {
|
||||
auto runtime_info = BuildRuntimeInfo(stage, l_stage);
|
||||
auto [it_pgm, new_program] = program_cache.try_emplace(params.hash);
|
||||
@ -555,32 +572,42 @@ PipelineCache::Result PipelineCache::GetProgram(Stage stage, LogicalStage l_stag
|
||||
auto& program = it_pgm.value();
|
||||
auto start = binding;
|
||||
const auto module = CompileModule(program->info, runtime_info, params.code, 0, binding);
|
||||
const auto spec = Shader::StageSpecialization(program->info, runtime_info, profile, start);
|
||||
auto spec = Shader::StageSpecialization(program->info, runtime_info, profile, start);
|
||||
const auto perm_hash = HashCombine(params.hash, 0);
|
||||
|
||||
RegisterShaderMeta(program->info, spec.fetch_shader_data, spec, perm_hash, 0);
|
||||
program->AddPermut(module, std::move(spec));
|
||||
return std::make_tuple(&program->info, module, spec.fetch_shader_data,
|
||||
HashCombine(params.hash, 0));
|
||||
return std::make_tuple(&program->info, module, program->modules[0].spec.fetch_shader_data,
|
||||
perm_hash);
|
||||
}
|
||||
it_pgm.value()->info.user_data = params.user_data;
|
||||
|
||||
auto& program = it_pgm.value();
|
||||
auto& info = program->info;
|
||||
info.pgm_base = params.Base(); // Needs to be actualized for inline cbuffer address fixup
|
||||
info.user_data = params.user_data;
|
||||
info.RefreshFlatBuf();
|
||||
const auto spec = Shader::StageSpecialization(info, runtime_info, profile, binding);
|
||||
auto spec = Shader::StageSpecialization(info, runtime_info, profile, binding);
|
||||
|
||||
size_t perm_idx = program->modules.size();
|
||||
u64 perm_hash = HashCombine(params.hash, perm_idx);
|
||||
|
||||
vk::ShaderModule module{};
|
||||
|
||||
const auto it = std::ranges::find(program->modules, spec, &Program::Module::spec);
|
||||
if (it == program->modules.end()) {
|
||||
auto new_info = Shader::Info(stage, l_stage, params);
|
||||
module = CompileModule(new_info, runtime_info, params.code, perm_idx, binding);
|
||||
|
||||
RegisterShaderMeta(info, spec.fetch_shader_data, spec, perm_hash, perm_idx);
|
||||
program->AddPermut(module, std::move(spec));
|
||||
} else {
|
||||
info.AddBindings(binding);
|
||||
module = it->module;
|
||||
perm_idx = std::distance(program->modules.begin(), it);
|
||||
perm_hash = HashCombine(params.hash, perm_idx);
|
||||
}
|
||||
return std::make_tuple(&info, module, spec.fetch_shader_data,
|
||||
HashCombine(params.hash, perm_idx));
|
||||
return std::make_tuple(&program->info, module,
|
||||
program->modules[perm_idx].spec.fetch_shader_data, perm_hash);
|
||||
}
|
||||
|
||||
std::optional<vk::ShaderModule> PipelineCache::ReplaceShader(vk::ShaderModule module,
|
||||
@ -654,5 +681,4 @@ std::optional<std::vector<u32>> PipelineCache::GetShaderPatch(u64 hash, Shader::
|
||||
file.Read(code);
|
||||
return code;
|
||||
}
|
||||
|
||||
} // namespace Vulkan
|
||||
|
||||
@ -23,6 +23,10 @@ namespace AmdGpu {
|
||||
class Liverpool;
|
||||
}
|
||||
|
||||
namespace Serialization {
|
||||
struct Archive;
|
||||
}
|
||||
|
||||
namespace Shader {
|
||||
struct Info;
|
||||
}
|
||||
@ -38,17 +42,25 @@ struct Program {
|
||||
vk::ShaderModule module;
|
||||
Shader::StageSpecialization spec;
|
||||
};
|
||||
using ModuleList = boost::container::small_vector<Module, 8>;
|
||||
static constexpr size_t MaxPermutations = 8;
|
||||
using ModuleList = boost::container::small_vector<Module, MaxPermutations>;
|
||||
|
||||
Shader::Info info;
|
||||
ModuleList modules;
|
||||
ModuleList modules{};
|
||||
|
||||
explicit Program(Shader::Stage stage, Shader::LogicalStage l_stage, Shader::ShaderParams params)
|
||||
Program() = default;
|
||||
Program(Shader::Stage stage, Shader::LogicalStage l_stage, Shader::ShaderParams params)
|
||||
: info{stage, l_stage, params} {}
|
||||
|
||||
void AddPermut(vk::ShaderModule module, const Shader::StageSpecialization&& spec) {
|
||||
void AddPermut(vk::ShaderModule module, Shader::StageSpecialization&& spec) {
|
||||
modules.emplace_back(module, std::move(spec));
|
||||
}
|
||||
|
||||
void InsertPermut(vk::ShaderModule module, Shader::StageSpecialization&& spec,
|
||||
size_t perm_idx) {
|
||||
modules.resize(std::max(modules.size(), perm_idx + 1)); // <-- beware of realloc
|
||||
modules[perm_idx] = {module, std::move(spec)};
|
||||
}
|
||||
};
|
||||
|
||||
class PipelineCache {
|
||||
@ -57,6 +69,13 @@ public:
|
||||
AmdGpu::Liverpool* liverpool);
|
||||
~PipelineCache();
|
||||
|
||||
void WarmUp();
|
||||
void Sync();
|
||||
|
||||
bool LoadComputePipeline(Serialization::Archive& ar);
|
||||
bool LoadGraphicsPipeline(Serialization::Archive& ar);
|
||||
bool LoadPipelineStage(Serialization::Archive& ar, size_t stage);
|
||||
|
||||
const GraphicsPipeline* GetGraphicsPipeline();
|
||||
|
||||
const ComputePipeline* GetComputePipeline();
|
||||
@ -64,7 +83,7 @@ public:
|
||||
using Result = std::tuple<const Shader::Info*, vk::ShaderModule,
|
||||
std::optional<Shader::Gcn::FetchShaderData>, u64>;
|
||||
Result GetProgram(Shader::Stage stage, Shader::LogicalStage l_stage,
|
||||
Shader::ShaderParams params, Shader::Backend::Bindings& binding);
|
||||
const Shader::ShaderParams& params, Shader::Backend::Bindings& binding);
|
||||
|
||||
std::optional<vk::ShaderModule> ReplaceShader(vk::ShaderModule module,
|
||||
std::span<const u32> spv_code);
|
||||
@ -86,10 +105,14 @@ private:
|
||||
std::optional<std::vector<u32>> GetShaderPatch(u64 hash, Shader::Stage stage, size_t perm_idx,
|
||||
std::string_view ext);
|
||||
vk::ShaderModule CompileModule(Shader::Info& info, Shader::RuntimeInfo& runtime_info,
|
||||
std::span<const u32> code, size_t perm_idx,
|
||||
const std::span<const u32>& code, size_t perm_idx,
|
||||
Shader::Backend::Bindings& binding);
|
||||
const Shader::RuntimeInfo& BuildRuntimeInfo(Shader::Stage stage, Shader::LogicalStage l_stage);
|
||||
|
||||
[[nodiscard]] bool IsPipelineCacheDirty() const {
|
||||
return num_new_pipelines > 0;
|
||||
}
|
||||
|
||||
private:
|
||||
const Instance& instance;
|
||||
Scheduler& scheduler;
|
||||
@ -108,6 +131,7 @@ private:
|
||||
std::optional<Shader::Gcn::FetchShaderData> fetch_shader{};
|
||||
GraphicsPipelineKey graphics_key{};
|
||||
ComputePipelineKey compute_key{};
|
||||
u32 num_new_pipelines{}; // new pipelines added to the cache since the game start
|
||||
|
||||
// Only if Config::collectShadersForDebug()
|
||||
tsl::robin_map<vk::ShaderModule,
|
||||
|
||||
480
src/video_core/renderer_vulkan/vk_pipeline_serialization.cpp
Normal file
480
src/video_core/renderer_vulkan/vk_pipeline_serialization.cpp
Normal file
@ -0,0 +1,480 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "common/config.h"
|
||||
#include "common/serdes.h"
|
||||
#include "shader_recompiler/frontend/fetch_shader.h"
|
||||
#include "shader_recompiler/info.h"
|
||||
#include "video_core/cache_storage.h"
|
||||
#include "video_core/renderer_vulkan/vk_instance.h"
|
||||
#include "video_core/renderer_vulkan/vk_pipeline_cache.h"
|
||||
#include "video_core/renderer_vulkan/vk_shader_util.h"
|
||||
|
||||
namespace Serialization {
|
||||
/* You should increment versions below once corresponding serialization scheme is changed. */
|
||||
static constexpr u32 ShaderBinaryVersion = 1u;
|
||||
static constexpr u32 ShaderMetaVersion = 1u;
|
||||
static constexpr u32 PipelineKeyVersion = 1u;
|
||||
} // namespace Serialization
|
||||
|
||||
namespace Vulkan {
|
||||
|
||||
void RegisterPipelineData(const ComputePipelineKey& key,
|
||||
ComputePipeline::SerializationSupport& sdata) {
|
||||
if (!Storage::DataBase::Instance().IsOpened()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Serialization::Archive ar{};
|
||||
Serialization::Writer pldata{ar};
|
||||
|
||||
pldata.Write(Serialization::PipelineKeyVersion);
|
||||
pldata.Write(u32{1}); // compute
|
||||
|
||||
key.Serialize(ar);
|
||||
sdata.Serialize(ar);
|
||||
|
||||
Storage::DataBase::Instance().Save(Storage::BlobType::PipelineKey,
|
||||
fmt::format("c_{:#018x}", key.value), ar.TakeOff());
|
||||
}
|
||||
|
||||
void RegisterPipelineData(const GraphicsPipelineKey& key, u64 hash,
|
||||
GraphicsPipeline::SerializationSupport& sdata) {
|
||||
if (!Storage::DataBase::Instance().IsOpened()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Serialization::Archive ar{};
|
||||
Serialization::Writer pldata{ar};
|
||||
|
||||
pldata.Write(Serialization::PipelineKeyVersion);
|
||||
pldata.Write(u32{0}); // graphics
|
||||
|
||||
key.Serialize(ar);
|
||||
sdata.Serialize(ar);
|
||||
|
||||
Storage::DataBase::Instance().Save(Storage::BlobType::PipelineKey,
|
||||
fmt::format("g_{:#018x}", hash), ar.TakeOff());
|
||||
}
|
||||
|
||||
void RegisterShaderMeta(const Shader::Info& info,
|
||||
const std::optional<Shader::Gcn::FetchShaderData>& fetch_shader_data,
|
||||
const Shader::StageSpecialization& spec, size_t perm_hash,
|
||||
size_t perm_idx) {
|
||||
if (!Storage::DataBase::Instance().IsOpened()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Serialization::Archive ar;
|
||||
Serialization::Writer meta{ar};
|
||||
|
||||
meta.Write(Serialization::ShaderMetaVersion);
|
||||
meta.Write(Serialization::ShaderBinaryVersion);
|
||||
|
||||
meta.Write(perm_hash);
|
||||
meta.Write(perm_idx);
|
||||
|
||||
spec.Serialize(ar);
|
||||
info.Serialize(ar);
|
||||
|
||||
Storage::DataBase::Instance().Save(Storage::BlobType::ShaderMeta,
|
||||
fmt::format("{:#018x}", perm_hash), ar.TakeOff());
|
||||
}
|
||||
|
||||
void RegisterShaderBinary(std::vector<u32>&& spv, u64 pgm_hash, size_t perm_idx) {
|
||||
if (!Storage::DataBase::Instance().IsOpened()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Storage::DataBase::Instance().Save(Storage::BlobType::ShaderBinary,
|
||||
fmt::format("{:#018x}_{}", pgm_hash, perm_idx),
|
||||
std::move(spv));
|
||||
}
|
||||
|
||||
bool LoadShaderMeta(Serialization::Archive& ar, Shader::Info& info,
|
||||
std::optional<Shader::Gcn::FetchShaderData>& fetch_shader_data,
|
||||
Shader::StageSpecialization& spec, size_t& perm_idx) {
|
||||
Serialization::Reader meta{ar};
|
||||
|
||||
u32 meta_version{};
|
||||
meta.Read(meta_version);
|
||||
if (meta_version != Serialization::ShaderMetaVersion) {
|
||||
return false;
|
||||
}
|
||||
|
||||
u32 binary_version{};
|
||||
meta.Read(binary_version);
|
||||
if (binary_version != Serialization::ShaderBinaryVersion) {
|
||||
return false;
|
||||
}
|
||||
|
||||
u64 perm_hash_ar{};
|
||||
meta.Read(perm_hash_ar);
|
||||
meta.Read(perm_idx);
|
||||
|
||||
spec.Deserialize(ar);
|
||||
info.Deserialize(ar);
|
||||
|
||||
fetch_shader_data = spec.fetch_shader_data;
|
||||
return true;
|
||||
}
|
||||
|
||||
void ComputePipelineKey::Serialize(Serialization::Archive& ar) const {
|
||||
Serialization::Writer key{ar};
|
||||
key.Write(value);
|
||||
}
|
||||
|
||||
bool ComputePipelineKey::Deserialize(Serialization::Archive& ar) {
|
||||
Serialization::Reader key{ar};
|
||||
key.Read(value);
|
||||
return true;
|
||||
}
|
||||
|
||||
void ComputePipeline::SerializationSupport::Serialize(Serialization::Archive& ar) const {
|
||||
// Nothing here yet
|
||||
return;
|
||||
}
|
||||
|
||||
bool ComputePipeline::SerializationSupport::Deserialize(Serialization::Archive& ar) {
|
||||
// Nothing here yet
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PipelineCache::LoadComputePipeline(Serialization::Archive& ar) {
|
||||
compute_key.Deserialize(ar);
|
||||
|
||||
ComputePipeline::SerializationSupport sdata{};
|
||||
sdata.Deserialize(ar);
|
||||
|
||||
std::vector<u8> meta_blob;
|
||||
Storage::DataBase::Instance().Load(Storage::BlobType::ShaderMeta,
|
||||
fmt::format("{:#018x}", compute_key.value), meta_blob);
|
||||
if (meta_blob.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Serialization::Archive meta_ar{std::move(meta_blob)};
|
||||
|
||||
if (!LoadPipelineStage(meta_ar, 0)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto [it, is_new] = compute_pipelines.try_emplace(compute_key);
|
||||
ASSERT(is_new);
|
||||
|
||||
it.value() =
|
||||
std::make_unique<ComputePipeline>(instance, scheduler, desc_heap, profile, *pipeline_cache,
|
||||
compute_key, *infos[0], modules[0], sdata, true);
|
||||
|
||||
infos.fill(nullptr);
|
||||
modules.fill(nullptr);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void GraphicsPipelineKey::Serialize(Serialization::Archive& ar) const {
|
||||
Serialization::Writer key{ar};
|
||||
|
||||
key.Write(this, sizeof(*this));
|
||||
}
|
||||
|
||||
bool GraphicsPipelineKey::Deserialize(Serialization::Archive& ar) {
|
||||
Serialization::Reader key{ar};
|
||||
|
||||
key.Read(this, sizeof(*this));
|
||||
return true;
|
||||
}
|
||||
|
||||
void GraphicsPipeline::SerializationSupport::Serialize(Serialization::Archive& ar) const {
|
||||
Serialization::Writer sdata{ar};
|
||||
|
||||
sdata.Write(&vertex_attributes, sizeof(vertex_attributes));
|
||||
sdata.Write(&vertex_bindings, sizeof(vertex_bindings));
|
||||
sdata.Write(&divisors, sizeof(divisors));
|
||||
sdata.Write(multisampling);
|
||||
sdata.Write(tcs);
|
||||
sdata.Write(tes);
|
||||
}
|
||||
|
||||
bool GraphicsPipeline::SerializationSupport::Deserialize(Serialization::Archive& ar) {
|
||||
Serialization::Reader sdata{ar};
|
||||
|
||||
sdata.Read(&vertex_attributes, sizeof(vertex_attributes));
|
||||
sdata.Read(&vertex_bindings, sizeof(vertex_bindings));
|
||||
sdata.Read(&divisors, sizeof(divisors));
|
||||
sdata.Read(multisampling);
|
||||
sdata.Read(tcs);
|
||||
sdata.Read(tes);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PipelineCache::LoadGraphicsPipeline(Serialization::Archive& ar) {
|
||||
graphics_key.Deserialize(ar);
|
||||
|
||||
GraphicsPipeline::SerializationSupport sdata{};
|
||||
sdata.Deserialize(ar);
|
||||
|
||||
for (int stage_idx = 0; stage_idx < MaxShaderStages; ++stage_idx) {
|
||||
const auto& hash = graphics_key.stage_hashes[stage_idx];
|
||||
if (!hash) {
|
||||
continue;
|
||||
}
|
||||
|
||||
std::vector<u8> meta_blob;
|
||||
Storage::DataBase::Instance().Load(Storage::BlobType::ShaderMeta,
|
||||
fmt::format("{:#018x}", hash), meta_blob);
|
||||
if (meta_blob.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Serialization::Archive meta_ar{std::move(meta_blob)};
|
||||
|
||||
if (!LoadPipelineStage(meta_ar, stage_idx)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
const auto [it, is_new] = graphics_pipelines.try_emplace(graphics_key);
|
||||
ASSERT(is_new);
|
||||
|
||||
it.value() = std::make_unique<GraphicsPipeline>(
|
||||
instance, scheduler, desc_heap, profile, graphics_key, *pipeline_cache, infos,
|
||||
runtime_infos, fetch_shader, modules, sdata, true);
|
||||
|
||||
infos.fill(nullptr);
|
||||
modules.fill(nullptr);
|
||||
fetch_shader.reset();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PipelineCache::LoadPipelineStage(Serialization::Archive& ar, size_t stage) {
|
||||
auto program = std::make_unique<Program>();
|
||||
Shader::StageSpecialization spec{};
|
||||
spec.info = &program->info;
|
||||
size_t perm_idx{};
|
||||
if (!LoadShaderMeta(ar, program->info, fetch_shader, spec, perm_idx)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<u32> spv{};
|
||||
Storage::DataBase::Instance().Load(Storage::BlobType::ShaderBinary,
|
||||
fmt::format("{:#018x}_{}", program->info.pgm_hash, perm_idx),
|
||||
spv);
|
||||
if (spv.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Permutation hash depends on shader variation index. To prevent collisions, we need insert it
|
||||
// at the exact position rather than append
|
||||
|
||||
vk::ShaderModule module{};
|
||||
|
||||
auto [it_pgm, new_program] = program_cache.try_emplace(program->info.pgm_hash);
|
||||
if (new_program) {
|
||||
module = CompileSPV(spv, instance.GetDevice());
|
||||
it_pgm.value() = std::move(program);
|
||||
} else {
|
||||
const auto& it = std::ranges::find(it_pgm.value()->modules, spec, &Program::Module::spec);
|
||||
if (it != it_pgm.value()->modules.end()) {
|
||||
// If the permutation is already preloaded, make sure it has the same permutation index
|
||||
const auto idx = std::distance(it_pgm.value()->modules.begin(), it);
|
||||
ASSERT_MSG(perm_idx == idx, "Permutation {} is already inserted at {}! ({}_{:x})",
|
||||
perm_idx, idx, program->info.stage, program->info.pgm_hash);
|
||||
module = it->module;
|
||||
} else {
|
||||
module = CompileSPV(spv, instance.GetDevice());
|
||||
}
|
||||
}
|
||||
it_pgm.value()->InsertPermut(module, std::move(spec), perm_idx);
|
||||
|
||||
infos[stage] = &it_pgm.value()->info;
|
||||
modules[stage] = module;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void PipelineCache::WarmUp() {
|
||||
if (!Config::isPipelineCacheEnabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Storage::DataBase::Instance().Open();
|
||||
|
||||
// Check if cache is compatible
|
||||
std::vector<u8> profile_data{};
|
||||
Storage::DataBase::Instance().Load(Storage::BlobType::ShaderProfile, "profile", profile_data);
|
||||
if (profile_data.empty()) {
|
||||
Storage::DataBase::Instance().FinishPreload();
|
||||
|
||||
profile_data.resize(sizeof(profile));
|
||||
std::memcpy(profile_data.data(), &profile, sizeof(profile));
|
||||
Storage::DataBase::Instance().Save(Storage::BlobType::ShaderProfile, "profile",
|
||||
std::move(profile_data));
|
||||
return;
|
||||
}
|
||||
if (std::memcmp(profile_data.data(), &profile, sizeof(profile)) != 0) {
|
||||
LOG_WARNING(Render,
|
||||
"Pipeline cache isn't compatible with current system. Ignoring the cache");
|
||||
return;
|
||||
}
|
||||
|
||||
u32 num_pipelines{};
|
||||
u32 num_total_pipelines{};
|
||||
|
||||
Storage::DataBase::Instance().ForEachBlob(
|
||||
Storage::BlobType::PipelineKey, [&](std::vector<u8>&& data) {
|
||||
++num_total_pipelines;
|
||||
|
||||
Serialization::Archive ar{std::move(data)};
|
||||
Serialization::Reader pldata{ar};
|
||||
|
||||
u32 version{};
|
||||
pldata.Read(version);
|
||||
if (version != Serialization::PipelineKeyVersion) {
|
||||
return;
|
||||
}
|
||||
|
||||
u32 is_compute{};
|
||||
pldata.Read(is_compute);
|
||||
|
||||
bool result{};
|
||||
if (is_compute) {
|
||||
result = LoadComputePipeline(ar);
|
||||
} else {
|
||||
result = LoadGraphicsPipeline(ar);
|
||||
}
|
||||
|
||||
if (result) {
|
||||
++num_pipelines;
|
||||
}
|
||||
});
|
||||
|
||||
LOG_INFO(Render, "Preloaded {} pipelines", num_pipelines);
|
||||
if (num_total_pipelines > num_pipelines) {
|
||||
LOG_WARNING(Render, "{} stale pipelines were found. Consider re-generating the cache",
|
||||
num_total_pipelines - num_pipelines);
|
||||
}
|
||||
|
||||
Storage::DataBase::Instance().FinishPreload();
|
||||
}
|
||||
|
||||
void PipelineCache::Sync() {
|
||||
Storage::DataBase::Instance().Close();
|
||||
}
|
||||
|
||||
} // namespace Vulkan
|
||||
|
||||
namespace Shader {
|
||||
|
||||
void Info::Serialize(Serialization::Archive& ar) const {
|
||||
Serialization::Writer info{ar};
|
||||
|
||||
info.Write(this, sizeof(InfoPersistent));
|
||||
info.Write(flattened_ud_buf);
|
||||
srt_info.Serialize(ar);
|
||||
}
|
||||
|
||||
bool Info::Deserialize(Serialization::Archive& ar) {
|
||||
Serialization::Reader info{ar};
|
||||
|
||||
info.Read(this, sizeof(Shader::InfoPersistent));
|
||||
info.Read(flattened_ud_buf);
|
||||
|
||||
return srt_info.Deserialize(ar);
|
||||
}
|
||||
|
||||
void Gcn::FetchShaderData::Serialize(Serialization::Archive& ar) const {
|
||||
Serialization::Writer fetch{ar};
|
||||
ar.Grow(6 + attributes.size() * sizeof(VertexAttribute));
|
||||
|
||||
fetch.Write(size);
|
||||
fetch.Write(vertex_offset_sgpr);
|
||||
fetch.Write(instance_offset_sgpr);
|
||||
fetch.Write(attributes);
|
||||
}
|
||||
|
||||
bool Gcn::FetchShaderData::Deserialize(Serialization::Archive& ar) {
|
||||
Serialization::Reader fetch{ar};
|
||||
|
||||
fetch.Read(size);
|
||||
fetch.Read(vertex_offset_sgpr);
|
||||
fetch.Read(instance_offset_sgpr);
|
||||
fetch.Read(attributes);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void PersistentSrtInfo::Serialize(Serialization::Archive& ar) const {
|
||||
Serialization::Writer srt{ar};
|
||||
|
||||
srt.Write(this, sizeof(*this));
|
||||
if (walker_func_size) {
|
||||
srt.Write(reinterpret_cast<void*>(walker_func), walker_func_size);
|
||||
}
|
||||
}
|
||||
|
||||
bool PersistentSrtInfo::Deserialize(Serialization::Archive& ar) {
|
||||
Serialization::Reader srt{ar};
|
||||
|
||||
srt.Read(this, sizeof(*this));
|
||||
|
||||
if (walker_func_size) {
|
||||
walker_func = RegisterWalkerCode(ar.CurrPtr(), walker_func_size);
|
||||
ar.Advance(walker_func_size);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void StageSpecialization::Serialize(Serialization::Archive& ar) const {
|
||||
Serialization::Writer spec{ar};
|
||||
|
||||
spec.Write(start);
|
||||
spec.Write(runtime_info);
|
||||
|
||||
spec.Write(bitset.to_string());
|
||||
|
||||
if (fetch_shader_data) {
|
||||
spec.Write(sizeof(*fetch_shader_data));
|
||||
fetch_shader_data->Serialize(ar);
|
||||
} else {
|
||||
spec.Write(size_t{0});
|
||||
}
|
||||
|
||||
spec.Write(vs_attribs);
|
||||
spec.Write(buffers);
|
||||
spec.Write(images);
|
||||
spec.Write(fmasks);
|
||||
spec.Write(samplers);
|
||||
}
|
||||
|
||||
bool StageSpecialization::Deserialize(Serialization::Archive& ar) {
|
||||
Serialization::Reader spec{ar};
|
||||
|
||||
spec.Read(start);
|
||||
spec.Read(runtime_info);
|
||||
|
||||
std::string bits{};
|
||||
spec.Read(bits);
|
||||
bitset = std::bitset<MaxStageResources>(bits);
|
||||
|
||||
u64 fetch_data_size{};
|
||||
spec.Read(fetch_data_size);
|
||||
|
||||
if (fetch_data_size) {
|
||||
Gcn::FetchShaderData fetch_data;
|
||||
fetch_data.Deserialize(ar);
|
||||
fetch_shader_data = fetch_data;
|
||||
}
|
||||
|
||||
spec.Read(vs_attribs);
|
||||
spec.Read(buffers);
|
||||
spec.Read(images);
|
||||
spec.Read(fmasks);
|
||||
spec.Read(samplers);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace Shader
|
||||
21
src/video_core/renderer_vulkan/vk_pipeline_serialization.h
Normal file
21
src/video_core/renderer_vulkan/vk_pipeline_serialization.h
Normal file
@ -0,0 +1,21 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "shader_recompiler/frontend/fetch_shader.h"
|
||||
#include "video_core/renderer_vulkan/vk_pipeline_cache.h"
|
||||
#include "video_core/renderer_vulkan/vk_shader_util.h"
|
||||
|
||||
namespace Vulkan {
|
||||
|
||||
void RegisterPipelineData(const ComputePipelineKey& key,
|
||||
ComputePipeline::SerializationSupport& sdata);
|
||||
void RegisterPipelineData(const GraphicsPipelineKey& key, u64 hash,
|
||||
GraphicsPipeline::SerializationSupport& sdata);
|
||||
void RegisterShaderMeta(const Shader::Info& info,
|
||||
const std::optional<Shader::Gcn::FetchShaderData>& fetch_shader_data,
|
||||
const Shader::StageSpecialization& spec, size_t perm_hash, size_t perm_idx);
|
||||
void RegisterShaderBinary(std::vector<u32>&& spv, u64 pgm_hash, size_t perm_idx);
|
||||
|
||||
} // namespace Vulkan
|
||||
@ -983,8 +983,8 @@ void Rasterizer::DepthStencilCopy(bool is_depth, bool is_stencil) {
|
||||
ScopeMarkerEnd();
|
||||
}
|
||||
|
||||
void Rasterizer::InlineData(VAddr address, const void* value, u32 num_bytes, bool is_gds) {
|
||||
buffer_cache.InlineData(address, value, num_bytes, is_gds);
|
||||
void Rasterizer::FillBuffer(VAddr address, u32 num_bytes, u32 value, bool is_gds) {
|
||||
buffer_cache.FillBuffer(address, num_bytes, value, is_gds);
|
||||
}
|
||||
|
||||
void Rasterizer::CopyBuffer(VAddr dst, VAddr src, u32 num_bytes, bool dst_gds, bool src_gds) {
|
||||
|
||||
@ -55,7 +55,7 @@ public:
|
||||
void ScopedMarkerInsertColor(const std::string_view& str, const u32 color,
|
||||
bool from_guest = false);
|
||||
|
||||
void InlineData(VAddr address, const void* value, u32 num_bytes, bool is_gds);
|
||||
void FillBuffer(VAddr address, u32 num_bytes, u32 value, bool is_gds);
|
||||
void CopyBuffer(VAddr dst, VAddr src, u32 num_bytes, bool dst_gds, bool src_gds);
|
||||
u32 ReadDataFromGds(u32 gsd_offset);
|
||||
bool InvalidateMemory(VAddr addr, u64 size);
|
||||
|
||||
@ -3,6 +3,7 @@
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/debug.h"
|
||||
#include "common/thread.h"
|
||||
#include "imgui/renderer/texture_manager.h"
|
||||
#include "video_core/renderer_vulkan/vk_instance.h"
|
||||
#include "video_core/renderer_vulkan/vk_scheduler.h"
|
||||
@ -17,6 +18,8 @@ Scheduler::Scheduler(const Instance& instance)
|
||||
profiler_scope = reinterpret_cast<tracy::VkCtxScope*>(std::malloc(sizeof(tracy::VkCtxScope)));
|
||||
#endif
|
||||
AllocateWorkerCommandBuffers();
|
||||
priority_pending_ops_thread =
|
||||
std::jthread(std::bind_front(&Scheduler::PriorityPendingOpsThread, this));
|
||||
}
|
||||
|
||||
Scheduler::~Scheduler() {
|
||||
@ -167,6 +170,32 @@ void Scheduler::SubmitExecution(SubmitInfo& info) {
|
||||
PopPendingOperations();
|
||||
}
|
||||
|
||||
void Scheduler::PriorityPendingOpsThread(std::stop_token stoken) {
|
||||
Common::SetCurrentThreadName("shadPS4:GpuSchedPriorityPendingOpsRunner");
|
||||
|
||||
while (!stoken.stop_requested()) {
|
||||
PendingOp op;
|
||||
{
|
||||
std::unique_lock lk(priority_pending_ops_mutex);
|
||||
priority_pending_ops_cv.wait(lk, stoken,
|
||||
[this] { return !priority_pending_ops.empty(); });
|
||||
if (stoken.stop_requested()) {
|
||||
break;
|
||||
}
|
||||
|
||||
op = std::move(priority_pending_ops.front());
|
||||
priority_pending_ops.pop();
|
||||
}
|
||||
|
||||
master_semaphore.Wait(op.gpu_tick);
|
||||
if (stoken.stop_requested()) {
|
||||
break;
|
||||
}
|
||||
|
||||
op.callback();
|
||||
}
|
||||
}
|
||||
|
||||
void DynamicState::Commit(const Instance& instance, const vk::CommandBuffer& cmdbuf) {
|
||||
if (dirty_state.viewports) {
|
||||
dirty_state.viewports = false;
|
||||
@ -290,9 +319,7 @@ void DynamicState::Commit(const Instance& instance, const vk::CommandBuffer& cmd
|
||||
}
|
||||
if (dirty_state.primitive_restart_enable) {
|
||||
dirty_state.primitive_restart_enable = false;
|
||||
if (instance.IsPrimitiveRestartDisableSupported()) {
|
||||
cmdbuf.setPrimitiveRestartEnable(primitive_restart_enable);
|
||||
}
|
||||
cmdbuf.setPrimitiveRestartEnable(primitive_restart_enable);
|
||||
}
|
||||
if (dirty_state.rasterizer_discard_enable) {
|
||||
dirty_state.rasterizer_discard_enable = false;
|
||||
|
||||
@ -5,6 +5,7 @@
|
||||
|
||||
#include <condition_variable>
|
||||
#include <mutex>
|
||||
#include <thread>
|
||||
#include <queue>
|
||||
|
||||
#include "common/unique_function.h"
|
||||
@ -401,10 +402,21 @@ public:
|
||||
}
|
||||
|
||||
/// Defers an operation until the gpu has reached the current cpu tick.
|
||||
/// Will be run when submitting or calling PopPendingOperations.
|
||||
void DeferOperation(Common::UniqueFunction<void>&& func) {
|
||||
pending_ops.emplace(std::move(func), CurrentTick());
|
||||
}
|
||||
|
||||
/// Defers an operation until the gpu has reached the current cpu tick.
|
||||
/// Runs as soon as possible in another thread.
|
||||
void DeferPriorityOperation(Common::UniqueFunction<void>&& func) {
|
||||
{
|
||||
std::unique_lock lk(priority_pending_ops_mutex);
|
||||
priority_pending_ops.emplace(std::move(func), CurrentTick());
|
||||
}
|
||||
priority_pending_ops_cv.notify_one();
|
||||
}
|
||||
|
||||
static std::mutex submit_mutex;
|
||||
|
||||
private:
|
||||
@ -412,6 +424,8 @@ private:
|
||||
|
||||
void SubmitExecution(SubmitInfo& info);
|
||||
|
||||
void PriorityPendingOpsThread(std::stop_token stoken);
|
||||
|
||||
private:
|
||||
const Instance& instance;
|
||||
MasterSemaphore master_semaphore;
|
||||
@ -424,6 +438,10 @@ private:
|
||||
u64 gpu_tick;
|
||||
};
|
||||
std::queue<PendingOp> pending_ops;
|
||||
std::queue<PendingOp> priority_pending_ops;
|
||||
std::mutex priority_pending_ops_mutex;
|
||||
std::condition_variable_any priority_pending_ops_cv;
|
||||
std::jthread priority_pending_ops_thread;
|
||||
RenderState render_state;
|
||||
bool is_rendering = false;
|
||||
tracy::VkCtxScope* profiler_scope{};
|
||||
|
||||
@ -52,9 +52,6 @@ TextureCache::TextureCache(const Vulkan::Instance& instance_, Vulkan::Scheduler&
|
||||
std::max<u64>(std::min(device_local_memory - min_vacancy_critical, min_spacing_critical),
|
||||
DEFAULT_CRITICAL_GC_MEMORY));
|
||||
trigger_gc_memory = static_cast<u64>((device_local_memory - mem_threshold) / 2);
|
||||
|
||||
downloaded_images_thread =
|
||||
std::jthread([&](const std::stop_token& token) { DownloadedImagesThread(token); });
|
||||
}
|
||||
|
||||
TextureCache::~TextureCache() = default;
|
||||
@ -88,11 +85,12 @@ ImageId TextureCache::GetNullImage(const vk::Format format) {
|
||||
|
||||
void TextureCache::ProcessDownloadImages() {
|
||||
for (const ImageId image_id : download_images) {
|
||||
DownloadImageMemory(image_id);
|
||||
DownloadImageMemory<true>(image_id);
|
||||
}
|
||||
download_images.clear();
|
||||
}
|
||||
|
||||
template <bool priority>
|
||||
void TextureCache::DownloadImageMemory(ImageId image_id) {
|
||||
Image& image = slot_images[image_id];
|
||||
if (False(image.flags & ImageFlagBits::GpuModified)) {
|
||||
@ -136,33 +134,20 @@ void TextureCache::DownloadImageMemory(ImageId image_id) {
|
||||
image.Transit(vk::ImageLayout::eTransferSrcOptimal, vk::AccessFlagBits2::eTransferRead, {});
|
||||
tile_manager.TileImage(image, buffer_copies, mapping.Buffer()->Handle(), mapping.Offset(),
|
||||
copy_size);
|
||||
{
|
||||
std::unique_lock lock(downloaded_images_mutex);
|
||||
downloaded_images_queue.emplace(scheduler.CurrentTick(), image_addr, mapping.Data(),
|
||||
image_size);
|
||||
downloaded_images_cv.notify_one();
|
||||
}
|
||||
}
|
||||
|
||||
void TextureCache::DownloadedImagesThread(const std::stop_token& token) {
|
||||
auto* memory = Core::Memory::Instance();
|
||||
while (!token.stop_requested()) {
|
||||
DownloadedImage image;
|
||||
{
|
||||
std::unique_lock lock{downloaded_images_mutex};
|
||||
downloaded_images_cv.wait(lock, token,
|
||||
[this] { return !downloaded_images_queue.empty(); });
|
||||
if (token.stop_requested()) {
|
||||
break;
|
||||
}
|
||||
image = downloaded_images_queue.front();
|
||||
downloaded_images_queue.pop();
|
||||
const auto operation = [this, device_addr = image.info.guest_address, download = mapping.Data(),
|
||||
image_size] {
|
||||
Core::Memory::Instance()->TryWriteBacking(std::bit_cast<u8*>(device_addr), download,
|
||||
image_size);
|
||||
if constexpr (!priority) {
|
||||
buffer_cache.InvalidateMemory(device_addr, image_size, false);
|
||||
}
|
||||
};
|
||||
|
||||
scheduler.GetMasterSemaphore()->Wait(image.tick);
|
||||
memory->TryWriteBacking(std::bit_cast<u8*>(image.device_addr), image.download,
|
||||
image.download_size);
|
||||
buffer_cache.InvalidateMemory(image.device_addr, image.download_size, false);
|
||||
if constexpr (priority) {
|
||||
scheduler.DeferPriorityOperation(operation);
|
||||
} else {
|
||||
scheduler.DeferOperation(operation);
|
||||
}
|
||||
}
|
||||
|
||||
@ -967,7 +952,7 @@ void TextureCache::RunGarbageCollector() {
|
||||
}
|
||||
|
||||
for (const auto& image_id : download_pending) {
|
||||
DownloadImageMemory(image_id);
|
||||
DownloadImageMemory<false>(image_id);
|
||||
DeleteImage(image_id);
|
||||
}
|
||||
|
||||
@ -976,6 +961,7 @@ void TextureCache::RunGarbageCollector() {
|
||||
// of the image are requested before they are downloaded in which case
|
||||
// outdated buffer cache contents are used instead.
|
||||
scheduler.Finish();
|
||||
scheduler.PopPendingOperations();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -318,16 +318,6 @@ private:
|
||||
Common::LeastRecentlyUsedCache<ImageId, u64> lru_cache;
|
||||
PageTable page_table;
|
||||
std::mutex mutex;
|
||||
struct DownloadedImage {
|
||||
u64 tick;
|
||||
VAddr device_addr;
|
||||
void* download;
|
||||
size_t download_size;
|
||||
};
|
||||
std::queue<DownloadedImage> downloaded_images_queue;
|
||||
std::mutex downloaded_images_mutex;
|
||||
std::condition_variable_any downloaded_images_cv;
|
||||
std::jthread downloaded_images_thread;
|
||||
struct MetaDataInfo {
|
||||
enum class Type {
|
||||
CMask,
|
||||
|
||||
Loading…
Reference in New Issue
Block a user