Devtools - Shader editing (#1705)

* devtools: shader editing and compiling

* devtools: patch shader at runtime

* devtools: shader editing load patch even with config disabled
This commit is contained in:
Vinicius Rangel 2024-12-09 17:11:11 -03:00 committed by GitHub
parent f623613d12
commit f1b23c616e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
19 changed files with 466 additions and 103 deletions

View file

@ -13,7 +13,7 @@ namespace Vulkan {
ComputePipeline::ComputePipeline(const Instance& instance_, Scheduler& scheduler_,
DescriptorHeap& desc_heap_, vk::PipelineCache pipeline_cache,
u64 compute_key_, const Shader::Info& info_,
ComputePipelineKey compute_key_, const Shader::Info& info_,
vk::ShaderModule module)
: Pipeline{instance_, scheduler_, desc_heap_, pipeline_cache, true}, compute_key{compute_key_} {
auto& info = stages[int(Shader::Stage::Compute)];

View file

@ -17,15 +17,33 @@ class Instance;
class Scheduler;
class DescriptorHeap;
struct ComputePipelineKey {
size_t value;
friend bool operator==(const ComputePipelineKey& lhs, const ComputePipelineKey& rhs) {
return lhs.value == rhs.value;
}
friend bool operator!=(const ComputePipelineKey& lhs, const ComputePipelineKey& rhs) {
return !(lhs == rhs);
}
};
class ComputePipeline : public Pipeline {
public:
ComputePipeline(const Instance& instance, Scheduler& scheduler, DescriptorHeap& desc_heap,
vk::PipelineCache pipeline_cache, u64 compute_key, const Shader::Info& info,
vk::ShaderModule module);
vk::PipelineCache pipeline_cache, ComputePipelineKey compute_key,
const Shader::Info& info, vk::ShaderModule module);
~ComputePipeline();
private:
u64 compute_key;
ComputePipelineKey compute_key;
};
} // namespace Vulkan
template <>
struct std::hash<Vulkan::ComputePipelineKey> {
std::size_t operator()(const Vulkan::ComputePipelineKey& key) const noexcept {
return std::hash<size_t>{}(key.value);
}
};

View file

@ -1,6 +1,8 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <xxhash.h>
#include "common/types.h"

View file

@ -189,10 +189,19 @@ const GraphicsPipeline* PipelineCache::GetGraphicsPipeline() {
}
const auto [it, is_new] = graphics_pipelines.try_emplace(graphics_key);
if (is_new) {
it.value() = graphics_pipeline_pool.Create(instance, scheduler, desc_heap, graphics_key,
*pipeline_cache, infos, fetch_shader, modules);
it.value() =
std::make_unique<GraphicsPipeline>(instance, scheduler, desc_heap, graphics_key,
*pipeline_cache, infos, fetch_shader, modules);
if (Config::collectShadersForDebug()) {
for (auto stage = 0; stage < MaxShaderStages; ++stage) {
if (infos[stage]) {
auto& m = modules[stage];
module_related_pipelines[m].emplace_back(graphics_key);
}
}
}
}
return it->second;
return it->second.get();
}
const ComputePipeline* PipelineCache::GetComputePipeline() {
@ -201,10 +210,14 @@ const ComputePipeline* PipelineCache::GetComputePipeline() {
}
const auto [it, is_new] = compute_pipelines.try_emplace(compute_key);
if (is_new) {
it.value() = compute_pipeline_pool.Create(instance, scheduler, desc_heap, *pipeline_cache,
compute_key, *infos[0], modules[0]);
it.value() = std::make_unique<ComputePipeline>(
instance, scheduler, desc_heap, *pipeline_cache, compute_key, *infos[0], modules[0]);
if (Config::collectShadersForDebug()) {
auto& m = modules[0];
module_related_pipelines[m].emplace_back(compute_key);
}
}
return it->second;
return it->second.get();
}
bool PipelineCache::RefreshGraphicsKey() {
@ -401,7 +414,7 @@ bool PipelineCache::RefreshComputeKey() {
Shader::Backend::Bindings binding{};
const auto* cs_pgm = &liverpool->regs.cs_program;
const auto cs_params = Liverpool::GetParams(*cs_pgm);
std::tie(infos[0], modules[0], fetch_shader, compute_key) =
std::tie(infos[0], modules[0], fetch_shader, compute_key.value) =
GetProgram(Shader::Stage::Compute, cs_params, binding);
return true;
}
@ -417,17 +430,23 @@ vk::ShaderModule PipelineCache::CompileModule(Shader::Info& info,
const auto ir_program = Shader::TranslateProgram(code, pools, info, runtime_info, profile);
auto spv = Shader::Backend::SPIRV::EmitSPIRV(profile, runtime_info, ir_program, binding);
DumpShader(spv, info.pgm_hash, info.stage, perm_idx, "spv");
vk::ShaderModule module;
auto patch = GetShaderPatch(info.pgm_hash, info.stage, perm_idx, "spv");
if (patch) {
spv = *patch;
const bool is_patched = patch && Config::patchShaders();
if (is_patched) {
LOG_INFO(Loader, "Loaded patch for {} shader {:#x}", info.stage, info.pgm_hash);
module = CompileSPV(*patch, instance.GetDevice());
} else {
module = CompileSPV(spv, instance.GetDevice());
}
const auto module = CompileSPV(spv, instance.GetDevice());
const auto name = fmt::format("{}_{:#x}_{}", info.stage, info.pgm_hash, perm_idx);
const auto name = fmt::format("{}_{:#018x}_{}", info.stage, info.pgm_hash, perm_idx);
Vulkan::SetObjectName(instance.GetDevice(), module, name);
if (Config::collectShadersForDebug()) {
DebugState.CollectShader(name, spv, code);
DebugState.CollectShader(name, module, spv, code, patch ? *patch : std::span<const u32>{},
is_patched);
}
return module;
}
@ -438,17 +457,17 @@ PipelineCache::GetProgram(Shader::Stage stage, Shader::ShaderParams params,
const auto runtime_info = BuildRuntimeInfo(stage);
auto [it_pgm, new_program] = program_cache.try_emplace(params.hash);
if (new_program) {
Program* program = program_pool.Create(stage, params);
it_pgm.value() = std::make_unique<Program>(stage, params);
auto& program = it_pgm.value();
auto start = binding;
const auto module = CompileModule(program->info, runtime_info, params.code, 0, binding);
const auto spec = Shader::StageSpecialization(program->info, runtime_info, profile, start);
program->AddPermut(module, std::move(spec));
it_pgm.value() = program;
return std::make_tuple(&program->info, module, spec.fetch_shader_data,
HashCombine(params.hash, 0));
}
Program* program = it_pgm->second;
auto& program = it_pgm.value();
auto& info = program->info;
info.RefreshFlatBuf();
const auto spec = Shader::StageSpecialization(info, runtime_info, profile, binding);
@ -469,6 +488,34 @@ PipelineCache::GetProgram(Shader::Stage stage, Shader::ShaderParams params,
HashCombine(params.hash, perm_idx));
}
std::optional<vk::ShaderModule> PipelineCache::ReplaceShader(vk::ShaderModule module,
std::span<const u32> spv_code) {
std::optional<vk::ShaderModule> new_module{};
for (const auto& [_, program] : program_cache) {
for (auto& m : program->modules) {
if (m.module == module) {
const auto& d = instance.GetDevice();
d.destroyShaderModule(m.module);
m.module = CompileSPV(spv_code, d);
new_module = m.module;
}
}
}
if (module_related_pipelines.contains(module)) {
auto& pipeline_keys = module_related_pipelines[module];
for (auto& key : pipeline_keys) {
if (std::holds_alternative<GraphicsPipelineKey>(key)) {
auto& graphics_key = std::get<GraphicsPipelineKey>(key);
graphics_pipelines.erase(graphics_key);
} else if (std::holds_alternative<ComputePipelineKey>(key)) {
auto& compute_key = std::get<ComputePipelineKey>(key);
compute_pipelines.erase(compute_key);
}
}
}
return new_module;
}
void PipelineCache::DumpShader(std::span<const u32> code, u64 hash, Shader::Stage stage,
size_t perm_idx, std::string_view ext) {
if (!Config::dumpShaders()) {
@ -488,9 +535,6 @@ void PipelineCache::DumpShader(std::span<const u32> code, u64 hash, Shader::Stag
std::optional<std::vector<u32>> PipelineCache::GetShaderPatch(u64 hash, Shader::Stage stage,
size_t perm_idx,
std::string_view ext) {
if (!Config::patchShaders()) {
return {};
}
using namespace Common::FS;
const auto patch_dir = GetUserPath(PathType::ShaderDir) / "patch";

View file

@ -3,6 +3,7 @@
#pragma once
#include <variant>
#include <tsl/robin_map.h>
#include "shader_recompiler/profile.h"
#include "shader_recompiler/recompiler.h"
@ -11,6 +12,13 @@
#include "video_core/renderer_vulkan/vk_graphics_pipeline.h"
#include "video_core/renderer_vulkan/vk_resource_pool.h"
template <>
struct std::hash<vk::ShaderModule> {
std::size_t operator()(const vk::ShaderModule& module) const noexcept {
return std::hash<size_t>{}(reinterpret_cast<size_t>((VkShaderModule)module));
}
};
namespace Shader {
struct Info;
}
@ -52,6 +60,9 @@ public:
GetProgram(Shader::Stage stage, Shader::ShaderParams params,
Shader::Backend::Bindings& binding);
std::optional<vk::ShaderModule> ReplaceShader(vk::ShaderModule module,
std::span<const u32> spv_code);
private:
bool RefreshGraphicsKey();
bool RefreshComputeKey();
@ -74,17 +85,19 @@ private:
vk::UniquePipelineLayout pipeline_layout;
Shader::Profile profile{};
Shader::Pools pools;
tsl::robin_map<size_t, Program*> program_cache;
Common::ObjectPool<Program> program_pool;
Common::ObjectPool<GraphicsPipeline> graphics_pipeline_pool;
Common::ObjectPool<ComputePipeline> compute_pipeline_pool;
tsl::robin_map<size_t, ComputePipeline*> compute_pipelines;
tsl::robin_map<GraphicsPipelineKey, GraphicsPipeline*> graphics_pipelines;
tsl::robin_map<size_t, std::unique_ptr<Program>> program_cache;
tsl::robin_map<ComputePipelineKey, std::unique_ptr<ComputePipeline>> compute_pipelines;
tsl::robin_map<GraphicsPipelineKey, std::unique_ptr<GraphicsPipeline>> graphics_pipelines;
std::array<const Shader::Info*, MaxShaderStages> infos{};
std::array<vk::ShaderModule, MaxShaderStages> modules{};
std::optional<Shader::Gcn::FetchShaderData> fetch_shader{};
GraphicsPipelineKey graphics_key{};
u64 compute_key{};
ComputePipelineKey compute_key{};
// Only if Config::collectShadersForDebug()
tsl::robin_map<vk::ShaderModule,
std::vector<std::variant<GraphicsPipelineKey, ComputePipelineKey>>>
module_related_pipelines;
};
} // namespace Vulkan

View file

@ -53,6 +53,10 @@ public:
return pp_settings.gamma;
}
Frontend::WindowSDL& GetWindow() const {
return window;
}
Frame* PrepareFrame(const Libraries::VideoOut::BufferAttributeGroup& attribute,
VAddr cpu_address, bool is_eop) {
auto desc = VideoCore::TextureCache::VideoOutDesc{attribute, cpu_address};
@ -90,6 +94,10 @@ public:
draw_scheduler.Flush(info);
}
Rasterizer& GetRasterizer() const {
return *rasterizer.get();
}
private:
void CreatePostProcessPipeline();
Frame* PrepareFrameInternal(VideoCore::ImageId image_id, bool is_eop = true);

View file

@ -54,6 +54,10 @@ public:
u64 Flush();
void Finish();
PipelineCache& GetPipelineCache() {
return pipeline_cache;
}
private:
RenderState PrepareRenderState(u32 mrt_mask);
void BeginRendering(const GraphicsPipeline& pipeline, RenderState& state);