mirror of
https://github.com/shadps4-emu/shadPS4.git
synced 2025-12-16 12:09:07 +00:00
Initial implementation
This commit is contained in:
parent
3f86c2e94a
commit
22ef7f1b0f
@ -982,6 +982,10 @@ 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_cache.h
|
||||||
src/video_core/renderer_vulkan/vk_pipeline_common.cpp
|
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_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_pipeline_storage.cpp
|
||||||
|
src/video_core/renderer_vulkan/vk_pipeline_storage.h
|
||||||
src/video_core/renderer_vulkan/vk_platform.cpp
|
src/video_core/renderer_vulkan/vk_platform.cpp
|
||||||
src/video_core/renderer_vulkan/vk_platform.h
|
src/video_core/renderer_vulkan/vk_platform.h
|
||||||
src/video_core/renderer_vulkan/vk_presenter.cpp
|
src/video_core/renderer_vulkan/vk_presenter.cpp
|
||||||
|
|||||||
@ -191,6 +191,7 @@ static ConfigEntry<bool> vkCrashDiagnostic(false);
|
|||||||
static ConfigEntry<bool> vkHostMarkers(false);
|
static ConfigEntry<bool> vkHostMarkers(false);
|
||||||
static ConfigEntry<bool> vkGuestMarkers(false);
|
static ConfigEntry<bool> vkGuestMarkers(false);
|
||||||
static ConfigEntry<bool> rdocEnable(false);
|
static ConfigEntry<bool> rdocEnable(false);
|
||||||
|
static ConfigEntry<bool> pipelineCacheEnable(false);
|
||||||
|
|
||||||
// Debug
|
// Debug
|
||||||
static ConfigEntry<bool> isDebugDump(false);
|
static ConfigEntry<bool> isDebugDump(false);
|
||||||
@ -452,6 +453,10 @@ bool isRdocEnabled() {
|
|||||||
return rdocEnable.get();
|
return rdocEnable.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool isPipelineCacheEnabled() {
|
||||||
|
return pipelineCacheEnable.get();
|
||||||
|
}
|
||||||
|
|
||||||
bool fpsColor() {
|
bool fpsColor() {
|
||||||
return isFpsColor.get();
|
return isFpsColor.get();
|
||||||
}
|
}
|
||||||
@ -603,6 +608,10 @@ void setRdocEnabled(bool enable, bool is_game_specific) {
|
|||||||
rdocEnable.set(enable, is_game_specific);
|
rdocEnable.set(enable, is_game_specific);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void setPipelineCacheEnabled(bool enable, bool is_game_specific) {
|
||||||
|
pipelineCacheEnable.set(enable, is_game_specific);
|
||||||
|
}
|
||||||
|
|
||||||
void setVblankFreq(u32 value, bool is_game_specific) {
|
void setVblankFreq(u32 value, bool is_game_specific) {
|
||||||
vblankFrequency.set(value, is_game_specific);
|
vblankFrequency.set(value, is_game_specific);
|
||||||
}
|
}
|
||||||
@ -939,6 +948,7 @@ void load(const std::filesystem::path& path, bool is_game_specific) {
|
|||||||
vkHostMarkers.setFromToml(vk, "hostMarkers", is_game_specific);
|
vkHostMarkers.setFromToml(vk, "hostMarkers", is_game_specific);
|
||||||
vkGuestMarkers.setFromToml(vk, "guestMarkers", is_game_specific);
|
vkGuestMarkers.setFromToml(vk, "guestMarkers", is_game_specific);
|
||||||
rdocEnable.setFromToml(vk, "rdocEnable", is_game_specific);
|
rdocEnable.setFromToml(vk, "rdocEnable", is_game_specific);
|
||||||
|
pipelineCacheEnable.setFromToml(vk, "pipelineCacheEnable", is_game_specific);
|
||||||
}
|
}
|
||||||
|
|
||||||
string current_version = {};
|
string current_version = {};
|
||||||
@ -1107,6 +1117,7 @@ void save(const std::filesystem::path& path, bool is_game_specific) {
|
|||||||
vkHostMarkers.setTomlValue(data, "Vulkan", "hostMarkers", is_game_specific);
|
vkHostMarkers.setTomlValue(data, "Vulkan", "hostMarkers", is_game_specific);
|
||||||
vkGuestMarkers.setTomlValue(data, "Vulkan", "guestMarkers", is_game_specific);
|
vkGuestMarkers.setTomlValue(data, "Vulkan", "guestMarkers", is_game_specific);
|
||||||
rdocEnable.setTomlValue(data, "Vulkan", "rdocEnable", is_game_specific);
|
rdocEnable.setTomlValue(data, "Vulkan", "rdocEnable", is_game_specific);
|
||||||
|
pipelineCacheEnable.setTomlValue(data, "Vulkan", "pipelineCacheEnable", is_game_specific);
|
||||||
|
|
||||||
isDebugDump.setTomlValue(data, "Debug", "DebugDump", is_game_specific);
|
isDebugDump.setTomlValue(data, "Debug", "DebugDump", is_game_specific);
|
||||||
isShaderDebug.setTomlValue(data, "Debug", "CollectShader", is_game_specific);
|
isShaderDebug.setTomlValue(data, "Debug", "CollectShader", is_game_specific);
|
||||||
@ -1237,6 +1248,7 @@ void setDefaultValues(bool is_game_specific) {
|
|||||||
vkHostMarkers.set(false, is_game_specific);
|
vkHostMarkers.set(false, is_game_specific);
|
||||||
vkGuestMarkers.set(false, is_game_specific);
|
vkGuestMarkers.set(false, is_game_specific);
|
||||||
rdocEnable.set(false, is_game_specific);
|
rdocEnable.set(false, is_game_specific);
|
||||||
|
pipelineCacheEnable.set(false, is_game_specific);
|
||||||
|
|
||||||
// GS - Debug
|
// GS - Debug
|
||||||
isDebugDump.set(false, is_game_specific);
|
isDebugDump.set(false, is_game_specific);
|
||||||
|
|||||||
@ -94,7 +94,9 @@ void setVkGuestMarkersEnabled(bool enable, bool is_game_specific = false);
|
|||||||
bool getEnableDiscordRPC();
|
bool getEnableDiscordRPC();
|
||||||
void setEnableDiscordRPC(bool enable);
|
void setEnableDiscordRPC(bool enable);
|
||||||
bool isRdocEnabled();
|
bool isRdocEnabled();
|
||||||
|
bool isPipelineCacheEnabled();
|
||||||
void setRdocEnabled(bool enable, bool is_game_specific = false);
|
void setRdocEnabled(bool enable, bool is_game_specific = false);
|
||||||
|
void setPipelineCacheEnabled(bool enable, bool is_game_specific = false);
|
||||||
std::string getLogType();
|
std::string getLogType();
|
||||||
void setLogType(const std::string& type, bool is_game_specific = false);
|
void setLogType(const std::string& type, bool is_game_specific = false);
|
||||||
std::string getLogFilter();
|
std::string getLogFilter();
|
||||||
|
|||||||
@ -127,6 +127,7 @@ static auto UserPaths = [] {
|
|||||||
create_path(PathType::MetaDataDir, user_dir / METADATA_DIR);
|
create_path(PathType::MetaDataDir, user_dir / METADATA_DIR);
|
||||||
create_path(PathType::CustomTrophy, user_dir / CUSTOM_TROPHY);
|
create_path(PathType::CustomTrophy, user_dir / CUSTOM_TROPHY);
|
||||||
create_path(PathType::CustomConfigs, user_dir / CUSTOM_CONFIGS);
|
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");
|
std::ofstream notice_file(user_dir / CUSTOM_TROPHY / "Notice.txt");
|
||||||
if (notice_file.is_open()) {
|
if (notice_file.is_open()) {
|
||||||
|
|||||||
@ -24,6 +24,7 @@ enum class PathType {
|
|||||||
MetaDataDir, // Where game metadata (e.g. trophies and menu backgrounds) is stored.
|
MetaDataDir, // Where game metadata (e.g. trophies and menu backgrounds) is stored.
|
||||||
CustomTrophy, // Where custom files for trophies are stored.
|
CustomTrophy, // Where custom files for trophies are stored.
|
||||||
CustomConfigs, // Where custom files for different games are stored.
|
CustomConfigs, // Where custom files for different games are stored.
|
||||||
|
CacheDir, // Where pipeline and shader cache is stored.
|
||||||
};
|
};
|
||||||
|
|
||||||
constexpr auto PORTABLE_DIR = "user";
|
constexpr auto PORTABLE_DIR = "user";
|
||||||
@ -42,6 +43,7 @@ constexpr auto PATCHES_DIR = "patches";
|
|||||||
constexpr auto METADATA_DIR = "game_data";
|
constexpr auto METADATA_DIR = "game_data";
|
||||||
constexpr auto CUSTOM_TROPHY = "custom_trophy";
|
constexpr auto CUSTOM_TROPHY = "custom_trophy";
|
||||||
constexpr auto CUSTOM_CONFIGS = "custom_configs";
|
constexpr auto CUSTOM_CONFIGS = "custom_configs";
|
||||||
|
constexpr auto CACHE_DIR = "cache";
|
||||||
|
|
||||||
// Filenames
|
// Filenames
|
||||||
constexpr auto LOG_FILE = "shad_log.txt";
|
constexpr auto LOG_FILE = "shad_log.txt";
|
||||||
|
|||||||
@ -51,7 +51,7 @@ std::optional<FetchShaderData> ParseFetchShader(const Shader::Info& info) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const auto* code = GetFetchShaderCode(info, info.fetch_shader_sgpr_base);
|
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());
|
GcnCodeSlice code_slice(code, code + std::numeric_limits<u32>::max());
|
||||||
GcnDecodeContext decoder;
|
GcnDecodeContext decoder;
|
||||||
|
|
||||||
|
|||||||
@ -8,6 +8,10 @@
|
|||||||
#include "common/types.h"
|
#include "common/types.h"
|
||||||
#include "shader_recompiler/info.h"
|
#include "shader_recompiler/info.h"
|
||||||
|
|
||||||
|
namespace Serialization {
|
||||||
|
struct Archive;
|
||||||
|
}
|
||||||
|
|
||||||
namespace Shader::Gcn {
|
namespace Shader::Gcn {
|
||||||
|
|
||||||
struct VertexAttribute {
|
struct VertexAttribute {
|
||||||
@ -50,7 +54,6 @@ struct VertexAttribute {
|
|||||||
};
|
};
|
||||||
|
|
||||||
struct FetchShaderData {
|
struct FetchShaderData {
|
||||||
const u32* code;
|
|
||||||
u32 size = 0;
|
u32 size = 0;
|
||||||
std::vector<VertexAttribute> attributes;
|
std::vector<VertexAttribute> attributes;
|
||||||
s8 vertex_offset_sgpr = -1; ///< SGPR of vertex offset from VADDR
|
s8 vertex_offset_sgpr = -1; ///< SGPR of vertex offset from VADDR
|
||||||
@ -60,6 +63,10 @@ struct FetchShaderData {
|
|||||||
return attributes == other.attributes && vertex_offset_sgpr == other.vertex_offset_sgpr &&
|
return attributes == other.attributes && vertex_offset_sgpr == other.vertex_offset_sgpr &&
|
||||||
instance_offset_sgpr == other.instance_offset_sgpr;
|
instance_offset_sgpr == other.instance_offset_sgpr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Serialize(Serialization::Archive& ar) const;
|
||||||
|
bool Deserialize(Serialization::Archive& buffer);
|
||||||
|
u64 Hash() const;
|
||||||
};
|
};
|
||||||
|
|
||||||
const u32* GetFetchShaderCode(const Info& info, u32 sgpr_base);
|
const u32* GetFetchShaderCode(const Info& info, u32 sgpr_base);
|
||||||
|
|||||||
@ -596,9 +596,8 @@ public:
|
|||||||
IR::AbstractSyntaxList& syntax_list_, std::span<const GcnInst> inst_list_,
|
IR::AbstractSyntaxList& syntax_list_, std::span<const GcnInst> inst_list_,
|
||||||
Info& info_, const RuntimeInfo& runtime_info_, const Profile& profile_)
|
Info& info_, const RuntimeInfo& runtime_info_, const Profile& profile_)
|
||||||
: stmt_pool{stmt_pool_}, inst_pool{inst_pool_}, block_pool{block_pool_},
|
: stmt_pool{stmt_pool_}, inst_pool{inst_pool_}, block_pool{block_pool_},
|
||||||
syntax_list{syntax_list_}, inst_list{inst_list_}, info{info_},
|
syntax_list{syntax_list_}, inst_list{inst_list_}, runtime_info{runtime_info_},
|
||||||
runtime_info{runtime_info_}, profile{profile_},
|
profile{profile_}, translator{info_, runtime_info_, profile_} {
|
||||||
translator{info_, runtime_info_, profile_} {
|
|
||||||
Visit(root_stmt, nullptr, nullptr);
|
Visit(root_stmt, nullptr, nullptr);
|
||||||
|
|
||||||
IR::Block* first_block = syntax_list.front().data.block;
|
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)};
|
Statement* merge_stmt{TryFindForwardBlock(stmt)};
|
||||||
if (!merge_stmt) {
|
if (!merge_stmt) {
|
||||||
// Create a merge block we can visit later
|
// Create a merge block we can visit later
|
||||||
@ -798,7 +797,6 @@ private:
|
|||||||
IR::AbstractSyntaxList& syntax_list;
|
IR::AbstractSyntaxList& syntax_list;
|
||||||
const Block dummy_flow_block{.is_dummy = true};
|
const Block dummy_flow_block{.is_dummy = true};
|
||||||
std::span<const GcnInst> inst_list;
|
std::span<const GcnInst> inst_list;
|
||||||
Info& info;
|
|
||||||
const RuntimeInfo& runtime_info;
|
const RuntimeInfo& runtime_info;
|
||||||
const Profile& profile;
|
const Profile& profile;
|
||||||
Translator translator;
|
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 filename = fmt::format("vs_{:#018x}.fetch.bin", info.pgm_hash);
|
||||||
const auto file = IOFile{dump_dir / filename, FileAccessMode::Create};
|
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) {
|
for (const auto& attrib : fetch_data->attributes) {
|
||||||
|
|||||||
@ -19,6 +19,10 @@
|
|||||||
#include "shader_recompiler/resource.h"
|
#include "shader_recompiler/resource.h"
|
||||||
#include "shader_recompiler/runtime_info.h"
|
#include "shader_recompiler/runtime_info.h"
|
||||||
|
|
||||||
|
namespace Serialization {
|
||||||
|
struct Archive;
|
||||||
|
}
|
||||||
|
|
||||||
namespace Shader {
|
namespace Shader {
|
||||||
|
|
||||||
enum class Qualifier : u8 {
|
enum class Qualifier : u8 {
|
||||||
@ -34,7 +38,49 @@ enum class Qualifier : u8 {
|
|||||||
/**
|
/**
|
||||||
* Contains general information generated by the shader recompiler for an input program.
|
* 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 {
|
struct AttributeFlags {
|
||||||
bool Get(IR::Attribute attrib, u32 comp = 0) const {
|
bool Get(IR::Attribute attrib, u32 comp = 0) const {
|
||||||
return flags[Index(attrib)] & (1 << comp);
|
return flags[Index(attrib)] & (1 << comp);
|
||||||
@ -58,56 +104,36 @@ struct Info {
|
|||||||
|
|
||||||
std::array<u8, IR::NumAttributes> flags;
|
std::array<u8, IR::NumAttributes> flags;
|
||||||
};
|
};
|
||||||
AttributeFlags loads{};
|
|
||||||
AttributeFlags stores{};
|
|
||||||
|
|
||||||
struct UserDataMask {
|
enum class ReadConstType {
|
||||||
void Set(IR::ScalarReg reg) noexcept {
|
None = 0,
|
||||||
mask |= 1 << static_cast<u32>(reg);
|
Immediate = 1 << 0,
|
||||||
}
|
Dynamic = 1 << 1,
|
||||||
|
|
||||||
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{};
|
|
||||||
|
|
||||||
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 {
|
struct Interpolation {
|
||||||
Qualifier primary;
|
Qualifier primary;
|
||||||
Qualifier auxiliary;
|
Qualifier auxiliary;
|
||||||
};
|
};
|
||||||
std::array<Interpolation, IR::NumParams> fs_interpolation{};
|
|
||||||
|
|
||||||
IR::ScalarReg tess_consts_ptr_base = IR::ScalarReg::Max;
|
// Non-trivially serialized members
|
||||||
s32 tess_consts_dword_offset = -1;
|
|
||||||
|
|
||||||
std::span<const u32> user_data;
|
std::span<const u32> user_data;
|
||||||
Stage stage;
|
std::vector<u32> flattened_ud_buf;
|
||||||
LogicalStage l_stage;
|
PersistentSrtInfo srt_info;
|
||||||
|
|
||||||
|
// Non-serializable members. To save some space, can be moved into another temporary struct and
|
||||||
|
// be droped once translation is finished
|
||||||
|
AttributeFlags loads{};
|
||||||
|
AttributeFlags stores{};
|
||||||
|
|
||||||
|
ReadConstType readconst_types{};
|
||||||
|
CopyShaderData gs_copy_data;
|
||||||
|
u32 uses_patches{};
|
||||||
|
|
||||||
u64 pgm_hash{};
|
|
||||||
VAddr pgm_base;
|
VAddr pgm_base;
|
||||||
bool has_storage_images{};
|
bool has_storage_images{};
|
||||||
bool has_discard{};
|
bool has_discard{};
|
||||||
bool has_bitwise_xor{};
|
|
||||||
bool has_image_gather{};
|
bool has_image_gather{};
|
||||||
bool has_image_query{};
|
bool has_image_query{};
|
||||||
bool uses_buffer_atomic_float_min_max{};
|
bool uses_buffer_atomic_float_min_max{};
|
||||||
@ -125,20 +151,12 @@ struct Info {
|
|||||||
bool stores_tess_level_outer{};
|
bool stores_tess_level_outer{};
|
||||||
bool stores_tess_level_inner{};
|
bool stores_tess_level_inner{};
|
||||||
bool translation_failed{};
|
bool translation_failed{};
|
||||||
u8 mrt_mask{0u};
|
|
||||||
bool has_fetch_shader{false};
|
|
||||||
u32 fetch_shader_sgpr_base{0u};
|
|
||||||
|
|
||||||
enum class ReadConstType {
|
std::array<Interpolation, IR::NumParams> fs_interpolation{};
|
||||||
None = 0,
|
|
||||||
Immediate = 1 << 0,
|
|
||||||
Dynamic = 1 << 1,
|
|
||||||
};
|
|
||||||
ReadConstType readconst_types{};
|
|
||||||
bool uses_dma{};
|
|
||||||
|
|
||||||
explicit Info(Stage stage_, LogicalStage l_stage_, ShaderParams params)
|
Info() = default;
|
||||||
: stage{stage_}, l_stage{l_stage_}, pgm_hash{params.hash}, pgm_base{params.Base()},
|
Info(Stage stage_, LogicalStage l_stage_, ShaderParams params)
|
||||||
|
: InfoPersistent(stage_, l_stage_, params.hash), pgm_base{params.Base()},
|
||||||
user_data{params.user_data} {}
|
user_data{params.user_data} {}
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
@ -192,6 +210,9 @@ struct Info {
|
|||||||
reinterpret_cast<TessellationDataConstantBuffer*>(tess_constants_addr),
|
reinterpret_cast<TessellationDataConstantBuffer*>(tess_constants_addr),
|
||||||
sizeof(tess_constants));
|
sizeof(tess_constants));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Serialize(Serialization::Archive& ar) const;
|
||||||
|
bool Deserialize(Serialization::Archive& ar);
|
||||||
};
|
};
|
||||||
DECLARE_ENUM_FLAG_OPERATORS(Info::ReadConstType);
|
DECLARE_ENUM_FLAG_OPERATORS(Info::ReadConstType);
|
||||||
|
|
||||||
|
|||||||
@ -28,6 +28,17 @@ using namespace Xbyak::util;
|
|||||||
static Xbyak::CodeGenerator g_srt_codegen(32_MB);
|
static Xbyak::CodeGenerator g_srt_codegen(32_MB);
|
||||||
static const u8* g_srt_codegen_start = nullptr;
|
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 {
|
namespace {
|
||||||
|
|
||||||
static void DumpSrtProgram(const Shader::Info& info, const u8* code, size_t codesize) {
|
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.ret();
|
||||||
c.ready();
|
c.ready();
|
||||||
|
|
||||||
|
info.srt_info.walker_func_size =
|
||||||
|
c.getCurr() - reinterpret_cast<const u8*>(info.srt_info.walker_func);
|
||||||
|
|
||||||
if (Config::dumpShaders()) {
|
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),
|
||||||
DumpSrtProgram(info, reinterpret_cast<const u8*>(info.srt_info.walker_func), codesize);
|
info.srt_info.walker_func_size);
|
||||||
}
|
}
|
||||||
|
|
||||||
info.srt_info.flattened_bufsize_dw = pass_info.dst_off_dw;
|
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
|
} // namespace
|
||||||
|
|
||||||
void HullShaderTransform(IR::Program& program, RuntimeInfo& runtime_info) {
|
void HullShaderTransform(IR::Program& program, const RuntimeInfo& runtime_info) {
|
||||||
const Info& info = program.info;
|
const Info& info = program.info;
|
||||||
|
|
||||||
for (IR::Block* block : program.blocks) {
|
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) {
|
void DomainShaderTransform(const IR::Program& program, const RuntimeInfo& runtime_info) {
|
||||||
Info& info = program.info;
|
const Info& info = program.info;
|
||||||
|
|
||||||
for (IR::Block* block : program.blocks) {
|
for (IR::Block* block : program.blocks) {
|
||||||
for (IR::Inst& inst : block->Instructions()) {
|
for (IR::Inst& inst : block->Instructions()) {
|
||||||
|
|||||||
@ -24,8 +24,8 @@ void LowerBufferFormatToRaw(IR::Program& program);
|
|||||||
void LowerFp64ToFp32(IR::Program& program);
|
void LowerFp64ToFp32(IR::Program& program);
|
||||||
void RingAccessElimination(const IR::Program& program, const RuntimeInfo& runtime_info);
|
void RingAccessElimination(const IR::Program& program, const RuntimeInfo& runtime_info);
|
||||||
void TessellationPreprocess(IR::Program& program, RuntimeInfo& runtime_info);
|
void TessellationPreprocess(IR::Program& program, RuntimeInfo& runtime_info);
|
||||||
void HullShaderTransform(IR::Program& program, RuntimeInfo& runtime_info);
|
void HullShaderTransform(IR::Program& program, const RuntimeInfo& runtime_info);
|
||||||
void DomainShaderTransform(IR::Program& program, RuntimeInfo& runtime_info);
|
void DomainShaderTransform(const IR::Program& program, const RuntimeInfo& runtime_info);
|
||||||
void SharedMemoryBarrierPass(IR::Program& program, const RuntimeInfo& runtime_info,
|
void SharedMemoryBarrierPass(IR::Program& program, const RuntimeInfo& runtime_info,
|
||||||
const Profile& profile);
|
const Profile& profile);
|
||||||
void SharedMemorySimplifyPass(IR::Program& program, 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 ...
|
// buffer_load_format_xyz v[8:10], v1, s[32:35], 0 ...
|
||||||
// is used to define an inline buffer resource
|
// is used to define an inline buffer resource
|
||||||
std::array<u64, 2> raw;
|
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;
|
raw[1] = handle->Arg(2).U32() | u64(handle->Arg(3).U32()) << 32;
|
||||||
const auto buffer = std::bit_cast<AmdGpu::Buffer>(raw);
|
const auto buffer = std::bit_cast<AmdGpu::Buffer>(raw);
|
||||||
buffer_binding = descriptors.Add(BufferResource{
|
buffer_binding = descriptors.Add(BufferResource{
|
||||||
|
|||||||
@ -7,9 +7,14 @@
|
|||||||
#include <boost/container/small_vector.hpp>
|
#include <boost/container/small_vector.hpp>
|
||||||
#include "common/types.h"
|
#include "common/types.h"
|
||||||
|
|
||||||
|
namespace Serialization {
|
||||||
|
struct Archive;
|
||||||
|
}
|
||||||
|
|
||||||
namespace Shader {
|
namespace Shader {
|
||||||
|
|
||||||
using PFN_SrtWalker = void PS4_SYSV_ABI (*)(const u32* /*user_data*/, u32* /*flat_dst*/);
|
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 {
|
struct PersistentSrtInfo {
|
||||||
// Special case when fetch shader uses step rates.
|
// Special case when fetch shader uses step rates.
|
||||||
@ -20,7 +25,11 @@ struct PersistentSrtInfo {
|
|||||||
};
|
};
|
||||||
|
|
||||||
PFN_SrtWalker walker_func{};
|
PFN_SrtWalker walker_func{};
|
||||||
|
size_t walker_func_size{};
|
||||||
u32 flattened_bufsize_dw = 16; // NumUserDataRegs
|
u32 flattened_bufsize_dw = 16; // NumUserDataRegs
|
||||||
|
|
||||||
|
void Serialize(Serialization::Archive& ar) const;
|
||||||
|
bool Deserialize(Serialization::Archive& ar);
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace Shader
|
} // namespace Shader
|
||||||
|
|||||||
@ -29,7 +29,7 @@ IR::BlockList GenerateBlocks(const IR::AbstractSyntaxList& syntax_list) {
|
|||||||
return blocks;
|
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) {
|
RuntimeInfo& runtime_info, const Profile& profile) {
|
||||||
// Ensure first instruction is expected.
|
// Ensure first instruction is expected.
|
||||||
constexpr u32 token_mov_vcchi = 0xBEEB03FF;
|
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};
|
Gcn::CFG cfg{gcn_block_pool, program.ins_list};
|
||||||
|
|
||||||
// Structurize control flow graph and create program.
|
// Structurize control flow graph and create program.
|
||||||
program.syntax_list = Shader::Gcn::BuildASL(pools.inst_pool, pools.block_pool, cfg,
|
program.syntax_list =
|
||||||
program.info, runtime_info, profile);
|
Shader::Gcn::BuildASL(pools.inst_pool, pools.block_pool, cfg, info, runtime_info, profile);
|
||||||
program.blocks = GenerateBlocks(program.syntax_list);
|
program.blocks = GenerateBlocks(program.syntax_list);
|
||||||
program.post_order_blocks = Shader::IR::PostOrder(program.syntax_list.front());
|
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,
|
[[nodiscard]] IR::Program TranslateProgram(const std::span<const u32>& code, Pools& pools,
|
||||||
RuntimeInfo& runtime_info, const Profile& profile);
|
Info& info, RuntimeInfo& runtime_info,
|
||||||
|
const Profile& profile);
|
||||||
|
|
||||||
} // namespace Shader
|
} // namespace Shader
|
||||||
|
|||||||
@ -53,8 +53,15 @@ struct BufferResource {
|
|||||||
}
|
}
|
||||||
|
|
||||||
constexpr AmdGpu::Buffer GetSharp(const auto& info) const noexcept {
|
constexpr AmdGpu::Buffer GetSharp(const auto& info) const noexcept {
|
||||||
const auto buffer =
|
AmdGpu::Buffer buffer{};
|
||||||
inline_cbuf ? inline_cbuf : info.template ReadUdSharp<AmdGpu::Buffer>(sharp_idx);
|
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()) {
|
if (!buffer.Valid()) {
|
||||||
LOG_DEBUG(Render, "Encountered invalid buffer sharp");
|
LOG_DEBUG(Render, "Encountered invalid buffer sharp");
|
||||||
return AmdGpu::Buffer::Null();
|
return AmdGpu::Buffer::Null();
|
||||||
|
|||||||
@ -159,7 +159,8 @@ struct GeometryRuntimeInfo {
|
|||||||
return num_outputs == other.num_outputs && outputs == other.outputs && num_invocations &&
|
return num_outputs == other.num_outputs && outputs == other.outputs && num_invocations &&
|
||||||
other.num_invocations && output_vertices == other.output_vertices &&
|
other.num_invocations && output_vertices == other.output_vertices &&
|
||||||
in_primitive == other.in_primitive &&
|
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;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -230,6 +230,8 @@ struct StageSpecialization {
|
|||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
u64 Hash() const;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace Shader
|
} // namespace Shader
|
||||||
|
|||||||
@ -79,10 +79,10 @@ enum class NumberFormat : u32 {
|
|||||||
Ubscaled = 13,
|
Ubscaled = 13,
|
||||||
};
|
};
|
||||||
|
|
||||||
enum class NumberClass {
|
enum class NumberClass : u8 {
|
||||||
Float,
|
Float = 0,
|
||||||
Sint,
|
Sint = 1,
|
||||||
Uint,
|
Uint = 2,
|
||||||
};
|
};
|
||||||
|
|
||||||
enum class CompSwizzle : u8 {
|
enum class CompSwizzle : u8 {
|
||||||
|
|||||||
@ -13,7 +13,8 @@ namespace Vulkan {
|
|||||||
ComputePipeline::ComputePipeline(const Instance& instance, Scheduler& scheduler,
|
ComputePipeline::ComputePipeline(const Instance& instance, Scheduler& scheduler,
|
||||||
DescriptorHeap& desc_heap, const Shader::Profile& profile,
|
DescriptorHeap& desc_heap, const Shader::Profile& profile,
|
||||||
vk::PipelineCache pipeline_cache, ComputePipelineKey compute_key_,
|
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},
|
: Pipeline{instance, scheduler, desc_heap, profile, pipeline_cache, true},
|
||||||
compute_key{compute_key_} {
|
compute_key{compute_key_} {
|
||||||
auto& info = stages[int(Shader::LogicalStage::Compute)];
|
auto& info = stages[int(Shader::LogicalStage::Compute)];
|
||||||
@ -29,7 +30,7 @@ ComputePipeline::ComputePipeline(const Instance& instance, Scheduler& scheduler,
|
|||||||
u32 binding{};
|
u32 binding{};
|
||||||
boost::container::small_vector<vk::DescriptorSetLayoutBinding, 32> bindings;
|
boost::container::small_vector<vk::DescriptorSetLayoutBinding, 32> bindings;
|
||||||
for (const auto& buffer : info->buffers) {
|
for (const auto& buffer : info->buffers) {
|
||||||
const auto sharp = buffer.GetSharp(*info);
|
const auto sharp = preloading ? AmdGpu::Buffer{} : buffer.GetSharp(*info); // Comment
|
||||||
bindings.push_back({
|
bindings.push_back({
|
||||||
.binding = binding++,
|
.binding = binding++,
|
||||||
.descriptorType = buffer.IsStorage(sharp) ? vk::DescriptorType::eStorageBuffer
|
.descriptorType = buffer.IsStorage(sharp) ? vk::DescriptorType::eStorageBuffer
|
||||||
|
|||||||
@ -11,6 +11,10 @@ class BufferCache;
|
|||||||
class TextureCache;
|
class TextureCache;
|
||||||
} // namespace VideoCore
|
} // namespace VideoCore
|
||||||
|
|
||||||
|
namespace Serialization {
|
||||||
|
struct Archive;
|
||||||
|
}
|
||||||
|
|
||||||
namespace Vulkan {
|
namespace Vulkan {
|
||||||
|
|
||||||
class Instance;
|
class Instance;
|
||||||
@ -26,14 +30,24 @@ struct ComputePipelineKey {
|
|||||||
friend bool operator!=(const ComputePipelineKey& lhs, const ComputePipelineKey& rhs) {
|
friend bool operator!=(const ComputePipelineKey& lhs, const ComputePipelineKey& rhs) {
|
||||||
return !(lhs == rhs);
|
return !(lhs == rhs);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Serialize(Serialization::Archive& ar) const;
|
||||||
|
bool Deserialize(Serialization::Archive& ar);
|
||||||
};
|
};
|
||||||
|
|
||||||
class ComputePipeline : public Pipeline {
|
class ComputePipeline : public Pipeline {
|
||||||
public:
|
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,
|
ComputePipeline(const Instance& instance, Scheduler& scheduler, DescriptorHeap& desc_heap,
|
||||||
const Shader::Profile& profile, vk::PipelineCache pipeline_cache,
|
const Shader::Profile& profile, vk::PipelineCache pipeline_cache,
|
||||||
ComputePipelineKey compute_key, const Shader::Info& info,
|
ComputePipelineKey compute_key, const Shader::Info& info,
|
||||||
vk::ShaderModule module);
|
vk::ShaderModule module, SerializationSupport& sdata, bool preloading);
|
||||||
~ComputePipeline();
|
~ComputePipeline();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|||||||
@ -41,12 +41,12 @@ GraphicsPipeline::GraphicsPipeline(
|
|||||||
vk::PipelineCache pipeline_cache, std::span<const Shader::Info*, MaxShaderStages> infos,
|
vk::PipelineCache pipeline_cache, std::span<const Shader::Info*, MaxShaderStages> infos,
|
||||||
std::span<const Shader::RuntimeInfo, MaxShaderStages> runtime_infos,
|
std::span<const Shader::RuntimeInfo, MaxShaderStages> runtime_infos,
|
||||||
std::optional<const Shader::Gcn::FetchShaderData> fetch_shader_,
|
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_},
|
: Pipeline{instance, scheduler, desc_heap, profile, pipeline_cache}, key{key_},
|
||||||
fetch_shader{std::move(fetch_shader_)} {
|
fetch_shader{std::move(fetch_shader_)} {
|
||||||
const vk::Device device = instance.GetDevice();
|
const vk::Device device = instance.GetDevice();
|
||||||
std::ranges::copy(infos, stages.begin());
|
std::ranges::copy(infos, stages.begin());
|
||||||
BuildDescSetLayout();
|
BuildDescSetLayout(preloading);
|
||||||
const auto debug_str = GetDebugString();
|
const auto debug_str = GetDebugString();
|
||||||
|
|
||||||
const vk::PushConstantRange push_constants = {
|
const vk::PushConstantRange push_constants = {
|
||||||
@ -68,27 +68,26 @@ GraphicsPipeline::GraphicsPipeline(
|
|||||||
pipeline_layout = std::move(layout);
|
pipeline_layout = std::move(layout);
|
||||||
SetObjectName(device, *pipeline_layout, "Graphics PipelineLayout {}", debug_str);
|
SetObjectName(device, *pipeline_layout, "Graphics PipelineLayout {}", debug_str);
|
||||||
|
|
||||||
VertexInputs<vk::VertexInputAttributeDescription> vertex_attributes;
|
if (!preloading) {
|
||||||
VertexInputs<vk::VertexInputBindingDescription> vertex_bindings;
|
VertexInputs<AmdGpu::Buffer> guest_buffers;
|
||||||
VertexInputs<vk::VertexInputBindingDivisorDescriptionEXT> divisors;
|
if (!instance.IsVertexInputDynamicState()) {
|
||||||
VertexInputs<AmdGpu::Buffer> guest_buffers;
|
const auto& vs_info = runtime_infos[u32(Shader::LogicalStage::Vertex)].vs_info;
|
||||||
if (!instance.IsVertexInputDynamicState()) {
|
GetVertexInputs(sdata.vertex_attributes, sdata.vertex_bindings, sdata.divisors,
|
||||||
const auto& vs_info = runtime_infos[u32(Shader::LogicalStage::Vertex)].vs_info;
|
guest_buffers, vs_info.step_rate_0, vs_info.step_rate_1);
|
||||||
GetVertexInputs(vertex_attributes, vertex_bindings, divisors, guest_buffers,
|
}
|
||||||
vs_info.step_rate_0, vs_info.step_rate_1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const vk::PipelineVertexInputDivisorStateCreateInfo divisor_state = {
|
const vk::PipelineVertexInputDivisorStateCreateInfo divisor_state = {
|
||||||
.vertexBindingDivisorCount = static_cast<u32>(divisors.size()),
|
.vertexBindingDivisorCount = static_cast<u32>(sdata.divisors.size()),
|
||||||
.pVertexBindingDivisors = divisors.data(),
|
.pVertexBindingDivisors = sdata.divisors.data(),
|
||||||
};
|
};
|
||||||
|
|
||||||
const vk::PipelineVertexInputStateCreateInfo vertex_input_info = {
|
const vk::PipelineVertexInputStateCreateInfo vertex_input_info = {
|
||||||
.pNext = divisors.empty() ? nullptr : &divisor_state,
|
.pNext = sdata.divisors.empty() ? nullptr : &divisor_state,
|
||||||
.vertexBindingDescriptionCount = static_cast<u32>(vertex_bindings.size()),
|
.vertexBindingDescriptionCount = static_cast<u32>(sdata.vertex_bindings.size()),
|
||||||
.pVertexBindingDescriptions = vertex_bindings.data(),
|
.pVertexBindingDescriptions = sdata.vertex_bindings.data(),
|
||||||
.vertexAttributeDescriptionCount = static_cast<u32>(vertex_attributes.size()),
|
.vertexAttributeDescriptionCount = static_cast<u32>(sdata.vertex_attributes.size()),
|
||||||
.pVertexAttributeDescriptions = vertex_attributes.data(),
|
.pVertexAttributeDescriptions = sdata.vertex_attributes.data(),
|
||||||
};
|
};
|
||||||
|
|
||||||
const auto topology = LiverpoolToVK::PrimitiveType(key.prim_type);
|
const auto topology = LiverpoolToVK::PrimitiveType(key.prim_type);
|
||||||
@ -102,7 +101,6 @@ GraphicsPipeline::GraphicsPipeline(
|
|||||||
|
|
||||||
const bool is_rect_list = key.prim_type == AmdGpu::PrimitiveType::RectList;
|
const bool is_rect_list = key.prim_type == AmdGpu::PrimitiveType::RectList;
|
||||||
const bool is_quad_list = key.prim_type == AmdGpu::PrimitiveType::QuadList;
|
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 = {
|
const vk::PipelineTessellationStateCreateInfo tessellation_state = {
|
||||||
.patchControlPoints = is_rect_list ? 3U : (is_quad_list ? 4U : key.patch_control_points),
|
.patchControlPoints = is_rect_list ? 3U : (is_quad_list ? 4U : key.patch_control_points),
|
||||||
};
|
};
|
||||||
@ -132,12 +130,15 @@ GraphicsPipeline::GraphicsPipeline(
|
|||||||
raster_chain.unlink<vk::PipelineRasterizationDepthClipStateCreateInfoEXT>();
|
raster_chain.unlink<vk::PipelineRasterizationDepthClipStateCreateInfoEXT>();
|
||||||
}
|
}
|
||||||
|
|
||||||
const vk::PipelineMultisampleStateCreateInfo multisampling = {
|
if (!preloading) {
|
||||||
.rasterizationSamples = LiverpoolToVK::NumSamples(
|
const auto& fs_info = runtime_infos[u32(Shader::LogicalStage::Fragment)].fs_info;
|
||||||
key.num_samples, instance.GetColorSampleCounts() & instance.GetDepthSampleCounts()),
|
sdata.multisampling = {
|
||||||
.sampleShadingEnable =
|
.rasterizationSamples = LiverpoolToVK::NumSamples(
|
||||||
fs_info.addr_flags.persp_sample_ena || fs_info.addr_flags.linear_sample_ena,
|
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 = {
|
const vk::PipelineViewportDepthClipControlCreateInfoEXT clip_control = {
|
||||||
.negativeOneToOne = key.clip_space == AmdGpu::ClipSpace::MinusWToW,
|
.negativeOneToOne = key.clip_space == AmdGpu::ClipSpace::MinusWToW,
|
||||||
@ -171,7 +172,7 @@ GraphicsPipeline::GraphicsPipeline(
|
|||||||
}
|
}
|
||||||
if (instance.IsVertexInputDynamicState()) {
|
if (instance.IsVertexInputDynamicState()) {
|
||||||
dynamic_states.push_back(vk::DynamicState::eVertexInputEXT);
|
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);
|
dynamic_states.push_back(vk::DynamicState::eVertexInputBindingStride);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -207,10 +208,13 @@ GraphicsPipeline::GraphicsPipeline(
|
|||||||
});
|
});
|
||||||
} else if (is_rect_list || is_quad_list) {
|
} else if (is_rect_list || is_quad_list) {
|
||||||
const auto type = is_quad_list ? AuxShaderType::QuadListTCS : AuxShaderType::RectListTCS;
|
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{
|
shader_stages.emplace_back(vk::PipelineShaderStageCreateInfo{
|
||||||
.stage = vk::ShaderStageFlagBits::eTessellationControl,
|
.stage = vk::ShaderStageFlagBits::eTessellationControl,
|
||||||
.module = CompileSPV(tcs, instance.GetDevice()),
|
.module = CompileSPV(sdata.tcs, instance.GetDevice()),
|
||||||
.pName = "main",
|
.pName = "main",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -222,11 +226,14 @@ GraphicsPipeline::GraphicsPipeline(
|
|||||||
.pName = "main",
|
.pName = "main",
|
||||||
});
|
});
|
||||||
} else if (is_rect_list || is_quad_list) {
|
} else if (is_rect_list || is_quad_list) {
|
||||||
auto tes =
|
if (!preloading) {
|
||||||
Shader::Backend::SPIRV::EmitAuxilaryTessShader(AuxShaderType::PassthroughTES, fs_info);
|
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{
|
shader_stages.emplace_back(vk::PipelineShaderStageCreateInfo{
|
||||||
.stage = vk::ShaderStageFlagBits::eTessellationEvaluation,
|
.stage = vk::ShaderStageFlagBits::eTessellationEvaluation,
|
||||||
.module = CompileSPV(tes, instance.GetDevice()),
|
.module = CompileSPV(sdata.tes, instance.GetDevice()),
|
||||||
.pName = "main",
|
.pName = "main",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -367,7 +374,7 @@ GraphicsPipeline::GraphicsPipeline(
|
|||||||
.pTessellationState = &tessellation_state,
|
.pTessellationState = &tessellation_state,
|
||||||
.pViewportState = &viewport_info,
|
.pViewportState = &viewport_info,
|
||||||
.pRasterizationState = &raster_chain.get(),
|
.pRasterizationState = &raster_chain.get(),
|
||||||
.pMultisampleState = &multisampling,
|
.pMultisampleState = &sdata.multisampling,
|
||||||
.pColorBlendState = &color_blending,
|
.pColorBlendState = &color_blending,
|
||||||
.pDynamicState = &dynamic_info,
|
.pDynamicState = &dynamic_info,
|
||||||
.layout = *pipeline_layout,
|
.layout = *pipeline_layout,
|
||||||
@ -435,7 +442,7 @@ template void GraphicsPipeline::GetVertexInputs(
|
|||||||
VertexInputs<vk::VertexInputBindingDivisorDescriptionEXT>& divisors,
|
VertexInputs<vk::VertexInputBindingDivisorDescriptionEXT>& divisors,
|
||||||
VertexInputs<AmdGpu::Buffer>& guest_buffers, u32 step_rate_0, u32 step_rate_1) const;
|
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;
|
boost::container::small_vector<vk::DescriptorSetLayoutBinding, 32> bindings;
|
||||||
u32 binding{};
|
u32 binding{};
|
||||||
|
|
||||||
@ -445,7 +452,7 @@ void GraphicsPipeline::BuildDescSetLayout() {
|
|||||||
}
|
}
|
||||||
const auto stage_bit = LogicalStageToStageBit[u32(stage->l_stage)];
|
const auto stage_bit = LogicalStageToStageBit[u32(stage->l_stage)];
|
||||||
for (const auto& buffer : stage->buffers) {
|
for (const auto& buffer : stage->buffers) {
|
||||||
const auto sharp = buffer.GetSharp(*stage);
|
const auto sharp = preloading ? AmdGpu::Buffer{} : buffer.GetSharp(*stage); // Comment
|
||||||
bindings.push_back({
|
bindings.push_back({
|
||||||
.binding = binding++,
|
.binding = binding++,
|
||||||
.descriptorType = buffer.IsStorage(sharp) ? vk::DescriptorType::eStorageBuffer
|
.descriptorType = buffer.IsStorage(sharp) ? vk::DescriptorType::eStorageBuffer
|
||||||
|
|||||||
@ -63,17 +63,33 @@ struct GraphicsPipelineKey {
|
|||||||
bool operator==(const GraphicsPipelineKey& key) const noexcept {
|
bool operator==(const GraphicsPipelineKey& key) const noexcept {
|
||||||
return std::memcmp(this, &key, sizeof(key)) == 0;
|
return std::memcmp(this, &key, sizeof(key)) == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Serialize(Serialization::Archive& ar) const;
|
||||||
|
bool Deserialize(Serialization::Archive& ar);
|
||||||
};
|
};
|
||||||
|
|
||||||
class GraphicsPipeline : public Pipeline {
|
class GraphicsPipeline : public Pipeline {
|
||||||
public:
|
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,
|
GraphicsPipeline(const Instance& instance, Scheduler& scheduler, DescriptorHeap& desc_heap,
|
||||||
const Shader::Profile& profile, const GraphicsPipelineKey& key,
|
const Shader::Profile& profile, const GraphicsPipelineKey& key,
|
||||||
vk::PipelineCache pipeline_cache,
|
vk::PipelineCache pipeline_cache,
|
||||||
std::span<const Shader::Info*, MaxShaderStages> stages,
|
std::span<const Shader::Info*, MaxShaderStages> stages,
|
||||||
std::span<const Shader::RuntimeInfo, MaxShaderStages> runtime_infos,
|
std::span<const Shader::RuntimeInfo, MaxShaderStages> runtime_infos,
|
||||||
std::optional<const Shader::Gcn::FetchShaderData> fetch_shader,
|
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();
|
~GraphicsPipeline();
|
||||||
|
|
||||||
const std::optional<const Shader::Gcn::FetchShaderData>& GetFetchShader() const noexcept {
|
const std::optional<const Shader::Gcn::FetchShaderData>& GetFetchShader() const noexcept {
|
||||||
@ -92,7 +108,7 @@ public:
|
|||||||
u32 step_rate_1) const;
|
u32 step_rate_1) const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void BuildDescSetLayout();
|
void BuildDescSetLayout(bool preloading);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
GraphicsPipelineKey key;
|
GraphicsPipelineKey key;
|
||||||
|
|||||||
@ -16,6 +16,8 @@
|
|||||||
#include "video_core/renderer_vulkan/liverpool_to_vk.h"
|
#include "video_core/renderer_vulkan/liverpool_to_vk.h"
|
||||||
#include "video_core/renderer_vulkan/vk_instance.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_cache.h"
|
||||||
|
#include "video_core/renderer_vulkan/vk_pipeline_serialization.h"
|
||||||
|
#include "video_core/renderer_vulkan/vk_pipeline_storage.h"
|
||||||
#include "video_core/renderer_vulkan/vk_scheduler.h"
|
#include "video_core/renderer_vulkan/vk_scheduler.h"
|
||||||
#include "video_core/renderer_vulkan/vk_shader_util.h"
|
#include "video_core/renderer_vulkan/vk_shader_util.h"
|
||||||
|
|
||||||
@ -266,6 +268,9 @@ PipelineCache::PipelineCache(const Instance& instance_, Scheduler& scheduler_,
|
|||||||
.max_viewport_height = instance.GetMaxViewportHeight(),
|
.max_viewport_height = instance.GetMaxViewportHeight(),
|
||||||
.max_shared_memory_size = instance.MaxComputeSharedMemorySize(),
|
.max_shared_memory_size = instance.MaxComputeSharedMemorySize(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
WarmUp();
|
||||||
|
|
||||||
auto [cache_result, cache] = instance.GetDevice().createPipelineCacheUnique({});
|
auto [cache_result, cache] = instance.GetDevice().createPipelineCacheUnique({});
|
||||||
ASSERT_MSG(cache_result == vk::Result::eSuccess, "Failed to create pipeline cache: {}",
|
ASSERT_MSG(cache_result == vk::Result::eSuccess, "Failed to create pipeline cache: {}",
|
||||||
vk::to_string(cache_result));
|
vk::to_string(cache_result));
|
||||||
@ -283,9 +288,13 @@ const GraphicsPipeline* PipelineCache::GetGraphicsPipeline() {
|
|||||||
const auto pipeline_hash = std::hash<GraphicsPipelineKey>{}(graphics_key);
|
const auto pipeline_hash = std::hash<GraphicsPipelineKey>{}(graphics_key);
|
||||||
LOG_INFO(Render_Vulkan, "Compiling graphics pipeline {:#x}", pipeline_hash);
|
LOG_INFO(Render_Vulkan, "Compiling graphics pipeline {:#x}", pipeline_hash);
|
||||||
|
|
||||||
it.value() = std::make_unique<GraphicsPipeline>(instance, scheduler, desc_heap, profile,
|
GraphicsPipeline::SerializationSupport sdata{};
|
||||||
graphics_key, *pipeline_cache, infos,
|
it.value() = std::make_unique<GraphicsPipeline>(
|
||||||
runtime_infos, fetch_shader, modules);
|
instance, scheduler, desc_heap, profile, graphics_key, *pipeline_cache, infos,
|
||||||
|
runtime_infos, fetch_shader, modules, sdata, false);
|
||||||
|
|
||||||
|
RegisterPipelineData(graphics_key, pipeline_hash, sdata);
|
||||||
|
|
||||||
if (Config::collectShadersForDebug()) {
|
if (Config::collectShadersForDebug()) {
|
||||||
for (auto stage = 0; stage < MaxShaderStages; ++stage) {
|
for (auto stage = 0; stage < MaxShaderStages; ++stage) {
|
||||||
if (infos[stage]) {
|
if (infos[stage]) {
|
||||||
@ -294,6 +303,7 @@ const GraphicsPipeline* PipelineCache::GetGraphicsPipeline() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
fetch_shader.reset();
|
||||||
}
|
}
|
||||||
return it->second.get();
|
return it->second.get();
|
||||||
}
|
}
|
||||||
@ -307,9 +317,12 @@ const ComputePipeline* PipelineCache::GetComputePipeline() {
|
|||||||
const auto pipeline_hash = std::hash<ComputePipelineKey>{}(compute_key);
|
const auto pipeline_hash = std::hash<ComputePipelineKey>{}(compute_key);
|
||||||
LOG_INFO(Render_Vulkan, "Compiling compute pipeline {:#x}", pipeline_hash);
|
LOG_INFO(Render_Vulkan, "Compiling compute pipeline {:#x}", pipeline_hash);
|
||||||
|
|
||||||
it.value() =
|
ComputePipeline::SerializationSupport sdata{};
|
||||||
std::make_unique<ComputePipeline>(instance, scheduler, desc_heap, profile,
|
it.value() = std::make_unique<ComputePipeline>(instance, scheduler, desc_heap, profile,
|
||||||
*pipeline_cache, compute_key, *infos[0], modules[0]);
|
*pipeline_cache, compute_key, *infos[0],
|
||||||
|
modules[0], sdata, false);
|
||||||
|
RegisterPipelineData(compute_key, sdata);
|
||||||
|
|
||||||
if (Config::collectShadersForDebug()) {
|
if (Config::collectShadersForDebug()) {
|
||||||
auto& m = modules[0];
|
auto& m = modules[0];
|
||||||
module_related_pipelines[m].emplace_back(compute_key);
|
module_related_pipelines[m].emplace_back(compute_key);
|
||||||
@ -445,6 +458,7 @@ bool PipelineCache::RefreshGraphicsStages() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
infos.fill(nullptr);
|
infos.fill(nullptr);
|
||||||
|
modules.fill(nullptr);
|
||||||
bind_stage(Stage::Fragment, LogicalStage::Fragment);
|
bind_stage(Stage::Fragment, LogicalStage::Fragment);
|
||||||
|
|
||||||
const auto* fs_info = infos[static_cast<u32>(LogicalStage::Fragment)];
|
const auto* fs_info = infos[static_cast<u32>(LogicalStage::Fragment)];
|
||||||
@ -515,7 +529,7 @@ bool PipelineCache::RefreshComputeKey() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
vk::ShaderModule PipelineCache::CompileModule(Shader::Info& info, Shader::RuntimeInfo& runtime_info,
|
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) {
|
Shader::Backend::Bindings& binding) {
|
||||||
LOG_INFO(Render_Vulkan, "Compiling {} shader {:#x} {}", info.stage, info.pgm_hash,
|
LOG_INFO(Render_Vulkan, "Compiling {} shader {:#x} {}", info.stage, info.pgm_hash,
|
||||||
perm_idx != 0 ? "(permutation)" : "");
|
perm_idx != 0 ? "(permutation)" : "");
|
||||||
@ -536,6 +550,8 @@ vk::ShaderModule PipelineCache::CompileModule(Shader::Info& info, Shader::Runtim
|
|||||||
module = CompileSPV(spv, instance.GetDevice());
|
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);
|
const auto name = GetShaderName(info.stage, info.pgm_hash, perm_idx);
|
||||||
Vulkan::SetObjectName(instance.GetDevice(), module, name);
|
Vulkan::SetObjectName(instance.GetDevice(), module, name);
|
||||||
if (Config::collectShadersForDebug()) {
|
if (Config::collectShadersForDebug()) {
|
||||||
@ -546,7 +562,7 @@ vk::ShaderModule PipelineCache::CompileModule(Shader::Info& info, Shader::Runtim
|
|||||||
}
|
}
|
||||||
|
|
||||||
PipelineCache::Result PipelineCache::GetProgram(Stage stage, LogicalStage l_stage,
|
PipelineCache::Result PipelineCache::GetProgram(Stage stage, LogicalStage l_stage,
|
||||||
Shader::ShaderParams params,
|
const Shader::ShaderParams& params,
|
||||||
Shader::Backend::Bindings& binding) {
|
Shader::Backend::Bindings& binding) {
|
||||||
auto runtime_info = BuildRuntimeInfo(stage, l_stage);
|
auto runtime_info = BuildRuntimeInfo(stage, l_stage);
|
||||||
auto [it_pgm, new_program] = program_cache.try_emplace(params.hash);
|
auto [it_pgm, new_program] = program_cache.try_emplace(params.hash);
|
||||||
@ -556,31 +572,40 @@ PipelineCache::Result PipelineCache::GetProgram(Stage stage, LogicalStage l_stag
|
|||||||
auto start = binding;
|
auto start = binding;
|
||||||
const auto module = CompileModule(program->info, runtime_info, params.code, 0, binding);
|
const auto module = CompileModule(program->info, runtime_info, params.code, 0, binding);
|
||||||
const auto spec = Shader::StageSpecialization(program->info, runtime_info, profile, start);
|
const auto spec = Shader::StageSpecialization(program->info, runtime_info, profile, start);
|
||||||
program->AddPermut(module, std::move(spec));
|
const auto spec_hash = spec.Hash();
|
||||||
return std::make_tuple(&program->info, module, spec.fetch_shader_data,
|
const auto perm_hash = HashCombine(params.hash, 0);
|
||||||
HashCombine(params.hash, 0));
|
|
||||||
|
program->AddPermut(module, spec_hash);
|
||||||
|
RegisterShaderMeta(program->info, spec.fetch_shader_data, perm_hash, spec_hash, 0);
|
||||||
|
return std::make_tuple(&program->info, module, spec.fetch_shader_data, perm_hash);
|
||||||
}
|
}
|
||||||
it_pgm.value()->info.user_data = params.user_data;
|
|
||||||
|
|
||||||
auto& program = it_pgm.value();
|
auto& program = it_pgm.value();
|
||||||
auto& info = program->info;
|
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();
|
info.RefreshFlatBuf();
|
||||||
const auto spec = Shader::StageSpecialization(info, runtime_info, profile, binding);
|
const auto spec = Shader::StageSpecialization(info, runtime_info, profile, binding);
|
||||||
|
const auto spec_hash = spec.Hash();
|
||||||
|
|
||||||
size_t perm_idx = program->modules.size();
|
size_t perm_idx = program->modules.size();
|
||||||
|
u64 perm_hash = HashCombine(params.hash, perm_idx);
|
||||||
|
|
||||||
vk::ShaderModule module{};
|
vk::ShaderModule module{};
|
||||||
|
|
||||||
const auto it = std::ranges::find(program->modules, spec, &Program::Module::spec);
|
const auto it = std::ranges::find(program->modules, spec_hash, &Program::Module::spec_hash);
|
||||||
if (it == program->modules.end()) {
|
if (it == program->modules.end()) {
|
||||||
auto new_info = Shader::Info(stage, l_stage, params);
|
auto new_info = Shader::Info(stage, l_stage, params);
|
||||||
module = CompileModule(new_info, runtime_info, params.code, perm_idx, binding);
|
module = CompileModule(new_info, runtime_info, params.code, perm_idx, binding);
|
||||||
program->AddPermut(module, std::move(spec));
|
program->AddPermut(module, spec_hash);
|
||||||
|
RegisterShaderMeta(info, spec.fetch_shader_data, perm_hash, spec_hash, perm_idx);
|
||||||
} else {
|
} else {
|
||||||
info.AddBindings(binding);
|
info.AddBindings(binding);
|
||||||
module = it->module;
|
module = it->module;
|
||||||
perm_idx = std::distance(program->modules.begin(), it);
|
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,
|
return std::make_tuple(&program->info, module, spec.fetch_shader_data, perm_hash);
|
||||||
HashCombine(params.hash, perm_idx));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
std::optional<vk::ShaderModule> PipelineCache::ReplaceShader(vk::ShaderModule module,
|
std::optional<vk::ShaderModule> PipelineCache::ReplaceShader(vk::ShaderModule module,
|
||||||
@ -611,48 +636,4 @@ std::optional<vk::ShaderModule> PipelineCache::ReplaceShader(vk::ShaderModule mo
|
|||||||
return new_module;
|
return new_module;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string PipelineCache::GetShaderName(Shader::Stage stage, u64 hash,
|
|
||||||
std::optional<size_t> perm) {
|
|
||||||
if (perm) {
|
|
||||||
return fmt::format("{}_{:#018x}_{}", stage, hash, *perm);
|
|
||||||
}
|
|
||||||
return fmt::format("{}_{:#018x}", stage, hash);
|
|
||||||
}
|
|
||||||
|
|
||||||
void PipelineCache::DumpShader(std::span<const u32> code, u64 hash, Shader::Stage stage,
|
|
||||||
size_t perm_idx, std::string_view ext) {
|
|
||||||
if (!Config::dumpShaders()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
using namespace Common::FS;
|
|
||||||
const auto dump_dir = GetUserPath(PathType::ShaderDir) / "dumps";
|
|
||||||
if (!std::filesystem::exists(dump_dir)) {
|
|
||||||
std::filesystem::create_directories(dump_dir);
|
|
||||||
}
|
|
||||||
const auto filename = fmt::format("{}.{}", GetShaderName(stage, hash, perm_idx), ext);
|
|
||||||
const auto file = IOFile{dump_dir / filename, FileAccessMode::Create};
|
|
||||||
file.WriteSpan(code);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::optional<std::vector<u32>> PipelineCache::GetShaderPatch(u64 hash, Shader::Stage stage,
|
|
||||||
size_t perm_idx,
|
|
||||||
std::string_view ext) {
|
|
||||||
|
|
||||||
using namespace Common::FS;
|
|
||||||
const auto patch_dir = GetUserPath(PathType::ShaderDir) / "patch";
|
|
||||||
if (!std::filesystem::exists(patch_dir)) {
|
|
||||||
std::filesystem::create_directories(patch_dir);
|
|
||||||
}
|
|
||||||
const auto filename = fmt::format("{}.{}", GetShaderName(stage, hash, perm_idx), ext);
|
|
||||||
const auto filepath = patch_dir / filename;
|
|
||||||
if (!std::filesystem::exists(filepath)) {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
const auto file = IOFile{patch_dir / filename, FileAccessMode::Read};
|
|
||||||
std::vector<u32> code(file.GetSize() / sizeof(u32));
|
|
||||||
file.Read(code);
|
|
||||||
return code;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace Vulkan
|
} // namespace Vulkan
|
||||||
|
|||||||
@ -23,6 +23,10 @@ namespace AmdGpu {
|
|||||||
class Liverpool;
|
class Liverpool;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
namespace Serialization {
|
||||||
|
struct Archive;
|
||||||
|
}
|
||||||
|
|
||||||
namespace Shader {
|
namespace Shader {
|
||||||
struct Info;
|
struct Info;
|
||||||
}
|
}
|
||||||
@ -36,18 +40,24 @@ class ShaderCache;
|
|||||||
struct Program {
|
struct Program {
|
||||||
struct Module {
|
struct Module {
|
||||||
vk::ShaderModule module;
|
vk::ShaderModule module;
|
||||||
Shader::StageSpecialization spec;
|
u64 spec_hash;
|
||||||
};
|
};
|
||||||
using ModuleList = boost::container::small_vector<Module, 8>;
|
using ModuleList = boost::container::small_vector<Module, 8>;
|
||||||
|
|
||||||
Shader::Info info;
|
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} {}
|
: info{stage, l_stage, params} {}
|
||||||
|
|
||||||
void AddPermut(vk::ShaderModule module, const Shader::StageSpecialization&& spec) {
|
void AddPermut(vk::ShaderModule module, u64 spec_hash) {
|
||||||
modules.emplace_back(module, std::move(spec));
|
modules.emplace_back(module, spec_hash);
|
||||||
|
}
|
||||||
|
|
||||||
|
void InsertPermut(vk::ShaderModule module, u64 spec_hash, size_t perm_idx) {
|
||||||
|
modules.resize(std::max(modules.size(), perm_idx + 1));
|
||||||
|
modules[perm_idx] = {module, spec_hash};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -57,6 +67,13 @@ public:
|
|||||||
AmdGpu::Liverpool* liverpool);
|
AmdGpu::Liverpool* liverpool);
|
||||||
~PipelineCache();
|
~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 GraphicsPipeline* GetGraphicsPipeline();
|
||||||
|
|
||||||
const ComputePipeline* GetComputePipeline();
|
const ComputePipeline* GetComputePipeline();
|
||||||
@ -64,7 +81,7 @@ public:
|
|||||||
using Result = std::tuple<const Shader::Info*, vk::ShaderModule,
|
using Result = std::tuple<const Shader::Info*, vk::ShaderModule,
|
||||||
std::optional<Shader::Gcn::FetchShaderData>, u64>;
|
std::optional<Shader::Gcn::FetchShaderData>, u64>;
|
||||||
Result GetProgram(Shader::Stage stage, Shader::LogicalStage l_stage,
|
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::optional<vk::ShaderModule> ReplaceShader(vk::ShaderModule module,
|
||||||
std::span<const u32> spv_code);
|
std::span<const u32> spv_code);
|
||||||
@ -86,7 +103,7 @@ private:
|
|||||||
std::optional<std::vector<u32>> GetShaderPatch(u64 hash, Shader::Stage stage, size_t perm_idx,
|
std::optional<std::vector<u32>> GetShaderPatch(u64 hash, Shader::Stage stage, size_t perm_idx,
|
||||||
std::string_view ext);
|
std::string_view ext);
|
||||||
vk::ShaderModule CompileModule(Shader::Info& info, Shader::RuntimeInfo& runtime_info,
|
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);
|
Shader::Backend::Bindings& binding);
|
||||||
const Shader::RuntimeInfo& BuildRuntimeInfo(Shader::Stage stage, Shader::LogicalStage l_stage);
|
const Shader::RuntimeInfo& BuildRuntimeInfo(Shader::Stage stage, Shader::LogicalStage l_stage);
|
||||||
|
|
||||||
|
|||||||
565
src/video_core/renderer_vulkan/vk_pipeline_serialization.cpp
Normal file
565
src/video_core/renderer_vulkan/vk_pipeline_serialization.cpp
Normal file
@ -0,0 +1,565 @@
|
|||||||
|
// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#include "common/config.h"
|
||||||
|
#include "common/hash.h"
|
||||||
|
#include "shader_recompiler/frontend/fetch_shader.h"
|
||||||
|
#include "shader_recompiler/info.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_storage.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 = 0u;
|
||||||
|
static constexpr u32 ShaderMetaVersion = 0u;
|
||||||
|
static constexpr u32 PipelineKeyVersion = 0u;
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IsEoS() const {
|
||||||
|
return offset >= container.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
Archive() = default;
|
||||||
|
explicit Archive(std::vector<u8>&& v) : container{v} {}
|
||||||
|
|
||||||
|
u32 offset{};
|
||||||
|
std::vector<u8> container{};
|
||||||
|
};
|
||||||
|
|
||||||
|
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>
|
||||||
|
void Write(T value) {
|
||||||
|
const auto size = sizeof(value);
|
||||||
|
Write(&value, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
void Write(const std::vector<T>& v) {
|
||||||
|
Write(v.size());
|
||||||
|
for (const auto& elem : v) {
|
||||||
|
Write(elem);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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>
|
||||||
|
void Read(T& value) {
|
||||||
|
const auto size = sizeof(T);
|
||||||
|
Read(&value, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
void Read(std::vector<T>& v) {
|
||||||
|
size_t num_elements{};
|
||||||
|
Read(num_elements);
|
||||||
|
v.resize(num_elements);
|
||||||
|
for (auto& elem : v) {
|
||||||
|
Read(elem);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Reader() = delete;
|
||||||
|
explicit Reader(Archive& ar_) : ar{ar_} {}
|
||||||
|
|
||||||
|
Archive& ar;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // 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("{:#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("{:#018x}", hash), ar.TakeOff());
|
||||||
|
}
|
||||||
|
|
||||||
|
void RegisterShaderMeta(const Shader::Info& info,
|
||||||
|
const std::optional<Shader::Gcn::FetchShaderData>& fetch_shader_data,
|
||||||
|
u64 perm_hash, u64 spec_hash, size_t perm_idx) {
|
||||||
|
if (!Storage::DataBase::Instance().IsOpened()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Serialization::Archive ar;
|
||||||
|
Serialization::Writer meta{ar};
|
||||||
|
|
||||||
|
meta.Write(Serialization::ShaderMetaVersion);
|
||||||
|
|
||||||
|
meta.Write(perm_hash);
|
||||||
|
meta.Write(perm_idx);
|
||||||
|
meta.Write(spec_hash);
|
||||||
|
|
||||||
|
info.Serialize(ar);
|
||||||
|
|
||||||
|
if (fetch_shader_data) {
|
||||||
|
meta.Write(sizeof(*fetch_shader_data));
|
||||||
|
fetch_shader_data->Serialize(ar);
|
||||||
|
} else {
|
||||||
|
meta.Write(size_t{0});
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
|
size_t& spec_hash, size_t& perm_idx) {
|
||||||
|
Serialization::Reader meta{ar};
|
||||||
|
|
||||||
|
u64 perm_hash_ar{};
|
||||||
|
meta.Read(perm_hash_ar);
|
||||||
|
assert(perm_hash == perm_hash_ar);
|
||||||
|
meta.Read(perm_idx);
|
||||||
|
meta.Read(spec_hash);
|
||||||
|
|
||||||
|
info.Deserialize(ar);
|
||||||
|
|
||||||
|
u64 fetch_data_size{};
|
||||||
|
meta.Read(fetch_data_size);
|
||||||
|
|
||||||
|
if (fetch_data_size) {
|
||||||
|
Shader::Gcn::FetchShaderData fetch_data;
|
||||||
|
fetch_data.Deserialize(ar);
|
||||||
|
fetch_shader_data = fetch_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 {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ComputePipeline::SerializationSupport::Deserialize(Serialization::Archive& ar) {
|
||||||
|
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)};
|
||||||
|
Serialization::Reader meta{meta_ar};
|
||||||
|
|
||||||
|
u32 meta_version{};
|
||||||
|
meta.Read(meta_version);
|
||||||
|
if (meta_version != Serialization::ShaderMetaVersion) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
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);
|
||||||
|
sdata.Write(vertex_bindings);
|
||||||
|
sdata.Write(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);
|
||||||
|
sdata.Read(vertex_bindings);
|
||||||
|
sdata.Read(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)};
|
||||||
|
Serialization::Reader meta{meta_ar};
|
||||||
|
|
||||||
|
u32 meta_version{};
|
||||||
|
meta.Read(meta_version);
|
||||||
|
if (meta_version != Serialization::ShaderMetaVersion) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
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(0);
|
||||||
|
modules.fill(nullptr);
|
||||||
|
fetch_shader.reset();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PipelineCache::LoadPipelineStage(Serialization::Archive& ar, size_t stage) {
|
||||||
|
Shader::Backend::Bindings binding{}; // not needed?
|
||||||
|
size_t spec_hash{};
|
||||||
|
size_t perm_idx;
|
||||||
|
|
||||||
|
auto program = std::make_unique<Program>();
|
||||||
|
|
||||||
|
if (!LoadShaderMeta(ar, program->info, fetch_shader, spec_hash, 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto module = CompileSPV(spv, instance.GetDevice());
|
||||||
|
|
||||||
|
auto [it_pgm, new_program] = program_cache.try_emplace(program->info.pgm_hash);
|
||||||
|
if (new_program) {
|
||||||
|
program->InsertPermut(module, spec_hash, perm_idx);
|
||||||
|
it_pgm.value() = std::move(program);
|
||||||
|
} else {
|
||||||
|
// Make sure the permutation doesn't exist yet
|
||||||
|
const auto& it =
|
||||||
|
std::ranges::find(it_pgm.value()->modules, spec_hash, &Program::Module::spec_hash);
|
||||||
|
assert(it == it_pgm.value()->modules.end());
|
||||||
|
it_pgm.value()->InsertPermut(module, spec_hash,
|
||||||
|
perm_idx); // need to insert to prevent collisions
|
||||||
|
}
|
||||||
|
|
||||||
|
infos[stage] = &it_pgm.value()->info;
|
||||||
|
modules[stage] = module;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PipelineCache::WarmUp() {
|
||||||
|
if (!Config::isPipelineCacheEnabled()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Storage::DataBase::Instance().Open();
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
u64 Shader::Gcn::FetchShaderData::Hash() const {
|
||||||
|
XXH64_state_t* const state = XXH64_createState();
|
||||||
|
XXH64_reset(state, 0);
|
||||||
|
XXH64_update(state, &size, sizeof(size));
|
||||||
|
XXH64_update(state, &vertex_offset_sgpr, sizeof(vertex_offset_sgpr));
|
||||||
|
XXH64_update(state, &instance_offset_sgpr, sizeof(instance_offset_sgpr));
|
||||||
|
for (const auto& attrib : attributes) {
|
||||||
|
XXH64_update(state, &attrib, sizeof(attrib));
|
||||||
|
}
|
||||||
|
return XXH64_digest(state);
|
||||||
|
}
|
||||||
|
|
||||||
|
u64 StageSpecialization::Hash() const {
|
||||||
|
XXH64_state_t* const state = XXH64_createState();
|
||||||
|
XXH64_reset(state, 0);
|
||||||
|
XXH64_update(state, &start, sizeof(start));
|
||||||
|
XXH64_update(state, &runtime_info,
|
||||||
|
sizeof(runtime_info)); // maybe broken because of union + span in GS
|
||||||
|
|
||||||
|
for (const auto& attrib : vs_attribs) {
|
||||||
|
XXH64_update(state, &attrib, sizeof(attrib));
|
||||||
|
}
|
||||||
|
for (const auto& buffer : buffers) {
|
||||||
|
XXH64_update(state, &buffer, sizeof(buffer));
|
||||||
|
}
|
||||||
|
for (const auto& image : images) {
|
||||||
|
XXH64_update(state, &image, sizeof(image));
|
||||||
|
}
|
||||||
|
for (const auto& sampler : samplers) {
|
||||||
|
XXH64_update(state, &sampler, sizeof(sampler));
|
||||||
|
}
|
||||||
|
for (const auto& fmask : fmasks) {
|
||||||
|
XXH64_update(state, &fmask, sizeof(fmask));
|
||||||
|
}
|
||||||
|
|
||||||
|
u64 hash = XXH64_digest(state);
|
||||||
|
|
||||||
|
if (fetch_shader_data) {
|
||||||
|
hash = HashCombine(hash, fetch_shader_data->Hash());
|
||||||
|
}
|
||||||
|
|
||||||
|
return hash;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Shader::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 Shader::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;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Shader
|
||||||
20
src/video_core/renderer_vulkan/vk_pipeline_serialization.h
Normal file
20
src/video_core/renderer_vulkan/vk_pipeline_serialization.h
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#include <optional>
|
||||||
|
|
||||||
|
#include "shader_recompiler/frontend/fetch_shader.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,
|
||||||
|
u64 perm_hash, u64 spec_hash, size_t perm_idx);
|
||||||
|
void RegisterShaderBinary(std::vector<u32>&& spv, u64 pgm_hash, size_t perm_idx);
|
||||||
|
|
||||||
|
} // namespace Vulkan
|
||||||
169
src/video_core/renderer_vulkan/vk_pipeline_storage.cpp
Normal file
169
src/video_core/renderer_vulkan/vk_pipeline_storage.cpp
Normal file
@ -0,0 +1,169 @@
|
|||||||
|
// 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/hash.h"
|
||||||
|
#include "common/io_file.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_pipeline_storage.h"
|
||||||
|
#include "video_core/renderer_vulkan/vk_shader_util.h"
|
||||||
|
|
||||||
|
namespace Vulkan {
|
||||||
|
namespace Storage {
|
||||||
|
|
||||||
|
constexpr std::string GetBlobFileExtension(BlobType type) {
|
||||||
|
switch (type) {
|
||||||
|
case BlobType::ShaderMeta: {
|
||||||
|
return "meta";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case BlobType::ShaderBinary: {
|
||||||
|
return "spv";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case BlobType::PipelineKey: {
|
||||||
|
return "key";
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DataBase::Open() {
|
||||||
|
const auto& game_info = Common::ElfInfo::Instance();
|
||||||
|
|
||||||
|
using namespace Common::FS;
|
||||||
|
cache_dir = GetUserPath(PathType::CacheDir) / game_info.GameSerial();
|
||||||
|
if (!std::filesystem::exists(cache_dir)) {
|
||||||
|
std::filesystem::create_directories(cache_dir);
|
||||||
|
}
|
||||||
|
|
||||||
|
opened = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DataBase::Close() {
|
||||||
|
if (!IsOpened()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
bool WriteVector(BlobType type, std::filesystem::path path, const std::vector<T>& v) {
|
||||||
|
using namespace Common::FS;
|
||||||
|
path.replace_extension(GetBlobFileExtension(type));
|
||||||
|
const auto file = IOFile{path, FileAccessMode::Create};
|
||||||
|
file.Write(v);
|
||||||
|
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));
|
||||||
|
const auto file = IOFile{path, FileAccessMode::Read};
|
||||||
|
v.resize(file.GetSize() / sizeof(T));
|
||||||
|
file.Read(v);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DataBase::Save(BlobType type, std::string name, std::vector<u8>&& data) {
|
||||||
|
if (!opened) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto path = cache_dir / name;
|
||||||
|
return WriteVector(type, path.string(), data);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DataBase::Save(BlobType type, std::string name, std::vector<u32>&& data) {
|
||||||
|
if (!opened) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto path = cache_dir / name;
|
||||||
|
return WriteVector(type, path.string(), data);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DataBase::Load(BlobType type, std::string name, std::vector<u8>& data) {
|
||||||
|
if (!opened) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto path = cache_dir / name;
|
||||||
|
return LoadVector(type, path.string(), data);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DataBase::Load(BlobType type, std::string name, std::vector<u32>& data) {
|
||||||
|
if (!opened) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto path = cache_dir / name;
|
||||||
|
return LoadVector(type, path.string(), data);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DataBase::ForEachBlob(BlobType type, std::function<void(std::vector<u8>&& data)> func) {
|
||||||
|
const auto& ext = GetBlobFileExtension(type);
|
||||||
|
for (const auto& file_name : std::filesystem::directory_iterator{cache_dir}) {
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Storage
|
||||||
|
|
||||||
|
std::string PipelineCache::GetShaderName(Shader::Stage stage, u64 hash,
|
||||||
|
std::optional<size_t> perm) {
|
||||||
|
if (perm) {
|
||||||
|
return fmt::format("{}_{:#018x}_{}", stage, hash, *perm);
|
||||||
|
}
|
||||||
|
return fmt::format("{}_{:#018x}", stage, hash);
|
||||||
|
}
|
||||||
|
|
||||||
|
void PipelineCache::DumpShader(std::span<const u32> code, u64 hash, Shader::Stage stage,
|
||||||
|
size_t perm_idx, std::string_view ext) {
|
||||||
|
if (!Config::dumpShaders()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
using namespace Common::FS;
|
||||||
|
const auto dump_dir = GetUserPath(PathType::ShaderDir) / "dumps";
|
||||||
|
if (!std::filesystem::exists(dump_dir)) {
|
||||||
|
std::filesystem::create_directories(dump_dir);
|
||||||
|
}
|
||||||
|
const auto filename = fmt::format("{}.{}", GetShaderName(stage, hash, perm_idx), ext);
|
||||||
|
const auto file = IOFile{dump_dir / filename, FileAccessMode::Create};
|
||||||
|
file.WriteSpan(code);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<std::vector<u32>> PipelineCache::GetShaderPatch(u64 hash, Shader::Stage stage,
|
||||||
|
size_t perm_idx,
|
||||||
|
std::string_view ext) {
|
||||||
|
|
||||||
|
using namespace Common::FS;
|
||||||
|
const auto patch_dir = GetUserPath(PathType::ShaderDir) / "patch";
|
||||||
|
if (!std::filesystem::exists(patch_dir)) {
|
||||||
|
std::filesystem::create_directories(patch_dir);
|
||||||
|
}
|
||||||
|
const auto filename = fmt::format("{}.{}", GetShaderName(stage, hash, perm_idx), ext);
|
||||||
|
const auto filepath = patch_dir / filename;
|
||||||
|
if (!std::filesystem::exists(filepath)) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
const auto file = IOFile{patch_dir / filename, FileAccessMode::Read};
|
||||||
|
std::vector<u32> code(file.GetSize() / sizeof(u32));
|
||||||
|
file.Read(code);
|
||||||
|
return code;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Vulkan
|
||||||
46
src/video_core/renderer_vulkan/vk_pipeline_storage.h
Normal file
46
src/video_core/renderer_vulkan/vk_pipeline_storage.h
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#include "common/path_util.h"
|
||||||
|
#include "common/singleton.h"
|
||||||
|
#include "common/types.h"
|
||||||
|
|
||||||
|
#include <functional>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace Vulkan {
|
||||||
|
namespace Storage {
|
||||||
|
|
||||||
|
enum class BlobType : u32 {
|
||||||
|
ShaderMeta,
|
||||||
|
ShaderBinary,
|
||||||
|
PipelineKey,
|
||||||
|
};
|
||||||
|
|
||||||
|
class DataBase {
|
||||||
|
public:
|
||||||
|
static DataBase& Instance() {
|
||||||
|
return *Common::Singleton<DataBase>::Instance();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Open();
|
||||||
|
void Close();
|
||||||
|
bool IsOpened() {
|
||||||
|
return opened;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Save(BlobType type, std::string name, std::vector<u8>&& data);
|
||||||
|
bool Save(BlobType type, std::string name, std::vector<u32>&& data);
|
||||||
|
|
||||||
|
void Load(BlobType type, std::string name, std::vector<u8>& data);
|
||||||
|
void Load(BlobType type, std::string name, std::vector<u32>& data);
|
||||||
|
|
||||||
|
void ForEachBlob(BlobType type, std::function<void(std::vector<u8>&& data)> func);
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::filesystem::path cache_dir{};
|
||||||
|
bool opened{};
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Storage
|
||||||
|
} // namespace Vulkan
|
||||||
Loading…
Reference in New Issue
Block a user