mirror of
https://github.com/KytyPS5/KytyPS5.git
synced 2026-10-23 04:16:57 +02:00
shader: rework resource specialization and optimize pipeline caching
This commit is contained in:
@@ -65,7 +65,7 @@ vk::DescriptorType NativeDescriptorType(BindingKind kind) {
|
||||
case BindingKind::BdaPagetable:
|
||||
case BindingKind::FaultBuffer:
|
||||
case BindingKind::FlattenedSrt:
|
||||
case BindingKind::UserData: return vk::DescriptorType::eStorageBuffer;
|
||||
case BindingKind::ShaderData: return vk::DescriptorType::eStorageBuffer;
|
||||
case BindingKind::Count: EXIT("invalid native descriptor binding kind");
|
||||
}
|
||||
EXIT("invalid native descriptor binding kind");
|
||||
@@ -108,12 +108,6 @@ static void BindNullStorageBuffer(RenderContext& context, BufferView& dst) {
|
||||
dst.range = 16;
|
||||
}
|
||||
|
||||
static void CopyNativeDescriptor(const ShaderRecompiler::IR::DescriptorValue& source,
|
||||
std::span<uint32_t> destination) {
|
||||
EXIT_IF(source.dword_count != destination.size());
|
||||
std::copy_n(source.dwords.begin(), destination.size(), destination.begin());
|
||||
}
|
||||
|
||||
static Prospero::ImageType TextureType(const ShaderTextureResource& descriptor) {
|
||||
const auto type = descriptor.Type();
|
||||
return type == Prospero::ImageType::kCube ? Prospero::ImageType::kColor2DArray : type;
|
||||
@@ -658,8 +652,7 @@ static ImageViewInfo TextureViewInfo(const ShaderRecompiler::IR::ImageResource&
|
||||
|
||||
TextureBinding RenderExecutor::ResolveTexture(const ShaderRecompiler::IR::ImageResource& resource,
|
||||
const ShaderRecompiler::IR::DescriptorValue& value) {
|
||||
ShaderTextureResource descriptor;
|
||||
CopyNativeDescriptor(value, descriptor.fields);
|
||||
auto descriptor = DecodeNativeDescriptor<ShaderTextureResource>(value);
|
||||
const bool storage = resource.written;
|
||||
if (storage) {
|
||||
ValidateStorageImageResource(resource);
|
||||
@@ -802,17 +795,11 @@ TextureBinding RenderExecutor::ResolveTexture(const ShaderRecompiler::IR::ImageR
|
||||
}
|
||||
|
||||
static vk::Sampler NativeSampler(RenderContext& context,
|
||||
const ShaderRecompiler::IR::Program& program, uint32_t index,
|
||||
const ShaderRecompiler::IR::CompiledShaderInfo& program,
|
||||
uint32_t index,
|
||||
const ShaderRecompiler::IR::DescriptorValue& value) {
|
||||
ShaderSamplerResource descriptor;
|
||||
CopyNativeDescriptor(value, descriptor.fields);
|
||||
const bool depth_compare = std::any_of(program.info.sampled_pairs.begin(),
|
||||
program.info.sampled_pairs.end(), [&](const auto& pair) {
|
||||
return pair.sampler == index &&
|
||||
pair.image < program.info.images.size() &&
|
||||
program.info.images[pair.image].depth_compare;
|
||||
});
|
||||
if (!depth_compare) {
|
||||
auto descriptor = DecodeNativeDescriptor<ShaderSamplerResource>(value);
|
||||
if (!program.info.samplers[index].depth_compare) {
|
||||
descriptor.fields[0] &= ~(0x7u << 12u);
|
||||
}
|
||||
if (program.info.samplers[index].force_point_filtering) {
|
||||
@@ -869,15 +856,10 @@ PreparedBindings RenderExecutor::PrepareBindings(const ShaderStageRuntime& runti
|
||||
KYTY_PROFILER_FUNCTION();
|
||||
EXIT_IF(!runtime);
|
||||
const auto& program = *runtime.program;
|
||||
const auto& snapshot = *runtime.resources;
|
||||
if (!ShaderRecompiler::IR::ValidateResourceSpecialization(program, snapshot)) {
|
||||
EXIT("invalid native shader runtime snapshot: hash=0x%016" PRIx64 " stage=%u\n",
|
||||
program.shader_hash, static_cast<unsigned>(program.stage));
|
||||
}
|
||||
|
||||
const auto& snapshot = runtime.resources;
|
||||
PreparedBindings prepared;
|
||||
prepared.program = runtime.program.get();
|
||||
prepared.snapshot = runtime.resources.get();
|
||||
prepared.program = runtime.program;
|
||||
prepared.snapshot = &runtime.resources;
|
||||
auto& descriptors = prepared.resources;
|
||||
descriptors.buffers.reserve(program.info.buffers.size());
|
||||
descriptors.images.reserve(program.info.images.size());
|
||||
@@ -890,17 +872,11 @@ PreparedBindings RenderExecutor::PrepareBindings(const ShaderStageRuntime& runti
|
||||
for (uint32_t i = 0; i < program.info.samplers.size(); i++) {
|
||||
descriptors.samplers.push_back(NativeSampler(m_context, program, i, snapshot.samplers[i]));
|
||||
}
|
||||
if (ShaderRecompiler::IR::FindBinding(
|
||||
program.bindings, ShaderRecompiler::IR::DescriptorBindingKind::FlattenedSrt) !=
|
||||
nullptr) {
|
||||
prepared.flattened_srt.assign(snapshot.flattened_srt.begin(), snapshot.flattened_srt.end());
|
||||
}
|
||||
|
||||
prepared.user_data.reserve(program.bindings.ShaderDataDwords());
|
||||
prepared.shader_data.reserve(program.bindings.ShaderDataDwords());
|
||||
for (const auto reg: program.bindings.user_data_registers) {
|
||||
prepared.user_data.push_back(snapshot.user_data[reg - program.user_data_base]);
|
||||
prepared.shader_data.push_back(snapshot.user_data[reg - program.user_data_base]);
|
||||
}
|
||||
prepared.user_data.resize(program.bindings.ShaderDataDwords());
|
||||
prepared.shader_data.resize(program.bindings.ShaderDataDwords());
|
||||
if (ShaderRecompiler::IR::FindBinding(
|
||||
program.bindings, ShaderRecompiler::IR::DescriptorBindingKind::Gds) != nullptr) {
|
||||
descriptors.gds.buffer = m_context.GetBufferCache().GetGdsBuffer()->Handle();
|
||||
@@ -915,22 +891,21 @@ void RenderExecutor::FindBuffers(PreparedBindings& prepared) {
|
||||
const auto& snapshot = *prepared.snapshot;
|
||||
auto& cache = m_context.GetBufferCache();
|
||||
|
||||
prepared.buffer_ids.clear();
|
||||
prepared.buffer_ids.reserve(program.info.buffers.size());
|
||||
prepared.buffer_sources.clear();
|
||||
prepared.buffer_sources.reserve(program.info.buffers.size());
|
||||
for (uint32_t i = 0; i < program.info.buffers.size(); i++) {
|
||||
ShaderBufferResource descriptor;
|
||||
CopyNativeDescriptor(snapshot.buffers[i], descriptor.fields);
|
||||
auto descriptor = DecodeNativeDescriptor<ShaderBufferResource>(snapshot.buffers[i]);
|
||||
const auto address = descriptor.Base48();
|
||||
const auto stride = descriptor.Stride();
|
||||
const auto records = descriptor.NumRecords();
|
||||
EXIT_IF(stride != 0 && records > UINT64_MAX / stride);
|
||||
const auto requested_size = stride != 0 ? static_cast<uint64_t>(stride) * records : records;
|
||||
if (address == 0 || requested_size == 0) {
|
||||
prepared.buffer_ids.emplace_back();
|
||||
prepared.buffer_sources.emplace_back(descriptor, BufferId {});
|
||||
continue;
|
||||
}
|
||||
const auto size = Libs::LibKernel::Memory::ClampRangeSize(address, requested_size);
|
||||
prepared.buffer_ids.push_back(cache.FindBuffer(address, size));
|
||||
prepared.buffer_sources.emplace_back(descriptor, cache.FindBuffer(address, size));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -942,32 +917,33 @@ void RenderExecutor::RebindBuffers(PreparedBindings& prepared) {
|
||||
const auto& snapshot = *prepared.snapshot;
|
||||
auto& resources = prepared.resources;
|
||||
const auto& layout = program.bindings;
|
||||
EXIT_IF(prepared.buffer_ids.size() != program.info.buffers.size());
|
||||
EXIT_IF(prepared.buffer_sources.size() != program.info.buffers.size());
|
||||
|
||||
resources.buffers.clear();
|
||||
resources.buffers.reserve(program.info.buffers.size());
|
||||
EXIT_IF(prepared.user_data.size() != layout.ShaderDataDwords());
|
||||
std::fill(prepared.user_data.begin() + layout.memory_offset_dword, prepared.user_data.end(), 0);
|
||||
EXIT_IF(prepared.shader_data.size() != layout.ShaderDataDwords());
|
||||
std::fill(prepared.shader_data.begin() + layout.memory_offset_dword,
|
||||
prepared.shader_data.end(), 0);
|
||||
auto pack_memory_offset = [&](uint32_t index, uint32_t offset) {
|
||||
const auto dword = layout.memory_offset_dword + index / 4u;
|
||||
const auto shift = (index % 4u) * 8u;
|
||||
prepared.user_data[dword] |= offset << shift;
|
||||
prepared.shader_data[dword] |= offset << shift;
|
||||
};
|
||||
for (uint32_t i = 0; i < program.info.buffers.size(); i++) {
|
||||
ShaderBufferResource descriptor;
|
||||
CopyNativeDescriptor(snapshot.buffers[i], descriptor.fields);
|
||||
const auto& [descriptor, buffer_id] = prepared.buffer_sources[i];
|
||||
uint32_t buffer_offset = 0;
|
||||
resources.buffers.push_back(NativeStorageBuffer(m_context, descriptor,
|
||||
program.info.buffers[i], program.stage, i,
|
||||
buffer_offset, prepared.buffer_ids[i]));
|
||||
buffer_offset, buffer_id));
|
||||
pack_memory_offset(i, buffer_offset);
|
||||
}
|
||||
if (!prepared.flattened_srt.empty()) {
|
||||
resources.flattened_srt = NativeUpload(m_context, prepared.flattened_srt);
|
||||
if (ShaderRecompiler::IR::FindBinding(
|
||||
layout, ShaderRecompiler::IR::DescriptorBindingKind::FlattenedSrt) != nullptr) {
|
||||
resources.flattened_srt = NativeUpload(m_context, snapshot.flattened_srt);
|
||||
}
|
||||
if (ShaderRecompiler::IR::FindBinding(
|
||||
program.bindings, ShaderRecompiler::IR::DescriptorBindingKind::UserData) != nullptr) {
|
||||
resources.user_data = NativeUpload(m_context, prepared.user_data);
|
||||
program.bindings, ShaderRecompiler::IR::DescriptorBindingKind::ShaderData) != nullptr) {
|
||||
resources.shader_data = NativeUpload(m_context, prepared.shader_data);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1056,14 +1032,13 @@ void RenderExecutor::CommitBindings(CommandBuffer& buffer,
|
||||
auto vk_buffer = buffer.Handle();
|
||||
size_t descriptor_count = 0;
|
||||
size_t write_count = 0;
|
||||
ShaderRecompiler::IR::PushData push_data;
|
||||
bool has_push_data = false;
|
||||
constexpr auto GraphicsStages =
|
||||
vk::ShaderStageFlagBits::eVertex | vk::ShaderStageFlagBits::eFragment;
|
||||
const auto push_constant_stages = pipeline_bind_point == vk::PipelineBindPoint::eGraphics
|
||||
? vk::ShaderStageFlags {GraphicsStages}
|
||||
: vk::ShaderStageFlags {vk::ShaderStageFlagBits::eCompute};
|
||||
for (const auto* prepared: prepared_bindings) {
|
||||
EXIT_IF(prepared == nullptr || prepared->program == nullptr ||
|
||||
prepared->snapshot == nullptr || prepared->committed);
|
||||
prepared->snapshot == nullptr);
|
||||
write_count += prepared->program->bindings.descriptors.size();
|
||||
for (const auto& binding: prepared->program->bindings.descriptors) {
|
||||
descriptor_count += NativeDescriptorCount(binding);
|
||||
@@ -1077,7 +1052,6 @@ void RenderExecutor::CommitBindings(CommandBuffer& buffer,
|
||||
m_descriptor_buffers.clear();
|
||||
m_descriptor_images.clear();
|
||||
m_descriptor_writes.clear();
|
||||
m_push_constants.fill(0);
|
||||
m_descriptor_buffers.reserve(descriptor_count);
|
||||
m_descriptor_images.reserve(descriptor_count);
|
||||
m_descriptor_writes.reserve(write_count);
|
||||
@@ -1167,13 +1141,13 @@ void RenderExecutor::CommitBindings(CommandBuffer& buffer,
|
||||
break;
|
||||
}
|
||||
case BindingKind::FlattenedSrt:
|
||||
case BindingKind::UserData:
|
||||
case BindingKind::ShaderData:
|
||||
case BindingKind::Gds: {
|
||||
const BufferView* view = &descriptors.gds;
|
||||
if (binding.kind == BindingKind::FlattenedSrt) {
|
||||
view = &descriptors.flattened_srt;
|
||||
} else if (binding.kind == BindingKind::UserData) {
|
||||
view = &descriptors.user_data;
|
||||
} else if (binding.kind == BindingKind::ShaderData) {
|
||||
view = &descriptors.shader_data;
|
||||
}
|
||||
EXIT_IF(view->buffer == nullptr);
|
||||
m_descriptor_buffers.emplace_back(view->buffer, view->offset, view->range);
|
||||
@@ -1206,20 +1180,22 @@ void RenderExecutor::CommitBindings(CommandBuffer& buffer,
|
||||
EXIT_IF(m_image_occurrences[i] != expected);
|
||||
}
|
||||
|
||||
if (program.bindings.push_constant_size != 0) {
|
||||
const auto offset = program.bindings.push_constant_offset;
|
||||
EXIT_IF(offset % sizeof(uint32_t) != 0 ||
|
||||
program.bindings.push_constant_size !=
|
||||
prepared->user_data.size() * sizeof(uint32_t) ||
|
||||
offset + program.bindings.push_constant_size >
|
||||
ShaderRecompiler::IR::NativePushConstantSize);
|
||||
std::copy(prepared->user_data.begin(), prepared->user_data.end(),
|
||||
m_push_constants.begin() + offset / sizeof(uint32_t));
|
||||
const auto shader_data_dwords = program.bindings.ShaderDataDwords();
|
||||
EXIT_IF(prepared->shader_data.size() != shader_data_dwords);
|
||||
if (program.bindings.UsesPushData()) {
|
||||
std::ranges::copy(prepared->shader_data,
|
||||
push_data.dwords.begin() + program.bindings.push_data_start_dword);
|
||||
has_push_data = true;
|
||||
}
|
||||
}
|
||||
vk_buffer.pushConstants(pipeline.pipeline_layout, push_constant_stages, 0,
|
||||
ShaderRecompiler::IR::NativePushConstantSize,
|
||||
m_push_constants.data());
|
||||
|
||||
if (has_push_data) {
|
||||
const auto stages = pipeline_bind_point == vk::PipelineBindPoint::eGraphics
|
||||
? vk::ShaderStageFlags {GraphicsStages}
|
||||
: vk::ShaderStageFlags {vk::ShaderStageFlagBits::eCompute};
|
||||
vk_buffer.pushConstants(pipeline.pipeline_layout, stages, 0, sizeof(push_data),
|
||||
push_data.dwords.data());
|
||||
}
|
||||
|
||||
if (!m_descriptor_writes.empty()) {
|
||||
EXIT_IF(pipeline.descriptor_set_layout == nullptr);
|
||||
@@ -1239,9 +1215,6 @@ void RenderExecutor::CommitBindings(CommandBuffer& buffer,
|
||||
0, nullptr);
|
||||
}
|
||||
}
|
||||
for (auto* prepared: prepared_bindings) {
|
||||
prepared->committed = true;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
|
||||
@@ -40,17 +40,15 @@ struct NativeDescriptors {
|
||||
std::vector<vk::Sampler> samplers;
|
||||
BufferView gds;
|
||||
BufferView flattened_srt;
|
||||
BufferView user_data;
|
||||
BufferView shader_data;
|
||||
};
|
||||
|
||||
struct PreparedBindings {
|
||||
const ShaderRecompiler::IR::Program* program = nullptr;
|
||||
const ShaderRecompiler::IR::CompiledShaderInfo* program = nullptr;
|
||||
const ShaderRecompiler::IR::ResourceSnapshot* snapshot = nullptr;
|
||||
NativeDescriptors resources;
|
||||
std::vector<BufferId> buffer_ids;
|
||||
std::vector<uint32_t> flattened_srt;
|
||||
std::vector<uint32_t> user_data;
|
||||
bool committed = false;
|
||||
std::vector<std::pair<ShaderBufferResource, BufferId>> buffer_sources;
|
||||
std::vector<uint32_t> shader_data;
|
||||
};
|
||||
|
||||
[[nodiscard]] vk::DescriptorType
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include "graphics/host_gpu/renderer/pipeline/pipelineCache.h"
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/emulatorConfig.h"
|
||||
#include "common/file.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/profiler.h"
|
||||
@@ -11,7 +12,9 @@
|
||||
#include "graphics/host_gpu/renderer/image/imageView.h"
|
||||
#include "graphics/host_gpu/renderer/render.h"
|
||||
#include "graphics/host_gpu/renderer/renderContext.h"
|
||||
#include "graphics/shader/recompiler/ShaderRecompiler.h"
|
||||
#include "graphics/shader/shaderCompiler.h"
|
||||
#include "kernel/memory.h"
|
||||
#include "kytyGitVersion.h"
|
||||
#include "loader/systemContent.h"
|
||||
|
||||
@@ -20,13 +23,14 @@
|
||||
#include <cctype>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <fmt/format.h>
|
||||
#include <limits>
|
||||
#include <span>
|
||||
#include <spirv-tools/libspirv.hpp>
|
||||
#include <string_view>
|
||||
#include <tuple>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include <fmt/format.h>
|
||||
#include <xxhash.h>
|
||||
|
||||
namespace Libs::Graphics {
|
||||
@@ -84,42 +88,188 @@ void NormalizeStaticParamsForDynamicState(PipelineStaticParameters& static_param
|
||||
static_params.scissor_ltrb[3] = 1;
|
||||
}
|
||||
|
||||
bool ReadShaderGuestMemory(void*, uint64_t address, uint32_t* value) {
|
||||
return value != nullptr &&
|
||||
Libs::LibKernel::Memory::TryReadGpuCleanBacking(address, value, sizeof(*value));
|
||||
}
|
||||
|
||||
void DumpShaderSpirv(const char* stage_name, uint64_t shader_hash,
|
||||
const std::vector<uint32_t>& spirv) {
|
||||
if (!Config::GraphicsDebugDumpEnabled()) {
|
||||
return;
|
||||
}
|
||||
static std::atomic_int id = 0;
|
||||
const auto path = Config::GetShaderLogFolder() / fmt::format("{:04d}_new_shader_{}_{:016x}.spv",
|
||||
id++, stage_name, shader_hash);
|
||||
Common::File::CreateDirectories(path.parent_path());
|
||||
Common::File file(path, Common::File::Mode::Write);
|
||||
if (file.IsInvalid()) {
|
||||
const auto path_text = Common::PathToString(path);
|
||||
LOGF_COLOR(Log::Color::BrightRed, "Can't create file: %s\n", path_text.c_str());
|
||||
return;
|
||||
}
|
||||
file.Write(spirv.data(), spirv.size() * sizeof(uint32_t));
|
||||
}
|
||||
|
||||
void DumpShaderOriginal(const char* stage_name, uint64_t shader_hash,
|
||||
std::span<const uint32_t> code, const std::string& decoded_dump) {
|
||||
if (!Config::GraphicsDebugDumpEnabled()) {
|
||||
return;
|
||||
}
|
||||
EXIT_IF(code.empty());
|
||||
static std::atomic_int id = 0;
|
||||
const auto base = Config::GetShaderLogFolder() / "original" /
|
||||
fmt::format("{:04d}_new_shader_{}_{:016x}", id++, stage_name, shader_hash);
|
||||
Common::File::CreateDirectories(base.parent_path());
|
||||
for (const auto& [suffix, data, size]: {
|
||||
std::tuple {".bin", static_cast<const void*>(code.data()), code.size_bytes()},
|
||||
std::tuple {".rdna2", static_cast<const void*>(decoded_dump.data()),
|
||||
decoded_dump.size()},
|
||||
}) {
|
||||
if (size == 0) {
|
||||
continue;
|
||||
}
|
||||
auto path = base;
|
||||
path += suffix;
|
||||
Common::File file(path, Common::File::Mode::Write);
|
||||
if (file.IsInvalid()) {
|
||||
const auto path_text = Common::PathToString(path);
|
||||
LOGF_COLOR(Log::Color::BrightRed, "Can't create file: %s\n", path_text.c_str());
|
||||
} else {
|
||||
file.Write(data, size);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool ValidateShaderSpirv(const char* label, uint64_t shader_hash,
|
||||
const std::vector<uint32_t>& spirv) {
|
||||
if (!Config::ShaderValidationEnabled()) {
|
||||
return true;
|
||||
}
|
||||
spvtools::SpirvTools tools(SPV_ENV_VULKAN_1_3);
|
||||
std::string messages;
|
||||
tools.SetMessageConsumer([&messages](spv_message_level_t, const char*,
|
||||
const spv_position_t& position, const char* message) {
|
||||
messages += fmt::format("{}: {} ({}) {}\n", static_cast<int>(position.line),
|
||||
static_cast<int>(position.column), static_cast<int>(position.index),
|
||||
message);
|
||||
});
|
||||
if (tools.Validate(spirv)) {
|
||||
return true;
|
||||
}
|
||||
spvtools::SpirvTools disassembler(SPV_ENV_VULKAN_1_2);
|
||||
std::string text;
|
||||
disassembler.Disassemble(spirv, &text,
|
||||
static_cast<uint32_t>(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER) |
|
||||
static_cast<uint32_t>(SPV_BINARY_TO_TEXT_OPTION_FRIENDLY_NAMES) |
|
||||
static_cast<uint32_t>(SPV_BINARY_TO_TEXT_OPTION_COMMENT) |
|
||||
static_cast<uint32_t>(SPV_BINARY_TO_TEXT_OPTION_INDENT) |
|
||||
static_cast<uint32_t>(SPV_BINARY_TO_TEXT_OPTION_COLOR));
|
||||
LOGF_COLOR(Log::Color::BrightRed, "%s SPIR-V validation failed hash=0x%016" PRIx64 ":\n%s",
|
||||
label, shader_hash, messages.c_str());
|
||||
LOGF("%s\n", text.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
struct PipelineCache::ProgramCache {
|
||||
struct SourceKey {
|
||||
ShaderType stage = ShaderType::Unknown;
|
||||
uint64_t hash = 0;
|
||||
struct ProgramKey {
|
||||
ShaderType stage = ShaderType::Unknown;
|
||||
uint64_t hash = 0;
|
||||
uint32_t user_data_count = 0;
|
||||
uint32_t code_size = 0;
|
||||
std::vector<uint32_t> static_state;
|
||||
|
||||
bool operator==(const SourceKey&) const = default;
|
||||
bool operator==(const ProgramKey&) const = default;
|
||||
};
|
||||
|
||||
struct Permutation {
|
||||
std::vector<uint32_t> static_key;
|
||||
std::shared_ptr<const ShaderRecompiler::IR::Program> program;
|
||||
ShaderProgram handle;
|
||||
ShaderRecompiler::IR::ResourceSpecialization specialization;
|
||||
ShaderRecompiler::IR::CompiledShaderInfo program;
|
||||
ShaderProgram handle;
|
||||
};
|
||||
|
||||
struct SourceKeyHash {
|
||||
std::size_t operator()(const SourceKey& key) const {
|
||||
struct SourceEntry {
|
||||
explicit SourceEntry(ShaderRecompiler::IR::ResourcePlan plan)
|
||||
: resource_plan(std::move(plan)) {
|
||||
permutations.reserve(8);
|
||||
}
|
||||
|
||||
ShaderRecompiler::IR::ResourcePlan resource_plan;
|
||||
std::vector<Permutation> permutations;
|
||||
};
|
||||
|
||||
struct ProgramKeyHash {
|
||||
std::size_t operator()(const ProgramKey& key) const {
|
||||
std::size_t hash = static_cast<std::size_t>(key.stage);
|
||||
PipelineKeyHash::Mix(hash, static_cast<std::size_t>(key.hash));
|
||||
if constexpr (sizeof(std::size_t) < sizeof(uint64_t)) {
|
||||
PipelineKeyHash::Mix(hash, static_cast<std::size_t>(key.hash >> 32u));
|
||||
}
|
||||
PipelineKeyHash::Mix(hash, key.user_data_count);
|
||||
PipelineKeyHash::Mix(hash, key.code_size);
|
||||
PipelineKeyHash::Mix(hash, key.static_state.size());
|
||||
// Bucket same-shape static variants by source. ProgramKey equality performs the one
|
||||
// exact state comparison needed on a stable hit without hashing up to 429 words first.
|
||||
return hash;
|
||||
}
|
||||
};
|
||||
|
||||
static uint64_t MixId(uint64_t hash, uint64_t value) {
|
||||
return hash ^ (value + 0x9e3779b97f4a7c15ull + (hash << 6u) + (hash >> 2u));
|
||||
static constexpr std::size_t MaxStaticKeyWords = 13 + ShaderVertexInputInfo::RES_MAX * 13;
|
||||
|
||||
template <ShaderType Stage>
|
||||
Permutation CompilePermutation(const ShaderParams& params,
|
||||
const ShaderRecompiler::CompileOptions& options,
|
||||
ShaderRecompiler::TranslateResult translated,
|
||||
ShaderRecompiler::IR::ResourceSpecialization specialization,
|
||||
uint32_t push_data_start_dword) {
|
||||
constexpr const char* stage_name = [] {
|
||||
if constexpr (Stage == ShaderType::Vertex) {
|
||||
return "vs";
|
||||
} else if constexpr (Stage == ShaderType::Pixel) {
|
||||
return "ps";
|
||||
} else {
|
||||
static_assert(Stage == ShaderType::Compute);
|
||||
return "cs";
|
||||
}
|
||||
}();
|
||||
auto result = ShaderRecompiler::CompileProgram(std::move(translated), options,
|
||||
specialization, push_data_start_dword);
|
||||
DumpShaderOriginal(stage_name, options.shader_hash, params.code, result.decoded_dump);
|
||||
if (!ValidateShaderSpirv(options.dump_label, options.shader_hash, result.spirv)) {
|
||||
DumpShaderSpirv(stage_name, options.shader_hash, result.spirv);
|
||||
EXIT("%s failed hash=0x%016" PRIx64 ": SPIR-V validation failed\n", options.dump_label,
|
||||
options.shader_hash);
|
||||
}
|
||||
DumpShaderSpirv(stage_name, options.shader_hash, result.spirv);
|
||||
|
||||
vk::ShaderModuleCreateInfo create_info {};
|
||||
create_info.sType = vk::StructureType::eShaderModuleCreateInfo;
|
||||
create_info.codeSize = result.spirv.size() * sizeof(uint32_t);
|
||||
create_info.pCode = result.spirv.data();
|
||||
vk::ShaderModule module = nullptr;
|
||||
RequireVulkanSuccess(device.createShaderModule(&create_info, nullptr, &module),
|
||||
"create recompiled shader module");
|
||||
EXIT_IF(module == nullptr);
|
||||
if (options.dump_ir) {
|
||||
if (!options.early_dump) {
|
||||
LOGF("%s decoded RDNA2:\n%s", options.dump_label, result.decoded_dump.c_str());
|
||||
LOGF("%s IR:\n%s", options.dump_label, result.ir_dump.c_str());
|
||||
}
|
||||
LOGF("%s SPIR-V words=%" PRIu64 " wave_size=%u\n", options.dump_label,
|
||||
static_cast<uint64_t>(result.spirv.size()), options.wave_size);
|
||||
}
|
||||
return {
|
||||
.specialization = std::move(specialization),
|
||||
.program = std::move(result.program).TakeCompiledInfo(),
|
||||
.handle = {.id = ++next_shader_id, .module = module},
|
||||
};
|
||||
}
|
||||
|
||||
static constexpr std::size_t MaxStaticKeyWords =
|
||||
12 + ShaderVertexInputInfo::RES_MAX * 17;
|
||||
|
||||
template <typename InputInfo>
|
||||
ShaderProgram Get(const ShaderParams& params, InputInfo& input_info) {
|
||||
ShaderProgram Get(const ShaderParams& params, InputInfo& input_info,
|
||||
uint32_t& push_data_cursor) {
|
||||
constexpr ShaderType stage = [] {
|
||||
if constexpr (std::is_same_v<InputInfo, ShaderVertexInputInfo>) {
|
||||
return ShaderType::Vertex;
|
||||
@@ -131,36 +281,106 @@ struct PipelineCache::ProgramCache {
|
||||
}
|
||||
}();
|
||||
|
||||
BuildStageStaticKey(input_info, key_scratch);
|
||||
auto& permutations = programs[{stage, params.hash}];
|
||||
for (const auto& permutation: permutations) {
|
||||
if (permutation.static_key == key_scratch &&
|
||||
MaterializeProgram(permutation.program, params, input_info)) {
|
||||
return permutation.handle;
|
||||
lookup_key.stage = stage;
|
||||
lookup_key.hash = params.hash;
|
||||
lookup_key.user_data_count = static_cast<uint32_t>(params.user_data.size());
|
||||
lookup_key.code_size = static_cast<uint32_t>(params.code.size());
|
||||
BuildStageStaticKey(input_info, lookup_key.static_state);
|
||||
auto entry = programs.find(lookup_key);
|
||||
ShaderRecompiler::IR::ResourceSnapshot resources;
|
||||
ShaderRecompiler::IR::ResourceSpecialization specialization;
|
||||
const ShaderRecompiler::IR::SrtRuntime runtime {
|
||||
.user_data = params.user_data,
|
||||
.shader_base = params.Base(),
|
||||
.read_specialization_memory = ReadShaderGuestMemory,
|
||||
};
|
||||
if (entry != programs.end()) {
|
||||
EXIT_IF(!ShaderRecompiler::IR::MaterializeResources(
|
||||
entry->second.resource_plan, runtime, resources, specialization));
|
||||
if (const auto permutation = std::ranges::find_if(
|
||||
entry->second.permutations, [&](const Permutation& candidate) {
|
||||
const auto& layout = candidate.program.bindings;
|
||||
return layout.push_data_start_dword ==
|
||||
ShaderRecompiler::IR::PushData::StartFor(
|
||||
push_data_cursor, layout.ShaderDataDwords()) &&
|
||||
candidate.specialization == specialization;
|
||||
});
|
||||
permutation != entry->second.permutations.end()) {
|
||||
input_info.stage = {.program = &permutation->program,
|
||||
.resources = std::move(resources)};
|
||||
permutation->program.bindings.AdvancePushData(push_data_cursor);
|
||||
return permutation->handle;
|
||||
}
|
||||
}
|
||||
|
||||
const auto module = CompileProgram(device, params, input_info);
|
||||
EXIT_IF(module == nullptr || !input_info.stage);
|
||||
uint64_t id = MixId(MixId(params.hash, static_cast<uint64_t>(stage)), permutations.size());
|
||||
if (id == 0) {
|
||||
id = 1;
|
||||
ShaderStageInputInfo stage_input {};
|
||||
if constexpr (std::is_same_v<InputInfo, ShaderVertexInputInfo>) {
|
||||
stage_input.vertex = &input_info;
|
||||
} else if constexpr (std::is_same_v<InputInfo, ShaderPixelInputInfo>) {
|
||||
stage_input.pixel = &input_info;
|
||||
} else {
|
||||
stage_input.compute = &input_info;
|
||||
}
|
||||
const ShaderProgram handle {.id = id, .module = module};
|
||||
permutations.push_back({key_scratch, input_info.stage.program, handle});
|
||||
constexpr const char* label = [] {
|
||||
if constexpr (stage == ShaderType::Vertex) {
|
||||
return "ShaderRecompiler VS";
|
||||
} else if constexpr (stage == ShaderType::Pixel) {
|
||||
return "ShaderRecompiler PS";
|
||||
} else {
|
||||
return "ShaderRecompiler CS";
|
||||
}
|
||||
}();
|
||||
ShaderRecompiler::CompileOptions options;
|
||||
options.stage = stage;
|
||||
options.shader_hash = params.hash;
|
||||
options.user_data = params.user_data;
|
||||
options.dump_ir = Config::GetShaderLogDirection() != Config::ShaderLogDirection::Silent;
|
||||
options.early_dump = options.dump_ir;
|
||||
options.dump_label = label;
|
||||
options.input_info = stage_input;
|
||||
if constexpr (stage == ShaderType::Vertex) {
|
||||
options.user_data_base = 8;
|
||||
options.scratch_dwords = input_info.scratch_size_dwords;
|
||||
} else if constexpr (stage == ShaderType::Pixel) {
|
||||
options.scratch_dwords = input_info.scratch_size_dwords;
|
||||
} else {
|
||||
options.scratch_dwords = input_info.scratch_size_dwords;
|
||||
options.wave_size = input_info.wave_size;
|
||||
}
|
||||
auto translated = ShaderRecompiler::TranslateProgram(params.code, options);
|
||||
if (entry == programs.end()) {
|
||||
auto resource_plan = ShaderRecompiler::IR::ExtractResourcePlan(translated.program);
|
||||
EXIT_IF(!ShaderRecompiler::IR::MaterializeResources(resource_plan, runtime, resources,
|
||||
specialization));
|
||||
entry = programs.try_emplace(lookup_key, std::move(resource_plan)).first;
|
||||
}
|
||||
entry->second.permutations.push_back(CompilePermutation<stage>(
|
||||
params, options, std::move(translated), std::move(specialization), push_data_cursor));
|
||||
const auto& permutation = entry->second.permutations.back();
|
||||
input_info.stage = {.program = &permutation.program, .resources = std::move(resources)};
|
||||
permutation.program.bindings.AdvancePushData(push_data_cursor);
|
||||
|
||||
std::printf("Num compiled %u shaders\n", ++num_compiled);
|
||||
return handle;
|
||||
return permutation.handle;
|
||||
}
|
||||
|
||||
explicit ProgramCache(vk::Device device): device(device) {
|
||||
key_scratch.reserve(MaxStaticKeyWords);
|
||||
lookup_key.static_state.reserve(MaxStaticKeyWords);
|
||||
}
|
||||
~ProgramCache() {
|
||||
for (const auto& [key, entry]: programs) {
|
||||
(void)key;
|
||||
for (const auto& permutation: entry.permutations) {
|
||||
device.destroyShaderModule(permutation.handle.module, nullptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::unordered_map<SourceKey, std::vector<Permutation>, SourceKeyHash> programs;
|
||||
std::vector<uint32_t> key_scratch;
|
||||
vk::Device device;
|
||||
uint32_t num_compiled = 0;
|
||||
std::unordered_map<ProgramKey, SourceEntry, ProgramKeyHash> programs;
|
||||
ProgramKey lookup_key;
|
||||
vk::Device device;
|
||||
uint32_t num_compiled = 0;
|
||||
uint64_t next_shader_id = 0;
|
||||
};
|
||||
|
||||
PipelineCache::PipelineCache(GraphicContext& graphics)
|
||||
@@ -181,12 +401,6 @@ PipelineCache::~PipelineCache() {
|
||||
};
|
||||
destroy(m_graphics_pipelines);
|
||||
destroy(m_compute_pipelines);
|
||||
for (const auto& [key, permutations]: m_program_cache->programs) {
|
||||
(void)key;
|
||||
for (const auto& permutation: permutations) {
|
||||
m_graphics.device.destroyShaderModule(permutation.handle.module, nullptr);
|
||||
}
|
||||
}
|
||||
if (m_driver_cache != nullptr) {
|
||||
m_graphics.device.destroyPipelineCache(m_driver_cache, nullptr);
|
||||
}
|
||||
@@ -212,7 +426,7 @@ void PipelineCache::InitializeDriverCache() {
|
||||
return;
|
||||
}
|
||||
|
||||
m_driver_cache_path = std::filesystem::path("_PipelineCache") / (title_id + ".bin");
|
||||
m_driver_cache_path = std::filesystem::path("_PipelineCache") / (title_id + ".bin");
|
||||
const auto path = Common::PathToString(m_driver_cache_path);
|
||||
const bool cache_exists = Common::File::IsFileExisting(m_driver_cache_path);
|
||||
if (cache_exists) {
|
||||
@@ -224,7 +438,7 @@ void PipelineCache::InitializeDriverCache() {
|
||||
if (cache_exists) {
|
||||
Common::File file(m_driver_cache_path, Common::File::Mode::Read);
|
||||
const auto file_size = file.IsInvalid() ? 0 : file.Size();
|
||||
const auto signature = DriverCacheSignature(m_graphics.GetPhysicalDeviceProperties());
|
||||
const auto signature = DriverCacheSignature(m_graphics.GetPhysicalDeviceProperties());
|
||||
if (file_size >= signature.size() + sizeof(uint64_t) &&
|
||||
file_size <= std::numeric_limits<uint32_t>::max()) {
|
||||
std::string cached_signature(signature.size(), '\0');
|
||||
@@ -236,7 +450,8 @@ void PipelineCache::InitializeDriverCache() {
|
||||
file.Read(cached_signature.data(), static_cast<uint32_t>(cached_signature.size()),
|
||||
&signature_read);
|
||||
file.Read(&payload_hash, sizeof(payload_hash), &hash_read);
|
||||
file.Read(initial_data.data(), static_cast<uint32_t>(initial_data.size()), &payload_read);
|
||||
file.Read(initial_data.data(), static_cast<uint32_t>(initial_data.size()),
|
||||
&payload_read);
|
||||
file.Close();
|
||||
if (signature_read != cached_signature.size() || hash_read != sizeof(payload_hash) ||
|
||||
payload_read != initial_data.size() || cached_signature != signature ||
|
||||
@@ -248,8 +463,7 @@ void PipelineCache::InitializeDriverCache() {
|
||||
}
|
||||
} else {
|
||||
file.Close();
|
||||
PipelineCacheLog("Vulkan pipeline cache: invalidating {} (invalid file size)",
|
||||
path);
|
||||
PipelineCacheLog("Vulkan pipeline cache: invalidating {} (invalid file size)", path);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -308,7 +522,7 @@ void PipelineCache::Save() {
|
||||
return;
|
||||
}
|
||||
payload.resize(size);
|
||||
auto prefix = DriverCacheSignature(m_graphics.GetPhysicalDeviceProperties());
|
||||
auto prefix = DriverCacheSignature(m_graphics.GetPhysicalDeviceProperties());
|
||||
const auto payload_hash = XXH3_64bits(payload.data(), payload.size());
|
||||
prefix.append(reinterpret_cast<const char*>(&payload_hash), sizeof(payload_hash));
|
||||
if (!Common::File::CreateDirectories(m_driver_cache_path.parent_path())) {
|
||||
@@ -338,15 +552,20 @@ void PipelineCache::Save() {
|
||||
m_driver_cache = nullptr;
|
||||
}
|
||||
|
||||
ShaderProgram PipelineCache::GetVertexProgram(const HW::VertexShaderInfo& regs,
|
||||
const HW::ShaderRegisters& sh,
|
||||
const HW::Context& context,
|
||||
ShaderVertexInputInfo& input_info) {
|
||||
const auto params = PrepareProgram(regs, sh, input_info);
|
||||
PipelineCache::GraphicsPrograms PipelineCache::GetGraphicsPrograms(
|
||||
const HW::VertexShaderInfo& vertex_regs, const HW::PixelShaderInfo& pixel_regs,
|
||||
const HW::ShaderRegisters& sh, const HW::Context& context,
|
||||
std::span<const Prospero::ColorComponentMapping, 8> target_export_mapping,
|
||||
bool pixel_active, ShaderVertexInputInfo& vertex_info, ShaderPixelInputInfo& pixel_info) {
|
||||
const auto vertex_params = PrepareProgram(vertex_regs, sh, vertex_info);
|
||||
ShaderParams pixel_params;
|
||||
if (pixel_active) {
|
||||
pixel_params = PrepareProgram(pixel_regs, sh, target_export_mapping, pixel_info);
|
||||
}
|
||||
if (context.GetClipControl().clip_disable) {
|
||||
const auto& viewport = context.GetScreenViewport().viewports[0];
|
||||
const auto& limits = m_graphics.GetPhysicalDeviceProperties().limits;
|
||||
auto& clip = input_info.clip_space;
|
||||
auto& clip = vertex_info.clip_space;
|
||||
clip.scale[0] = viewport.xscale;
|
||||
clip.scale[1] = viewport.yscale;
|
||||
clip.offset[0] = viewport.xoffset;
|
||||
@@ -358,26 +577,23 @@ ShaderProgram PipelineCache::GetVertexProgram(const HW::VertexShaderInfo& regs,
|
||||
clip.enabled = true;
|
||||
}
|
||||
Common::LockGuard lock(m_mutex);
|
||||
return m_program_cache->Get(params, input_info);
|
||||
}
|
||||
|
||||
ShaderProgram PipelineCache::GetPixelProgram(
|
||||
const HW::PixelShaderInfo& regs, const HW::ShaderRegisters& sh,
|
||||
const ShaderVertexInputInfo& vertex_info,
|
||||
std::span<const Prospero::ColorComponentMapping, 8> target_export_mapping,
|
||||
ShaderPixelInputInfo& input_info) {
|
||||
const auto params = PrepareProgram(regs, sh, vertex_info, target_export_mapping, input_info);
|
||||
Common::LockGuard lock(m_mutex);
|
||||
return m_program_cache->Get(params, input_info);
|
||||
uint32_t push_data_cursor = 0;
|
||||
GraphicsPrograms result;
|
||||
if (pixel_active) {
|
||||
result.pixel = m_program_cache->Get(pixel_params, pixel_info, push_data_cursor);
|
||||
}
|
||||
result.vertex = m_program_cache->Get(vertex_params, vertex_info, push_data_cursor);
|
||||
return result;
|
||||
}
|
||||
|
||||
ShaderProgram PipelineCache::GetComputeProgram(const HW::ComputeShaderInfo& regs,
|
||||
const HW::ShaderRegisters& sh,
|
||||
ShaderComputeInputInfo& input_info) {
|
||||
input_info.needs_lds_barriers = !m_graphics.compute_wave64_supported;
|
||||
const auto params = PrepareProgram(regs, sh, input_info);
|
||||
const auto params = PrepareProgram(regs, sh, input_info);
|
||||
Common::LockGuard lock(m_mutex);
|
||||
return m_program_cache->Get(params, input_info);
|
||||
uint32_t push_data_cursor = 0;
|
||||
return m_program_cache->Get(params, input_info, push_data_cursor);
|
||||
}
|
||||
|
||||
bool PipelineStaticParameters::operator==(const PipelineStaticParameters& other) const noexcept {
|
||||
@@ -401,8 +617,7 @@ PipelineCache::GraphicsPipeline& PipelineCache::CreateGraphicsPipeline(
|
||||
Common::LockGuard lock(m_mutex);
|
||||
auto& ctx = command.GetRegisters();
|
||||
|
||||
const HW::BlendColor& bclr = ctx.GetBlendColor();
|
||||
uint32_t color_mask[RENDER_COLOR_ATTACHMENTS_MAX] = {};
|
||||
uint32_t color_mask[RENDER_COLOR_ATTACHMENTS_MAX] = {};
|
||||
for (uint32_t i = 0; i < color_count; i++) {
|
||||
color_mask[i] =
|
||||
(colors[i].image_id ? colors[i].export_mapping.ApplyMask(render_target_mask_slot(
|
||||
@@ -506,11 +721,6 @@ PipelineCache::GraphicsPipeline& PipelineCache::CreateGraphicsPipeline(
|
||||
static_params.blend_enable[i] = bc.enable;
|
||||
static_params.blend_bypass[i] = rt.info.blend_bypass;
|
||||
}
|
||||
static_params.blend_color_red = bclr.red;
|
||||
static_params.blend_color_green = bclr.green;
|
||||
static_params.blend_color_blue = bclr.blue;
|
||||
static_params.blend_color_alpha = bclr.alpha;
|
||||
|
||||
NormalizeStaticParamsForDynamicState(static_params);
|
||||
|
||||
GraphicsPipelineKey key {};
|
||||
@@ -518,6 +728,30 @@ PipelineCache::GraphicsPipeline& PipelineCache::CreateGraphicsPipeline(
|
||||
key.vs_shader_id = p.vs_shader_id;
|
||||
key.ps_shader_id = p.ps_shader_id;
|
||||
key.static_params = static_params;
|
||||
EXIT_IF(vs_input_info.buffers_num < 0 ||
|
||||
vs_input_info.buffers_num > ShaderVertexInputInfo::RES_MAX ||
|
||||
vs_input_info.resources_num < 0 ||
|
||||
vs_input_info.resources_num > ShaderVertexInputInfo::RES_MAX);
|
||||
key.vertex_input.binding_count = static_cast<uint8_t>(vs_input_info.buffers_num);
|
||||
key.vertex_input.attribute_count = static_cast<uint8_t>(vs_input_info.resources_num);
|
||||
uint32_t attributes_num = 0;
|
||||
for (int binding = 0; binding < vs_input_info.buffers_num; binding++) {
|
||||
const auto& buffer = vs_input_info.buffers[binding];
|
||||
EXIT_IF(buffer.attr_num < 0 || buffer.attr_num > ShaderVertexInputBuffer::ATTR_MAX);
|
||||
attributes_num += static_cast<uint32_t>(buffer.attr_num);
|
||||
EXIT_IF(attributes_num > static_cast<uint32_t>(vs_input_info.resources_num));
|
||||
key.vertex_input.bindings[binding] = {.stride = buffer.stride,
|
||||
.instance = buffer.fetch_index != 0};
|
||||
for (int attribute = 0; attribute < buffer.attr_num; attribute++) {
|
||||
const auto index = buffer.attr_indices[attribute];
|
||||
EXIT_IF(index < 0 || index >= vs_input_info.resources_num);
|
||||
key.vertex_input.attributes[index] = {
|
||||
.offset = buffer.attr_offsets[attribute],
|
||||
.binding = static_cast<uint8_t>(binding),
|
||||
};
|
||||
}
|
||||
}
|
||||
EXIT_IF(attributes_num != static_cast<uint32_t>(vs_input_info.resources_num));
|
||||
|
||||
if (auto iter = m_graphics_pipelines.find(key); iter != m_graphics_pipelines.end()) {
|
||||
return *iter->second;
|
||||
@@ -528,16 +762,16 @@ PipelineCache::GraphicsPipeline& PipelineCache::CreateGraphicsPipeline(
|
||||
if (ps_active) {
|
||||
ShaderDbgDumpInputInfo(*ps_input_info);
|
||||
}
|
||||
LOGF("PipelineTrace: shader modules VS=%" PRIu64 " module=%p PS=%" PRIu64
|
||||
" module=%p\n",
|
||||
LOGF("PipelineTrace: shader modules VS=%" PRIu64 " module=%p PS=%" PRIu64 " module=%p\n",
|
||||
vs_id, static_cast<void*>(vertex_program.module), ps_id,
|
||||
static_cast<void*>(pixel_program.module));
|
||||
}
|
||||
|
||||
auto cached = std::make_unique<GraphicsPipeline>(p);
|
||||
LogPipelineTrace("CreatePipelineInternal begin", vs_id, ps_id);
|
||||
CreatePipelineInternal(m_graphics, *cached, rendering, vs_input_info, vertex_program.module,
|
||||
ps_input_info, pixel_program.module, static_params, m_driver_cache);
|
||||
CreatePipelineInternal(m_graphics, *cached, rendering, key.vertex_input, vs_input_info,
|
||||
vertex_program.module, ps_input_info, pixel_program.module,
|
||||
static_params, m_driver_cache);
|
||||
LogPipelineTrace("CreatePipelineInternal done", vs_id, ps_id);
|
||||
|
||||
EXIT_NOT_IMPLEMENTED(cached->pipeline == nullptr);
|
||||
|
||||
@@ -65,10 +65,6 @@ struct PipelineStaticParameters {
|
||||
bool separate_alpha_blend[RENDER_COLOR_ATTACHMENTS_MAX] = {};
|
||||
bool blend_enable[RENDER_COLOR_ATTACHMENTS_MAX] = {};
|
||||
bool blend_bypass[RENDER_COLOR_ATTACHMENTS_MAX] = {};
|
||||
float blend_color_red = 0.0f;
|
||||
float blend_color_green = 0.0f;
|
||||
float blend_color_blue = 0.0f;
|
||||
float blend_color_alpha = 0.0f;
|
||||
|
||||
bool operator==(const PipelineStaticParameters& other) const noexcept;
|
||||
};
|
||||
@@ -85,7 +81,7 @@ static_assert(sizeof(PipelineStaticParameters) ==
|
||||
sizeof(bool) + sizeof(PipelineStencilStaticState) * 2 + sizeof(uint32_t) +
|
||||
sizeof(uint32_t[RENDER_COLOR_ATTACHMENTS_MAX]) + sizeof(bool) * 3 +
|
||||
sizeof(uint8_t[RENDER_COLOR_ATTACHMENTS_MAX]) * 6 +
|
||||
sizeof(bool[RENDER_COLOR_ATTACHMENTS_MAX]) * 3 + sizeof(float) * 4);
|
||||
sizeof(bool[RENDER_COLOR_ATTACHMENTS_MAX]) * 3);
|
||||
|
||||
struct PipelineRenderingState {
|
||||
std::array<vk::Format, RENDER_COLOR_ATTACHMENTS_MAX> color_formats {};
|
||||
@@ -96,13 +92,31 @@ struct PipelineRenderingState {
|
||||
bool operator==(const PipelineRenderingState&) const = default;
|
||||
};
|
||||
|
||||
struct PipelineVertexInputState {
|
||||
struct Binding {
|
||||
uint32_t stride = 0;
|
||||
bool instance = false;
|
||||
bool operator==(const Binding&) const = default;
|
||||
};
|
||||
struct Attribute {
|
||||
uint32_t offset = 0;
|
||||
uint8_t binding = 0;
|
||||
bool operator==(const Attribute&) const = default;
|
||||
};
|
||||
|
||||
std::array<Binding, ShaderVertexInputInfo::RES_MAX> bindings {};
|
||||
std::array<Attribute, ShaderVertexInputInfo::RES_MAX> attributes {};
|
||||
uint8_t binding_count = 0;
|
||||
uint8_t attribute_count = 0;
|
||||
|
||||
bool operator==(const PipelineVertexInputState&) const = default;
|
||||
};
|
||||
|
||||
struct ShaderProgram {
|
||||
uint64_t id = 0;
|
||||
vk::ShaderModule module = nullptr;
|
||||
|
||||
explicit operator bool() const {
|
||||
return id != 0 && module != nullptr;
|
||||
}
|
||||
explicit operator bool() const { return id != 0 && module != nullptr; }
|
||||
};
|
||||
|
||||
class PipelineCache {
|
||||
@@ -128,25 +142,26 @@ public:
|
||||
uint64_t cs_shader_id = 0;
|
||||
};
|
||||
|
||||
ShaderProgram GetVertexProgram(const HW::VertexShaderInfo& regs,
|
||||
const HW::ShaderRegisters& sh,
|
||||
const HW::Context& context,
|
||||
ShaderVertexInputInfo& input_info);
|
||||
ShaderProgram
|
||||
GetPixelProgram(const HW::PixelShaderInfo& regs, const HW::ShaderRegisters& sh,
|
||||
const ShaderVertexInputInfo& vertex_info,
|
||||
std::span<const Prospero::ColorComponentMapping, 8> target_export_mapping,
|
||||
ShaderPixelInputInfo& input_info);
|
||||
ShaderProgram GetComputeProgram(const HW::ComputeShaderInfo& regs,
|
||||
const HW::ShaderRegisters& sh,
|
||||
ShaderComputeInputInfo& input_info);
|
||||
struct GraphicsPrograms {
|
||||
ShaderProgram vertex;
|
||||
ShaderProgram pixel;
|
||||
};
|
||||
|
||||
GraphicsPipeline& CreateGraphicsPipeline(
|
||||
std::span<const RenderColorInfo> colors, const RenderDepthInfo& depth,
|
||||
const ShaderVertexInputInfo& vs_input_info, CommandBuffer& command,
|
||||
const ShaderPixelInputInfo* ps_input_info, vk::PrimitiveTopology topology,
|
||||
bool primitive_restart_enable, const ShaderProgram& vertex_program,
|
||||
const ShaderProgram& pixel_program);
|
||||
GraphicsPrograms GetGraphicsPrograms(
|
||||
const HW::VertexShaderInfo& vertex_regs, const HW::PixelShaderInfo& pixel_regs,
|
||||
const HW::ShaderRegisters& sh, const HW::Context& context,
|
||||
std::span<const Prospero::ColorComponentMapping, 8> target_export_mapping,
|
||||
bool pixel_active, ShaderVertexInputInfo& vertex_info, ShaderPixelInputInfo& pixel_info);
|
||||
ShaderProgram GetComputeProgram(const HW::ComputeShaderInfo& regs,
|
||||
const HW::ShaderRegisters& sh,
|
||||
ShaderComputeInputInfo& input_info);
|
||||
|
||||
GraphicsPipeline&
|
||||
CreateGraphicsPipeline(std::span<const RenderColorInfo> colors, const RenderDepthInfo& depth,
|
||||
const ShaderVertexInputInfo& vs_input_info, CommandBuffer& command,
|
||||
const ShaderPixelInputInfo* ps_input_info,
|
||||
vk::PrimitiveTopology topology, bool primitive_restart_enable,
|
||||
const ShaderProgram& vertex_program, const ShaderProgram& pixel_program);
|
||||
ComputePipeline& CreateComputePipeline(ShaderComputeInputInfo& input_info,
|
||||
const ShaderProgram& compute_program);
|
||||
|
||||
@@ -157,11 +172,13 @@ private:
|
||||
PipelineRenderingState rendering;
|
||||
uint64_t vs_shader_id = 0;
|
||||
uint64_t ps_shader_id = 0;
|
||||
PipelineVertexInputState vertex_input;
|
||||
PipelineStaticParameters static_params;
|
||||
|
||||
bool operator==(const GraphicsPipelineKey& other) const {
|
||||
return rendering == other.rendering && vs_shader_id == other.vs_shader_id &&
|
||||
ps_shader_id == other.ps_shader_id && static_params == other.static_params;
|
||||
ps_shader_id == other.ps_shader_id && vertex_input == other.vertex_input &&
|
||||
static_params == other.static_params;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -202,6 +219,16 @@ private:
|
||||
PipelineKeyHash::MixRendering(hash, key.rendering);
|
||||
PipelineKeyHash::Mix(hash, key.vs_shader_id);
|
||||
PipelineKeyHash::Mix(hash, key.ps_shader_id);
|
||||
PipelineKeyHash::Mix(hash, key.vertex_input.binding_count);
|
||||
for (uint32_t i = 0; i < key.vertex_input.binding_count; i++) {
|
||||
PipelineKeyHash::Mix(hash, key.vertex_input.bindings[i].stride);
|
||||
PipelineKeyHash::Mix(hash, key.vertex_input.bindings[i].instance);
|
||||
}
|
||||
PipelineKeyHash::Mix(hash, key.vertex_input.attribute_count);
|
||||
for (uint32_t i = 0; i < key.vertex_input.attribute_count; i++) {
|
||||
PipelineKeyHash::Mix(hash, key.vertex_input.attributes[i].offset);
|
||||
PipelineKeyHash::Mix(hash, key.vertex_input.attributes[i].binding);
|
||||
}
|
||||
PipelineKeyHash::MixStaticParams(hash, key.static_params);
|
||||
return hash;
|
||||
}
|
||||
@@ -215,7 +242,7 @@ private:
|
||||
}
|
||||
};
|
||||
|
||||
GraphicContext& m_graphics;
|
||||
GraphicContext& m_graphics;
|
||||
std::unique_ptr<ProgramCache> m_program_cache;
|
||||
vk::PipelineCache m_driver_cache = nullptr;
|
||||
std::filesystem::path m_driver_cache_path;
|
||||
@@ -232,13 +259,13 @@ private:
|
||||
void LogPipelineTrace(const char* phase, uint64_t vertex_program_id, uint64_t pixel_program_id);
|
||||
void CreatePipelineInternal(
|
||||
GraphicContext& graphics, PipelineCache::GraphicsPipeline& pipeline,
|
||||
const PipelineRenderingState& rendering, const ShaderVertexInputInfo& vs_input_info,
|
||||
vk::ShaderModule vertex_module, const ShaderPixelInputInfo* ps_input_info,
|
||||
vk::ShaderModule pixel_module, const PipelineStaticParameters& static_params,
|
||||
vk::PipelineCache driver_cache);
|
||||
const PipelineRenderingState& rendering, const PipelineVertexInputState& vertex_input,
|
||||
const ShaderVertexInputInfo& vs_input_info, vk::ShaderModule vertex_module,
|
||||
const ShaderPixelInputInfo* ps_input_info, vk::ShaderModule pixel_module,
|
||||
const PipelineStaticParameters& static_params, vk::PipelineCache driver_cache);
|
||||
void CreatePipelineInternal(GraphicContext& graphics, PipelineCache::ComputePipeline& pipeline,
|
||||
const ShaderComputeInputInfo& input_info,
|
||||
vk::ShaderModule compute_module, vk::PipelineCache driver_cache);
|
||||
vk::ShaderModule compute_module, vk::PipelineCache driver_cache);
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
|
||||
|
||||
@@ -69,7 +69,7 @@ vk::BufferMemoryBarrier MakeGdsDependency(vk::Buffer buffer) {
|
||||
}
|
||||
|
||||
std::vector<ShaderBufferWriteRange>
|
||||
CollectShaderBufferWrites(const ShaderRecompiler::IR::Program& program,
|
||||
CollectShaderBufferWrites(const ShaderRecompiler::IR::CompiledShaderInfo& program,
|
||||
const ShaderRecompiler::IR::ResourceSnapshot& resources) {
|
||||
EXIT_IF(resources.buffers.size() != program.info.buffers.size());
|
||||
std::vector<ShaderBufferWriteRange> writes;
|
||||
@@ -97,7 +97,7 @@ CollectShaderBufferWrites(const ShaderRecompiler::IR::Program& program,
|
||||
|
||||
bool HasShaderBufferWrites(const ShaderStageRuntime& runtime) {
|
||||
EXIT_IF(!runtime);
|
||||
return !CollectShaderBufferWrites(*runtime.program, *runtime.resources).empty();
|
||||
return !CollectShaderBufferWrites(*runtime.program, runtime.resources).empty();
|
||||
}
|
||||
|
||||
void ShaderAccessBarrier(vk::CommandBuffer vk_buffer, vk::PipelineStageFlags source_stages) {
|
||||
|
||||
@@ -23,7 +23,7 @@ VulkanMemoryBarrier MakeShaderWriteHazardDependency();
|
||||
VulkanMemoryBarrier MakeShaderWriteDependency();
|
||||
vk::BufferMemoryBarrier MakeGdsDependency(vk::Buffer buffer);
|
||||
std::vector<ShaderBufferWriteRange>
|
||||
CollectShaderBufferWrites(const ShaderRecompiler::IR::Program& program,
|
||||
CollectShaderBufferWrites(const ShaderRecompiler::IR::CompiledShaderInfo& program,
|
||||
const ShaderRecompiler::IR::ResourceSnapshot& resources);
|
||||
bool HasShaderBufferWrites(const ShaderStageRuntime& runtime);
|
||||
void ShaderAccessBarrier(vk::CommandBuffer vk_buffer, vk::PipelineStageFlags source_stages);
|
||||
|
||||
@@ -390,7 +390,7 @@ static vk::BlendOp GetBlendOp(uint32_t op) {
|
||||
}
|
||||
|
||||
static void AddLayoutBindings(std::vector<vk::DescriptorSetLayoutBinding>& descriptor_bindings,
|
||||
const ShaderRecompiler::IR::Program& program,
|
||||
const ShaderRecompiler::IR::CompiledShaderInfo& program,
|
||||
vk::ShaderStageFlagBits stage) {
|
||||
for (const auto& binding: program.bindings.descriptors) {
|
||||
descriptor_bindings.push_back(
|
||||
@@ -421,10 +421,10 @@ static void CreateDescriptorLayout(GraphicContext& graphics, PipelineCache::Pipe
|
||||
// NOLINTNEXTLINE(readability-function-cognitive-complexity)
|
||||
void CreatePipelineInternal(
|
||||
GraphicContext& graphics, PipelineCache::GraphicsPipeline& pipeline,
|
||||
const PipelineRenderingState& rendering, const ShaderVertexInputInfo& vs_input_info,
|
||||
vk::ShaderModule vertex_module, const ShaderPixelInputInfo* ps_input_info,
|
||||
vk::ShaderModule pixel_module, const PipelineStaticParameters& static_params,
|
||||
vk::PipelineCache driver_cache) {
|
||||
const PipelineRenderingState& rendering, const PipelineVertexInputState& vertex_input,
|
||||
const ShaderVertexInputInfo& vs_input_info, vk::ShaderModule vertex_module,
|
||||
const ShaderPixelInputInfo* ps_input_info, vk::ShaderModule pixel_module,
|
||||
const PipelineStaticParameters& static_params, vk::PipelineCache driver_cache) {
|
||||
const bool ps_active = ps_input_info != nullptr;
|
||||
EXIT_IF(vertex_module == nullptr || (ps_active && pixel_module == nullptr));
|
||||
|
||||
@@ -505,135 +505,131 @@ void CreatePipelineInternal(
|
||||
shader_stages[shader_stage_count++] = frag_shader_stage_info;
|
||||
}
|
||||
|
||||
vk::VertexInputAttributeDescription input_attr[ShaderVertexInputInfo::RES_MAX];
|
||||
vk::VertexInputBindingDescription input_desc[ShaderVertexInputInfo::RES_MAX];
|
||||
vk::VertexInputAttributeDescription input_attr[ShaderVertexInputInfo::RES_MAX] {};
|
||||
vk::VertexInputBindingDescription input_desc[ShaderVertexInputInfo::RES_MAX] {};
|
||||
|
||||
for (int bi = 0; bi < vs_input_info.buffers_num; bi++) {
|
||||
const auto& b = vs_input_info.buffers[bi];
|
||||
input_desc[bi].binding = bi;
|
||||
input_desc[bi].stride = b.stride;
|
||||
input_desc[bi].inputRate =
|
||||
(b.fetch_index == 0 ? vk::VertexInputRate::eVertex : vk::VertexInputRate::eInstance);
|
||||
for (uint32_t binding = 0; binding < vertex_input.binding_count; binding++) {
|
||||
input_desc[binding].binding = binding;
|
||||
input_desc[binding].stride = vertex_input.bindings[binding].stride;
|
||||
input_desc[binding].inputRate = vertex_input.bindings[binding].instance
|
||||
? vk::VertexInputRate::eInstance
|
||||
: vk::VertexInputRate::eVertex;
|
||||
}
|
||||
for (uint32_t index = 0; index < vertex_input.attribute_count; index++) {
|
||||
input_attr[index].binding = vertex_input.attributes[index].binding;
|
||||
input_attr[index].location = index;
|
||||
input_attr[index].offset = vertex_input.attributes[index].offset;
|
||||
|
||||
for (int ai = 0; ai < b.attr_num; ai++) {
|
||||
auto index = b.attr_indices[ai];
|
||||
input_attr[index].binding = bi;
|
||||
input_attr[index].location = index;
|
||||
input_attr[index].offset = b.attr_offsets[ai];
|
||||
uint32_t attr_size = 4;
|
||||
const auto registers_num = vs_input_info.resources_dst[index].registers_num;
|
||||
const auto compiled_components =
|
||||
vs_input_info.stage.program->info.vertex_fetch_components[index];
|
||||
const auto used_components =
|
||||
compiled_components > 0 ? static_cast<int>(compiled_components) : registers_num;
|
||||
GetInputFormat(vs_input_info.resources[index], input_attr[index].format, attr_size,
|
||||
static_cast<uint32_t>(used_components));
|
||||
|
||||
uint32_t attr_size = 4;
|
||||
auto registers_num = vs_input_info.resources_dst[index].registers_num;
|
||||
const auto compiled_components =
|
||||
vs_input_info.stage.program->info.vertex_fetch_components[index];
|
||||
auto used_components =
|
||||
compiled_components > 0 ? static_cast<int>(compiled_components) : registers_num;
|
||||
GetInputFormat(vs_input_info.resources[index], input_attr[index].format, attr_size,
|
||||
static_cast<uint32_t>(used_components));
|
||||
|
||||
if (graphics_debug_dump_enabled()) {
|
||||
static std::atomic_uint log_count = 0;
|
||||
const auto log_id = log_count.fetch_add(1, std::memory_order_relaxed);
|
||||
if (log_id < 128) {
|
||||
LOGF("VertexInputState[%u]: attr=%u binding=%u offset=%u stride=%u fmt=%d "
|
||||
"src_fmt=%u dst=v%u regs=%u"
|
||||
" fetched_components=%u attr_size=%u swizzle=%u,%u,%u,%u\n",
|
||||
log_id, index, input_attr[index].binding, input_attr[index].offset,
|
||||
input_desc[bi].stride, static_cast<int>(input_attr[index].format),
|
||||
static_cast<uint32_t>(vs_input_info.resources[index].Format()),
|
||||
static_cast<uint32_t>(vs_input_info.resources_dst[index].register_start),
|
||||
static_cast<uint32_t>(registers_num),
|
||||
static_cast<uint32_t>(used_components), attr_size,
|
||||
static_cast<uint32_t>(vs_input_info.resources[index].DstSelX()),
|
||||
static_cast<uint32_t>(vs_input_info.resources[index].DstSelY()),
|
||||
static_cast<uint32_t>(vs_input_info.resources[index].DstSelZ()),
|
||||
static_cast<uint32_t>(vs_input_info.resources[index].DstSelW()));
|
||||
}
|
||||
if (graphics_debug_dump_enabled()) {
|
||||
static std::atomic_uint log_count = 0;
|
||||
const auto log_id = log_count.fetch_add(1, std::memory_order_relaxed);
|
||||
if (log_id < 128) {
|
||||
LOGF("VertexInputState[%u]: attr=%u binding=%u offset=%u stride=%u fmt=%d "
|
||||
"src_fmt=%u dst=v%u regs=%u"
|
||||
" fetched_components=%u attr_size=%u swizzle=%u,%u,%u,%u\n",
|
||||
log_id, index, input_attr[index].binding, input_attr[index].offset,
|
||||
input_desc[input_attr[index].binding].stride,
|
||||
static_cast<int>(input_attr[index].format),
|
||||
static_cast<uint32_t>(vs_input_info.resources[index].Format()),
|
||||
static_cast<uint32_t>(vs_input_info.resources_dst[index].register_start),
|
||||
static_cast<uint32_t>(registers_num), static_cast<uint32_t>(used_components),
|
||||
attr_size, static_cast<uint32_t>(vs_input_info.resources[index].DstSelX()),
|
||||
static_cast<uint32_t>(vs_input_info.resources[index].DstSelY()),
|
||||
static_cast<uint32_t>(vs_input_info.resources[index].DstSelZ()),
|
||||
static_cast<uint32_t>(vs_input_info.resources[index].DstSelW()));
|
||||
}
|
||||
}
|
||||
|
||||
if (vs_input_info.resources[index].OutOfBounds() != 0) {
|
||||
static bool logged = false;
|
||||
if (!logged) {
|
||||
LOGF("VertexInput: temporary: accepting PS5 out-of-bounds behavior %" PRIu8
|
||||
"\n",
|
||||
vs_input_info.resources[index].OutOfBounds());
|
||||
logged = true;
|
||||
}
|
||||
if (vs_input_info.resources[index].OutOfBounds() != 0) {
|
||||
static bool logged = false;
|
||||
if (!logged) {
|
||||
LOGF("VertexInput: temporary: accepting PS5 out-of-bounds behavior %" PRIu8 "\n",
|
||||
vs_input_info.resources[index].OutOfBounds());
|
||||
logged = true;
|
||||
}
|
||||
}
|
||||
|
||||
EXIT_NOT_IMPLEMENTED(vs_input_info.resources[index].AddTid());
|
||||
EXIT_NOT_IMPLEMENTED(vs_input_info.resources[index].SwizzleEnabled());
|
||||
EXIT_NOT_IMPLEMENTED(vs_input_info.resources[index].AddTid());
|
||||
EXIT_NOT_IMPLEMENTED(vs_input_info.resources[index].SwizzleEnabled());
|
||||
|
||||
auto log_unsupported_vertex_swizzle = [index, attr_size, registers_num](
|
||||
uint32_t swizzle, uint32_t expected) {
|
||||
static bool logged = false;
|
||||
if (!logged) {
|
||||
LOGF(
|
||||
"VertexInput: temporary: accepting unsupported dst swizzle at attr %" PRIu32
|
||||
" (attr_size=%" PRIu32 ", regs=%" PRIu32 ", swizzle=0x%03" PRIx32
|
||||
", expected=0x%03" PRIx32 ")\n",
|
||||
static_cast<uint32_t>(index), attr_size, registers_num, swizzle, expected);
|
||||
logged = true;
|
||||
}
|
||||
};
|
||||
|
||||
switch (registers_num) {
|
||||
case 1: {
|
||||
auto swizzle = vs_input_info.resources[index].DstSelX();
|
||||
if (swizzle != DstSel(4)) {
|
||||
log_unsupported_vertex_swizzle(swizzle, DstSel(4));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 2: {
|
||||
auto swizzle = vs_input_info.resources[index].DstSelXY();
|
||||
auto expected = (attr_size == 1 ? DstSel(4, 0) : DstSel(4, 5));
|
||||
if (swizzle != expected) {
|
||||
log_unsupported_vertex_swizzle(swizzle, expected);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 3: {
|
||||
auto swizzle = vs_input_info.resources[index].DstSelXYZ();
|
||||
auto expected = DstSel(4, 5, 6);
|
||||
switch (attr_size) {
|
||||
case 1: expected = DstSel(4, 0, 0); break;
|
||||
case 2: expected = DstSel(4, 5, 0); break;
|
||||
default: break;
|
||||
}
|
||||
if (swizzle != expected) {
|
||||
log_unsupported_vertex_swizzle(swizzle, expected);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 4: {
|
||||
auto swizzle = vs_input_info.resources[index].DstSelXYZW();
|
||||
auto expected = DstSel(4, 5, 6, 7);
|
||||
bool supported = false;
|
||||
switch (attr_size) {
|
||||
case 1:
|
||||
expected = DstSel(4, 0, 0, 1);
|
||||
supported = (swizzle == expected);
|
||||
break;
|
||||
case 2:
|
||||
expected = DstSel(4, 5, 0, 1);
|
||||
supported = (swizzle == expected);
|
||||
break;
|
||||
case 3:
|
||||
expected = DstSel(4, 5, 6, 1);
|
||||
supported = (swizzle == expected || swizzle == DstSel(4, 5, 6, 0));
|
||||
break;
|
||||
default:
|
||||
supported = (swizzle == expected || swizzle == DstSel(4, 5, 6, 1) ||
|
||||
swizzle == DstSel(4, 5, 6, 0));
|
||||
break;
|
||||
}
|
||||
if (!supported) {
|
||||
log_unsupported_vertex_swizzle(swizzle, expected);
|
||||
}
|
||||
break;
|
||||
}
|
||||
default: EXIT("invalid registers_num");
|
||||
auto log_unsupported_vertex_swizzle = [index, attr_size, registers_num](uint32_t swizzle,
|
||||
uint32_t expected) {
|
||||
static bool logged = false;
|
||||
if (!logged) {
|
||||
LOGF("VertexInput: temporary: accepting unsupported dst swizzle at attr %" PRIu32
|
||||
" (attr_size=%" PRIu32 ", regs=%" PRIu32 ", swizzle=0x%03" PRIx32
|
||||
", expected=0x%03" PRIx32 ")\n",
|
||||
static_cast<uint32_t>(index), attr_size, registers_num, swizzle, expected);
|
||||
logged = true;
|
||||
}
|
||||
};
|
||||
|
||||
switch (registers_num) {
|
||||
case 1: {
|
||||
auto swizzle = vs_input_info.resources[index].DstSelX();
|
||||
if (swizzle != DstSel(4)) {
|
||||
log_unsupported_vertex_swizzle(swizzle, DstSel(4));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 2: {
|
||||
auto swizzle = vs_input_info.resources[index].DstSelXY();
|
||||
auto expected = (attr_size == 1 ? DstSel(4, 0) : DstSel(4, 5));
|
||||
if (swizzle != expected) {
|
||||
log_unsupported_vertex_swizzle(swizzle, expected);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 3: {
|
||||
auto swizzle = vs_input_info.resources[index].DstSelXYZ();
|
||||
auto expected = DstSel(4, 5, 6);
|
||||
switch (attr_size) {
|
||||
case 1: expected = DstSel(4, 0, 0); break;
|
||||
case 2: expected = DstSel(4, 5, 0); break;
|
||||
default: break;
|
||||
}
|
||||
if (swizzle != expected) {
|
||||
log_unsupported_vertex_swizzle(swizzle, expected);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 4: {
|
||||
auto swizzle = vs_input_info.resources[index].DstSelXYZW();
|
||||
auto expected = DstSel(4, 5, 6, 7);
|
||||
bool supported = false;
|
||||
switch (attr_size) {
|
||||
case 1:
|
||||
expected = DstSel(4, 0, 0, 1);
|
||||
supported = (swizzle == expected);
|
||||
break;
|
||||
case 2:
|
||||
expected = DstSel(4, 5, 0, 1);
|
||||
supported = (swizzle == expected);
|
||||
break;
|
||||
case 3:
|
||||
expected = DstSel(4, 5, 6, 1);
|
||||
supported = (swizzle == expected || swizzle == DstSel(4, 5, 6, 0));
|
||||
break;
|
||||
default:
|
||||
supported = (swizzle == expected || swizzle == DstSel(4, 5, 6, 1) ||
|
||||
swizzle == DstSel(4, 5, 6, 0));
|
||||
break;
|
||||
}
|
||||
if (!supported) {
|
||||
log_unsupported_vertex_swizzle(swizzle, expected);
|
||||
}
|
||||
break;
|
||||
}
|
||||
default: EXIT("invalid registers_num");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -641,9 +637,9 @@ void CreatePipelineInternal(
|
||||
vertex_input_info.sType = vk::StructureType::ePipelineVertexInputStateCreateInfo;
|
||||
vertex_input_info.pNext = nullptr;
|
||||
vertex_input_info.flags = {};
|
||||
vertex_input_info.vertexBindingDescriptionCount = vs_input_info.buffers_num;
|
||||
vertex_input_info.vertexBindingDescriptionCount = vertex_input.binding_count;
|
||||
vertex_input_info.pVertexBindingDescriptions = input_desc;
|
||||
vertex_input_info.vertexAttributeDescriptionCount = vs_input_info.resources_num;
|
||||
vertex_input_info.vertexAttributeDescriptionCount = vertex_input.attribute_count;
|
||||
vertex_input_info.pVertexAttributeDescriptions = input_attr;
|
||||
|
||||
vk::PipelineInputAssemblyStateCreateInfo input_assembly {};
|
||||
@@ -792,15 +788,11 @@ void CreatePipelineInternal(
|
||||
#else
|
||||
color_blending.pNext = &color_write;
|
||||
#endif
|
||||
color_blending.flags = {};
|
||||
color_blending.logicOpEnable = VK_FALSE;
|
||||
color_blending.logicOp = vk::LogicOp::eCopy;
|
||||
color_blending.attachmentCount = static_params.color_count;
|
||||
color_blending.pAttachments = color_blend_attachment;
|
||||
color_blending.blendConstants[0] = static_params.blend_color_red;
|
||||
color_blending.blendConstants[1] = static_params.blend_color_green;
|
||||
color_blending.blendConstants[2] = static_params.blend_color_blue;
|
||||
color_blending.blendConstants[3] = static_params.blend_color_alpha;
|
||||
color_blending.flags = {};
|
||||
color_blending.logicOpEnable = VK_FALSE;
|
||||
color_blending.logicOp = vk::LogicOp::eCopy;
|
||||
color_blending.attachmentCount = static_params.color_count;
|
||||
color_blending.pAttachments = color_blend_attachment;
|
||||
|
||||
EXIT_IF(!vs_input_info.stage);
|
||||
std::vector<vk::DescriptorSetLayoutBinding> descriptor_bindings;
|
||||
@@ -814,8 +806,8 @@ void CreatePipelineInternal(
|
||||
CreateDescriptorLayout(graphics, pipeline, descriptor_bindings);
|
||||
constexpr auto GraphicsStages =
|
||||
vk::ShaderStageFlagBits::eVertex | vk::ShaderStageFlagBits::eFragment;
|
||||
const vk::PushConstantRange push_constants {
|
||||
GraphicsStages, 0, ShaderRecompiler::IR::NativePushConstantSize};
|
||||
const vk::PushConstantRange push_constants {GraphicsStages, 0,
|
||||
ShaderRecompiler::IR::NativePushConstantSize};
|
||||
|
||||
vk::PipelineLayoutCreateInfo pipeline_layout_info {};
|
||||
pipeline_layout_info.sType = vk::StructureType::ePipelineLayoutCreateInfo;
|
||||
@@ -877,6 +869,7 @@ void CreatePipelineInternal(
|
||||
vk::DynamicState::eStencilCompareMask,
|
||||
vk::DynamicState::eStencilReference,
|
||||
vk::DynamicState::eStencilWriteMask,
|
||||
vk::DynamicState::eBlendConstants,
|
||||
#if !defined(__APPLE__)
|
||||
// Keep last so depth-only pipelines can omit this dynamic state.
|
||||
vk::DynamicState::eColorWriteEnableEXT, // unsupported by MoltenVK; static mask instead
|
||||
|
||||
@@ -190,8 +190,6 @@ private:
|
||||
std::vector<vk::DescriptorImageInfo> m_descriptor_images;
|
||||
std::vector<vk::WriteDescriptorSet> m_descriptor_writes;
|
||||
std::vector<uint32_t> m_image_occurrences;
|
||||
std::array<uint32_t, ShaderRecompiler::IR::NativePushConstantSize / sizeof(uint32_t)>
|
||||
m_push_constants {};
|
||||
|
||||
friend struct RenderExecutorTestAccess;
|
||||
};
|
||||
|
||||
@@ -46,7 +46,7 @@ static uint64_t BufferDescriptorSize(const ShaderBufferResource& descriptor) {
|
||||
bool RenderExecutor::TryConsumeComputeMetaClear(const ShaderComputeInputInfo& input,
|
||||
const CommandBuffer& buffer) {
|
||||
const auto& program = *input.stage.program;
|
||||
const auto& resources = *input.stage.resources;
|
||||
const auto& resources = input.stage.resources;
|
||||
if (resources.buffers.size() != program.info.buffers.size()) {
|
||||
EXIT("compute runtime buffer count does not match shader metadata\n");
|
||||
}
|
||||
@@ -81,7 +81,7 @@ bool ResolveComputeImageClear(const ShaderComputeInputInfo& input, uint32_t grou
|
||||
ShaderBufferResource& resolved_descriptor, uint32_t& resolved_clear,
|
||||
uint64_t& resolved_size) {
|
||||
const auto& program = *input.stage.program;
|
||||
const auto& resources = *input.stage.resources;
|
||||
const auto& resources = input.stage.resources;
|
||||
if (program.info.buffers.size() != 1 || resources.buffers.size() != 1 ||
|
||||
!program.info.images.empty() || !program.info.samplers.empty() || program.info.uses_dma ||
|
||||
!resources.images.empty() || !resources.samplers.empty()) {
|
||||
@@ -224,7 +224,7 @@ void RenderExecutor::DispatchDirect(uint64_t submit_id, CommandBuffer& buffer,
|
||||
const bool large_workgroup =
|
||||
(input_info.threads_num[0] * input_info.threads_num[1] * input_info.threads_num[2] >= 512);
|
||||
const auto& program = *input_info.stage.program;
|
||||
const auto& resources = *input_info.stage.resources;
|
||||
const auto& resources = input_info.stage.resources;
|
||||
if (TryConsumeComputeMetaClear(input_info, buffer)) {
|
||||
ResetBindings();
|
||||
return;
|
||||
@@ -249,7 +249,10 @@ void RenderExecutor::DispatchDirect(uint64_t submit_id, CommandBuffer& buffer,
|
||||
thread_group_z, mode, input_info.threads_num[0], input_info.threads_num[1],
|
||||
input_info.threads_num[2], program.info.buffers.size(), program.info.images.size(),
|
||||
sampled_images, program.info.images.size() - sampled_images,
|
||||
program.info.samplers.size(), program.bindings.push_constant_size);
|
||||
program.info.samplers.size(),
|
||||
program.bindings.UsesPushData()
|
||||
? static_cast<uint32_t>(sizeof(ShaderRecompiler::IR::PushData))
|
||||
: 0u);
|
||||
for (uint32_t i = 0; i < program.info.buffers.size(); i++) {
|
||||
const auto& buffer = program.info.buffers[i];
|
||||
const auto r = DecodeNativeDescriptor<ShaderBufferResource>(resources.buffers[i]);
|
||||
|
||||
@@ -52,7 +52,7 @@ int32_t ResolveVertexOffset(uint32_t index_offset, const ShaderVertexInputInfo&
|
||||
|
||||
EXIT_IF(!vs_input_info.stage);
|
||||
const auto& program = *vs_input_info.stage.program;
|
||||
const auto& resources = *vs_input_info.stage.resources;
|
||||
const auto& resources = vs_input_info.stage.resources;
|
||||
if (program.info.vertex_offset_sgpr >= static_cast<int32_t>(program.user_data_base)) {
|
||||
const auto index =
|
||||
static_cast<uint32_t>(program.info.vertex_offset_sgpr) - program.user_data_base;
|
||||
@@ -364,6 +364,9 @@ static void SetGraphicsDynamicParams(const CommandBuffer& buffer, vk::CommandBuf
|
||||
line_width = 1.0f;
|
||||
}
|
||||
vk_buffer.setLineWidth(line_width);
|
||||
const auto& blend = ctx.GetBlendColor();
|
||||
const std::array blend_constants {blend.red, blend.green, blend.blue, blend.alpha};
|
||||
vk_buffer.setBlendConstants(blend_constants.data());
|
||||
|
||||
const auto& mode = ctx.GetModeControl();
|
||||
const auto& poly_offset = ctx.GetPolyOffset();
|
||||
@@ -495,8 +498,7 @@ struct DrawRenderState {
|
||||
RenderState rendering;
|
||||
ShaderVertexInputInfo vs_input_info;
|
||||
ShaderPixelInputInfo ps_input_info;
|
||||
ShaderProgram vertex_program;
|
||||
ShaderProgram pixel_program;
|
||||
PipelineCache::GraphicsPrograms programs;
|
||||
};
|
||||
|
||||
struct DrawCallInfo {
|
||||
@@ -1008,30 +1010,20 @@ static void RefreshShaders(CommandBuffer& buffer, const DrawCallInfo& draw, bool
|
||||
const auto& pixel_shader_info = sh_ctx.GetPs();
|
||||
const auto& shader_regs = ctx.GetShaderRegisters();
|
||||
|
||||
state.vertex_program = {};
|
||||
state.pixel_program = {};
|
||||
state.ps_input_info = {};
|
||||
state.programs = {};
|
||||
state.ps_input_info = {};
|
||||
std::array<Prospero::ColorComponentMapping, RENDER_COLOR_ATTACHMENTS_MAX>
|
||||
target_export_mapping {};
|
||||
for (uint32_t i = 0; i < state.color_count; i++) {
|
||||
target_export_mapping[state.color_info[i].target_slot] = state.color_info[i].export_mapping;
|
||||
}
|
||||
if (log_phases) {
|
||||
LogDrawPhase(draw.name, "GetVertexProgram");
|
||||
}
|
||||
auto& pipeline_cache = buffer.GetContext().GetPipelineCache();
|
||||
state.vertex_program = pipeline_cache.GetVertexProgram(vertex_shader_info, shader_regs, ctx,
|
||||
state.vs_input_info);
|
||||
|
||||
if (!state.ps_active) {
|
||||
return;
|
||||
}
|
||||
if (log_phases) {
|
||||
LogDrawPhase(draw.name, "GetPixelProgram");
|
||||
LogDrawPhase(draw.name, "GetGraphicsPrograms");
|
||||
}
|
||||
state.pixel_program =
|
||||
pipeline_cache.GetPixelProgram(pixel_shader_info, shader_regs, state.vs_input_info,
|
||||
target_export_mapping, state.ps_input_info);
|
||||
state.programs = pipeline_cache.GetGraphicsPrograms(
|
||||
vertex_shader_info, pixel_shader_info, shader_regs, ctx, target_export_mapping,
|
||||
state.ps_active, state.vs_input_info, state.ps_input_info);
|
||||
}
|
||||
|
||||
static PreparedVertexBuffers PrepareVertexBuffers(uint64_t submit_id, CommandBuffer& buffer,
|
||||
@@ -1185,7 +1177,7 @@ void RenderExecutor::ExecutePreparedDraw(uint64_t submit_id, CommandBuffer& buff
|
||||
auto& pipeline = m_context.GetPipelineCache().CreateGraphicsPipeline(
|
||||
std::span {state.color_info, state.color_count}, state.depth_info, state.vs_input_info, buffer,
|
||||
state.ps_active ? &state.ps_input_info : nullptr, topology, primitive_restart_enable,
|
||||
state.vertex_program, state.pixel_program);
|
||||
state.programs.vertex, state.programs.pixel);
|
||||
|
||||
// Resource preparation above may synchronously finish and restart the scheduler. From this
|
||||
// point onward, every operation targets the current command buffer and cannot touch guest
|
||||
@@ -1452,7 +1444,8 @@ void RenderExecutor::DrawAuto(uint64_t submit_id, CommandBuffer& buffer, uint32_
|
||||
|
||||
const bool rect_list = topology == vk::PrimitiveTopology::ePatchList;
|
||||
if (rect_list && state.vs_input_info.buffers_num == 0 &&
|
||||
state.vs_input_info.param_export_mask == 0 && state.ps_input_info.input_num != 0) {
|
||||
state.vs_input_info.stage.program->param_export_mask == 0 &&
|
||||
state.ps_input_info.input_num != 0) {
|
||||
if (graphics_debug_dump_enabled()) {
|
||||
LOGF("DrawIndexAuto: skipping rect-list draw with no VS param exports and PS inputs: "
|
||||
"ps_inputs=%u ps=0x%016" PRIx64 " es=0x%016" PRIx64 " gs=0x%016" PRIx64 "\n",
|
||||
|
||||
@@ -34,9 +34,9 @@ const char* GetDumpLabel(const CompileOptions& options) {
|
||||
return options.dump_label != nullptr ? options.dump_label : "ShaderRecompiler";
|
||||
}
|
||||
|
||||
std::string MakeIrDump(const CFG::Graph& cfg, const IR::Program& ir) {
|
||||
std::string MakeIrDump(std::string_view cfg, const IR::Program& ir) {
|
||||
std::string dump = "CFG:\n";
|
||||
dump += CFG::GraphToString(cfg);
|
||||
dump += cfg;
|
||||
dump += "\nIR:\n";
|
||||
dump += fmt::format("mode={} scratch_dwords={}\n",
|
||||
ir.dispatcher_fallback ? "dispatcher" : "structured", ir.scratch_dwords);
|
||||
@@ -464,7 +464,7 @@ EmbeddedFetchData DetectEmbeddedVertexFetch(const Decoder::Program& decoded
|
||||
|
||||
} // namespace
|
||||
|
||||
CompileResult Recompile(std::span<const uint32_t> code, const CompileOptions& options) {
|
||||
TranslateResult TranslateProgram(std::span<const uint32_t> code, const CompileOptions& options) {
|
||||
if (code.empty()) {
|
||||
EXIT("shader recompiler input is empty\n");
|
||||
}
|
||||
@@ -618,30 +618,42 @@ CompileResult Recompile(std::span<const uint32_t> code, const CompileOptions& op
|
||||
ir.info.vertex_offset_sgpr = embedded_fetch.vertex_offset_sgpr;
|
||||
}
|
||||
|
||||
const IR::SrtRuntime runtime {
|
||||
.user_data = options.user_data,
|
||||
.shader_base = reinterpret_cast<uint64_t>(code.data()),
|
||||
.read_memory = options.read_memory,
|
||||
.userdata = options.read_memory_data,
|
||||
.read_specialization_memory = options.read_specialization_memory,
|
||||
};
|
||||
IR::ResourceSnapshot resources;
|
||||
if (!IR::MaterializeResources(ir, runtime, resources)) {
|
||||
EXIT("shader resource materialization failed: stage=%s hash=0x%016" PRIx64 "\n",
|
||||
StageName(options.stage), options.shader_hash);
|
||||
TranslateResult result;
|
||||
result.program = std::move(ir);
|
||||
if (options.dump_ir) {
|
||||
result.decoded_dump = std::move(decoded_dump);
|
||||
result.cfg_dump = CFG::GraphToString(cfg);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
CompileResult CompileProgram(TranslateResult translated, const CompileOptions& options,
|
||||
const IR::ResourceSpecialization& specialization,
|
||||
uint32_t push_data_start_dword) {
|
||||
const auto emit_begin = std::chrono::steady_clock::now();
|
||||
auto& ir = translated.program;
|
||||
IR::ApplyResourceSpecialization(ir, specialization);
|
||||
|
||||
const ShaderVertexInputInfo* vertex = nullptr;
|
||||
const ShaderPixelInputInfo* pixel = nullptr;
|
||||
const ShaderComputeInputInfo* compute = nullptr;
|
||||
switch (options.stage) {
|
||||
case ShaderType::Vertex: vertex = options.input_info.vertex; break;
|
||||
case ShaderType::Pixel: pixel = options.input_info.pixel; break;
|
||||
case ShaderType::Compute: compute = options.input_info.compute; break;
|
||||
default: EXIT("invalid shader stage\n");
|
||||
}
|
||||
IR::SpecializeResources(ir, resources);
|
||||
|
||||
IR::ShaderInfoOptions info_options;
|
||||
info_options.vertex = vertex;
|
||||
info_options.pixel = pixel;
|
||||
info_options.compute = compute;
|
||||
IR::CollectShaderInfo(ir, info_options);
|
||||
IR::AllocateBindings(ir, options.push_constant_offset);
|
||||
IR::AllocateBindings(ir, push_data_start_dword);
|
||||
Spirv::AnalyzeProgramRequirements(ir);
|
||||
std::string ir_dump;
|
||||
if (options.dump_ir) {
|
||||
ir_dump = MakeIrDump(cfg, ir);
|
||||
ir_dump = MakeIrDump(translated.cfg_dump, ir);
|
||||
if (options.early_dump) {
|
||||
LOGF("%s native IR and bindings (early):\n%s", GetDumpLabel(options), ir_dump.c_str());
|
||||
}
|
||||
@@ -649,17 +661,19 @@ CompileResult Recompile(std::span<const uint32_t> code, const CompileOptions& op
|
||||
|
||||
LOGF("%s phase begin: stage=%s hash=0x%016" PRIx64 " SPIR-V EmitProgram\n",
|
||||
GetDumpLabel(options), StageName(options.stage), options.shader_hash);
|
||||
auto spirv = Spirv::EmitProgram(ir, resources, options.input_info);
|
||||
auto spirv = Spirv::EmitProgram(ir, options.input_info);
|
||||
LOGF("%s phase end: stage=%s hash=0x%016" PRIx64 " SPIR-V EmitProgram words=%" PRIu64
|
||||
" elapsed_ms=%" PRIu64 "\n",
|
||||
GetDumpLabel(options), StageName(options.stage), options.shader_hash,
|
||||
static_cast<uint64_t>(spirv.size()), phase_ms());
|
||||
static_cast<uint64_t>(spirv.size()),
|
||||
static_cast<uint64_t>(std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||
std::chrono::steady_clock::now() - emit_begin)
|
||||
.count()));
|
||||
CompileResult result;
|
||||
result.spirv = std::move(spirv);
|
||||
result.program = std::move(ir);
|
||||
result.resources = std::move(resources);
|
||||
result.spirv = std::move(spirv);
|
||||
result.program = std::move(ir);
|
||||
if (options.dump_ir) {
|
||||
result.decoded_dump = std::move(decoded_dump);
|
||||
result.decoded_dump = std::move(translated.decoded_dump);
|
||||
result.ir_dump = std::move(ir_dump);
|
||||
}
|
||||
return result;
|
||||
|
||||
@@ -17,27 +17,32 @@ struct CompileOptions {
|
||||
uint32_t user_data_base = 0;
|
||||
uint32_t scratch_dwords = 0;
|
||||
uint64_t shader_hash = 0;
|
||||
uint32_t push_constant_offset = 0;
|
||||
bool dump_ir = true;
|
||||
bool early_dump = false;
|
||||
const char* dump_label = nullptr;
|
||||
std::span<const uint32_t> user_data;
|
||||
IR::SrtMemoryReader read_memory = nullptr;
|
||||
IR::SrtMemoryReader read_specialization_memory = nullptr;
|
||||
void* read_memory_data = nullptr;
|
||||
ShaderStageInputInfo input_info;
|
||||
};
|
||||
|
||||
struct CompileResult {
|
||||
std::vector<uint32_t> spirv;
|
||||
std::string decoded_dump;
|
||||
std::string ir_dump;
|
||||
IR::Program program;
|
||||
IR::ResourceSnapshot resources;
|
||||
struct TranslateResult {
|
||||
IR::Program program;
|
||||
std::string decoded_dump;
|
||||
std::string cfg_dump;
|
||||
};
|
||||
|
||||
[[nodiscard]] CompileResult Recompile(std::span<const uint32_t> code,
|
||||
const CompileOptions& options);
|
||||
struct CompileResult {
|
||||
std::vector<uint32_t> spirv;
|
||||
std::string decoded_dump;
|
||||
std::string ir_dump;
|
||||
IR::Program program;
|
||||
};
|
||||
|
||||
[[nodiscard]] TranslateResult TranslateProgram(std::span<const uint32_t> code,
|
||||
const CompileOptions& options);
|
||||
[[nodiscard]] CompileResult CompileProgram(TranslateResult translated,
|
||||
const CompileOptions& options,
|
||||
const IR::ResourceSpecialization& specialization,
|
||||
uint32_t push_data_start_dword = 0);
|
||||
|
||||
} // namespace Libs::Graphics::ShaderRecompiler
|
||||
|
||||
|
||||
@@ -81,13 +81,13 @@ void ValidateNativeProgram(const IR::Program& program) {
|
||||
const bool uses_flattened_runtime =
|
||||
!program.srt_reads.empty() ||
|
||||
std::ranges::any_of(program.info.images, [](const IR::ImageResource& image) {
|
||||
return image.indirect_mapping_capacity != 0u;
|
||||
return image.indirect_search_iterations != 0u;
|
||||
});
|
||||
if (uses_flattened_runtime) {
|
||||
Expect(Kind::FlattenedSrt);
|
||||
}
|
||||
if (program.bindings.ShaderDataDwords() != 0 && program.bindings.push_constant_size == 0) {
|
||||
Expect(Kind::UserData);
|
||||
if (program.bindings.ShaderDataDwords() != 0 && !program.bindings.UsesPushData()) {
|
||||
Expect(Kind::ShaderData);
|
||||
}
|
||||
|
||||
std::array<bool, KindCount> seen {};
|
||||
@@ -104,21 +104,19 @@ void ValidateNativeProgram(const IR::Program& program) {
|
||||
Fail(program, "native shader plan is missing a required descriptor group");
|
||||
}
|
||||
}
|
||||
const auto has_user_storage = present[static_cast<size_t>(Kind::UserData)];
|
||||
if (program.bindings.push_constant_offset % sizeof(uint32_t) != 0 ||
|
||||
program.bindings.push_constant_offset + program.bindings.push_constant_size >
|
||||
IR::NativePushConstantSize ||
|
||||
const auto has_shader_data_storage = present[static_cast<size_t>(Kind::ShaderData)];
|
||||
const auto shader_data_dwords = program.bindings.ShaderDataDwords();
|
||||
if ((program.bindings.UsesPushData() &&
|
||||
!IR::PushData::CanFit(program.bindings.push_data_start_dword, shader_data_dwords)) ||
|
||||
program.bindings.memory_offset_dword != program.bindings.user_data_registers.size() ||
|
||||
program.bindings.memory_offset_count != program.info.buffers.size() ||
|
||||
(!has_user_storage &&
|
||||
program.bindings.push_constant_size != program.bindings.ShaderDataDwords() * 4u) ||
|
||||
(has_user_storage && program.bindings.push_constant_size != 0) ||
|
||||
has_shader_data_storage != (shader_data_dwords != 0 && !program.bindings.UsesPushData()) ||
|
||||
!std::is_sorted(program.bindings.user_data_registers.begin(),
|
||||
program.bindings.user_data_registers.end()) ||
|
||||
std::adjacent_find(program.bindings.user_data_registers.begin(),
|
||||
program.bindings.user_data_registers.end()) !=
|
||||
program.bindings.user_data_registers.end()) {
|
||||
Fail(program, "native user-data layout is inconsistent");
|
||||
Fail(program, "native shader-data layout is inconsistent");
|
||||
}
|
||||
|
||||
const auto planning_only_handle = [&](const IR::Inst& handle) {
|
||||
@@ -302,7 +300,6 @@ void AnalyzeProgramRequirements(IR::Program& program) {
|
||||
}
|
||||
|
||||
std::vector<uint32_t> EmitProgram(const IR::Program& program,
|
||||
const IR::ResourceSnapshot& resources,
|
||||
ShaderStageInputInfo input_info) {
|
||||
using namespace Emitter;
|
||||
|
||||
@@ -315,15 +312,9 @@ std::vector<uint32_t> EmitProgram(const IR::Program& program,
|
||||
!program.spirv_requirements.has_value()) {
|
||||
Fail(program, "SPIR-V emitter requires a fully planned native shader program");
|
||||
}
|
||||
if (!IR::ValidateResourceSnapshot(program, resources)) {
|
||||
Fail(program, "resource snapshot is inconsistent with the native shader plan");
|
||||
}
|
||||
if (!IR::ValidateResourceSpecialization(program, resources)) {
|
||||
Fail(program, "resource specialization is inconsistent with the native shader plan");
|
||||
}
|
||||
ValidateNativeProgram(program);
|
||||
IR::ValidateProgram(program, true);
|
||||
EmitterState state(program, resources, input_info);
|
||||
EmitterState state(program, input_info);
|
||||
state.stage = program.stage;
|
||||
state.wave_size = program.wave_size;
|
||||
state.inputs.reserve(program.info.inputs.size());
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
#include "common/common.h"
|
||||
#include "common/stringUtils.h"
|
||||
#include "graphics/shader/recompiler/ir/passes/ResourceMaterialization.h"
|
||||
#include "graphics/shader/recompiler/ir/ShaderIR.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
@@ -12,7 +12,6 @@ namespace Libs::Graphics::ShaderRecompiler::Spirv {
|
||||
void AnalyzeProgramRequirements(IR::Program& program);
|
||||
|
||||
std::vector<uint32_t> EmitProgram(const IR::Program& program,
|
||||
const IR::ResourceSnapshot& resources,
|
||||
ShaderStageInputInfo input_info);
|
||||
|
||||
} // namespace Libs::Graphics::ShaderRecompiler::Spirv
|
||||
|
||||
@@ -806,7 +806,7 @@ bool EmitValueImage(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
return true;
|
||||
}
|
||||
const auto key = ctx.Def(handle->Arg(source->indirect_image->key_arg));
|
||||
if (state.flattened_srt_variable == 0 || image.indirect_mapping_capacity == 0u ||
|
||||
if (state.flattened_srt_variable == 0 || image.indirect_search_iterations == 0u ||
|
||||
image.indirect_resources.size() < 2u) {
|
||||
ctx.Fail(inst, "has no indirect image runtime mapping");
|
||||
return true;
|
||||
@@ -824,7 +824,7 @@ bool EmitValueImage(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
auto low = ConstantU32(state, 0u);
|
||||
auto high = LoadMapping(mapping);
|
||||
auto selected = ConstantU32(state, 0u);
|
||||
for (uint32_t iteration = 0; iteration < std::bit_width(image.indirect_mapping_capacity);
|
||||
for (uint32_t iteration = 0; iteration < image.indirect_search_iterations;
|
||||
iteration++) {
|
||||
const auto active = Binary(state, OpULessThan, TypeBool(state), low, high);
|
||||
const auto mid =
|
||||
|
||||
@@ -341,14 +341,12 @@ constexpr std::array<ImageDimensionInfo, 7> ImageDimensions {{
|
||||
const ImageDimensionInfo& ImageDimensionInfoFor(ImageDimension dimension);
|
||||
|
||||
struct EmitterState {
|
||||
EmitterState(const IR::Program& program_, const IR::ResourceSnapshot& resources_,
|
||||
ShaderStageInputInfo input_info_)
|
||||
: program(program_), resources(resources_), input_info(input_info_),
|
||||
EmitterState(const IR::Program& program_, ShaderStageInputInfo input_info_)
|
||||
: program(program_), input_info(input_info_),
|
||||
requirements(*program_.spirv_requirements) {}
|
||||
|
||||
Builder builder;
|
||||
const IR::Program& program;
|
||||
const IR::ResourceSnapshot& resources;
|
||||
ShaderStageInputInfo input_info;
|
||||
const IR::SpirvRequirements& requirements;
|
||||
ShaderType stage = ShaderType::Unknown;
|
||||
@@ -361,7 +359,7 @@ struct EmitterState {
|
||||
uint32_t gds_variable = 0;
|
||||
uint32_t gds_length = 0;
|
||||
uint32_t push_constant_variable = 0;
|
||||
uint32_t vsharp_storage_variable = 0;
|
||||
uint32_t shader_data_storage_variable = 0;
|
||||
uint32_t flattened_srt_variable = 0;
|
||||
uint32_t lds_variable = 0;
|
||||
uint32_t scratch_variable = 0;
|
||||
|
||||
@@ -4,6 +4,7 @@ namespace Libs::Graphics::ShaderRecompiler::Spirv::Emitter {
|
||||
|
||||
uint32_t EmitShaderDataDwordLoad(EmitterState& state, uint32_t dword_index) {
|
||||
if (state.push_constant_variable != 0) {
|
||||
dword_index += state.program.bindings.push_data_start_dword;
|
||||
const auto pointer = state.builder.AllocateId();
|
||||
const auto value = state.builder.AllocateId();
|
||||
state.builder.AddFunction({OpAccessChain, TypePushConstantElementPointer(state), pointer,
|
||||
@@ -12,11 +13,11 @@ uint32_t EmitShaderDataDwordLoad(EmitterState& state, uint32_t dword_index) {
|
||||
state.builder.AddFunction({OpLoad, TypeU32(state), value, pointer});
|
||||
return value;
|
||||
}
|
||||
if (state.vsharp_storage_variable != 0) {
|
||||
if (state.shader_data_storage_variable != 0) {
|
||||
const auto pointer = state.builder.AllocateId();
|
||||
const auto value = state.builder.AllocateId();
|
||||
state.builder.AddFunction({OpAccessChain, TypeStorageBufferElementPointer(state), pointer,
|
||||
state.vsharp_storage_variable, ConstantU32(state, 0),
|
||||
state.shader_data_storage_variable, ConstantU32(state, 0),
|
||||
ConstantU32(state, dword_index)});
|
||||
state.builder.AddFunction({OpLoad, TypeU32(state), value, pointer});
|
||||
return value;
|
||||
|
||||
@@ -119,8 +119,7 @@ uint32_t TypeU32ElementPointer(EmitterState& state, uint32_t storage_class) {
|
||||
namespace {
|
||||
|
||||
uint32_t PushConstantArrayType(EmitterState& state) {
|
||||
const auto count =
|
||||
ConstantU32(state, state.program.bindings.push_constant_size / sizeof(uint32_t));
|
||||
const auto count = ConstantU32(state, IR::PushData::DwordCount);
|
||||
return state.builder.DecoratedType(OpTypeArray, {TypeU32(state), count},
|
||||
{{OpDecorate, {DecorationArrayStride, sizeof(uint32_t)}}});
|
||||
}
|
||||
@@ -128,7 +127,7 @@ uint32_t PushConstantArrayType(EmitterState& state) {
|
||||
uint32_t PushConstantBlockType(EmitterState& state) {
|
||||
return state.builder.DecoratedType(
|
||||
OpTypeStruct, {PushConstantArrayType(state)},
|
||||
{{OpMemberDecorate, {0, DecorationOffset, state.program.bindings.push_constant_offset}},
|
||||
{{OpMemberDecorate, {0, DecorationOffset, 0}},
|
||||
{OpDecorate, {DecorationBlock}}});
|
||||
}
|
||||
|
||||
@@ -173,14 +172,14 @@ void DefineDescriptorVariables(EmitterState& state) {
|
||||
state.fault_buffer_variable = state.builder.DefineGlobalVariable(
|
||||
TypeStorageBufferPointer(state), StorageClassStorageBuffer);
|
||||
}
|
||||
if (state.program.bindings.push_constant_size != 0) {
|
||||
if (state.program.bindings.UsesPushData()) {
|
||||
const auto pointer_type =
|
||||
TypePointer(state, StorageClassPushConstant, PushConstantBlockType(state));
|
||||
state.push_constant_variable =
|
||||
state.builder.DefineGlobalVariable(pointer_type, StorageClassPushConstant);
|
||||
}
|
||||
if (DescriptorBinding(state, IR::DescriptorBindingKind::UserData) != nullptr) {
|
||||
state.vsharp_storage_variable = state.builder.DefineGlobalVariable(
|
||||
if (DescriptorBinding(state, IR::DescriptorBindingKind::ShaderData) != nullptr) {
|
||||
state.shader_data_storage_variable = state.builder.DefineGlobalVariable(
|
||||
TypeStorageBufferPointer(state), StorageClassStorageBuffer);
|
||||
}
|
||||
if (DescriptorBinding(state, IR::DescriptorBindingKind::FlattenedSrt) != nullptr) {
|
||||
@@ -604,9 +603,9 @@ void AddVsharpAnnotationsAndNames(EmitterState& state) {
|
||||
state.builder.AddName(PushConstantBlockType(state), "BufferResource");
|
||||
state.builder.AddName(state.push_constant_variable, "vsharp");
|
||||
}
|
||||
if (state.vsharp_storage_variable != 0) {
|
||||
DecorateDescriptor(state, state.vsharp_storage_variable, "user_data",
|
||||
IR::DescriptorBindingKind::UserData);
|
||||
if (state.shader_data_storage_variable != 0) {
|
||||
DecorateDescriptor(state, state.shader_data_storage_variable, "shader_data",
|
||||
IR::DescriptorBindingKind::ShaderData);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -54,7 +54,7 @@ bool IsRuntimeRead(ValueOpcode opcode) {
|
||||
AddressOpcodeInfoOf(opcode).access == AddressAccess::Read;
|
||||
}
|
||||
|
||||
bool EquivalentValue(const Program& program, Value left, Value right,
|
||||
bool EquivalentValue(const ResourcePlan& program, Value left, Value right,
|
||||
std::vector<std::pair<const Inst*, const Inst*>>& visited) {
|
||||
left = left.Resolve();
|
||||
right = right.Resolve();
|
||||
@@ -97,6 +97,20 @@ bool EquivalentValue(const Program& program, Value left, Value right,
|
||||
|
||||
} // namespace
|
||||
|
||||
ResourcePlan::~ResourcePlan() {
|
||||
for (auto& inst: value_storage) {
|
||||
inst.Invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
ResourcePlan& ResourcePlan::operator=(ResourcePlan&& other) noexcept {
|
||||
if (this != &other) {
|
||||
this->~ResourcePlan();
|
||||
new (this) ResourcePlan(std::move(other));
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
Program::~Program() {
|
||||
// Values may cross block boundaries. Detach all arguments before any block starts destroying
|
||||
// its instruction storage so reverse-use links always point to live definitions.
|
||||
@@ -115,12 +129,31 @@ Program& Program::operator=(Program&& other) noexcept {
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool EquivalentValue(const Program& program, Value left, Value right) {
|
||||
CompiledShaderInfo Program::TakeCompiledInfo() && {
|
||||
CompiledShaderInfo result {
|
||||
.stage = stage,
|
||||
.shader_hash = shader_hash,
|
||||
.wave_size = wave_size,
|
||||
.user_data_base = user_data_base,
|
||||
.user_data_count = user_data_count,
|
||||
.scratch_dwords = scratch_dwords,
|
||||
.info = std::move(info),
|
||||
.bindings = std::move(bindings),
|
||||
};
|
||||
for (const auto& output: result.info.outputs) {
|
||||
if (output.kind == StageOutputKind::Parameter && output.index < 32) {
|
||||
result.param_export_mask |= 1u << output.index;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
bool EquivalentValue(const ResourcePlan& program, Value left, Value right) {
|
||||
std::vector<std::pair<const Inst*, const Inst*>> visited;
|
||||
return EquivalentValue(program, left, right, visited);
|
||||
}
|
||||
|
||||
Value ResolveInvariantPhi(const Program& program, Value value) {
|
||||
Value ResolveInvariantPhi(const ResourcePlan& program, Value value) {
|
||||
value = value.Resolve();
|
||||
const auto* root = value.TryInstruction();
|
||||
if (root == nullptr || root->GetOpcode() != ValueOpcode::Phi) {
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
#ifndef EMULATOR_INCLUDE_EMULATOR_GRAPHICS_SHADER_RECOMPILER_RESOURCESNAPSHOT_H_
|
||||
#define EMULATOR_INCLUDE_EMULATOR_GRAPHICS_SHADER_RECOMPILER_RESOURCESNAPSHOT_H_
|
||||
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
namespace Libs::Graphics::ShaderRecompiler::IR {
|
||||
|
||||
struct DescriptorValue {
|
||||
std::array<uint32_t, 8> dwords = {};
|
||||
uint32_t dword_count = 0;
|
||||
|
||||
bool operator==(const DescriptorValue& other) const {
|
||||
return dword_count == other.dword_count && dwords == other.dwords;
|
||||
}
|
||||
};
|
||||
|
||||
struct ResourceSnapshot {
|
||||
std::vector<DescriptorValue> buffers;
|
||||
std::vector<DescriptorValue> images;
|
||||
std::vector<DescriptorValue> samplers;
|
||||
std::vector<uint32_t> flattened_srt;
|
||||
std::vector<uint32_t> user_data;
|
||||
};
|
||||
|
||||
} // namespace Libs::Graphics::ShaderRecompiler::IR
|
||||
|
||||
#endif // EMULATOR_INCLUDE_EMULATOR_GRAPHICS_SHADER_RECOMPILER_RESOURCESNAPSHOT_H_
|
||||
@@ -8,11 +8,13 @@
|
||||
#include "graphics/shader/recompiler/frontend/cfg/ShaderCFG.h"
|
||||
#include "graphics/shader/recompiler/frontend/decode/ShaderDecoder.h"
|
||||
#include "graphics/shader/recompiler/ir/Block.h"
|
||||
#include "graphics/shader/recompiler/ir/ResourceSnapshot.h"
|
||||
#include "graphics/shader/recompiler/ir/opcodes/ValueOpcodes.h"
|
||||
#include "graphics/shader/shader.h"
|
||||
|
||||
#include <array>
|
||||
#include <bit>
|
||||
#include <list>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string_view>
|
||||
@@ -87,15 +89,6 @@ struct ExportInfo {
|
||||
bool operator==(const ExportInfo& other) const = default;
|
||||
};
|
||||
|
||||
struct DescriptorValue {
|
||||
std::array<uint32_t, 8> dwords = {};
|
||||
uint32_t dword_count = 0;
|
||||
|
||||
bool operator==(const DescriptorValue& other) const {
|
||||
return dword_count == other.dword_count && dwords == other.dwords;
|
||||
}
|
||||
};
|
||||
|
||||
struct BufferResource {
|
||||
static constexpr uint32_t NoImageAlias = UINT32_MAX;
|
||||
|
||||
@@ -139,7 +132,7 @@ struct ImageResource {
|
||||
bool r128 = false;
|
||||
uint32_t indirect_root = NoIndirectImage;
|
||||
uint32_t indirect_mapping_offset = 0;
|
||||
uint32_t indirect_mapping_capacity = 0;
|
||||
uint32_t indirect_search_iterations = 0;
|
||||
std::vector<uint32_t> indirect_resources;
|
||||
|
||||
bool operator==(const ImageResource& other) const = default;
|
||||
@@ -149,6 +142,7 @@ struct SamplerResource {
|
||||
uint32_t source = 0;
|
||||
uint32_t first_use_pc = 0;
|
||||
bool force_point_filtering = false;
|
||||
bool depth_compare = false;
|
||||
|
||||
bool operator==(const SamplerResource& other) const = default;
|
||||
};
|
||||
@@ -270,14 +264,28 @@ enum class DescriptorBindingKind : uint32_t {
|
||||
BdaPagetable,
|
||||
FaultBuffer,
|
||||
FlattenedSrt,
|
||||
UserData,
|
||||
ShaderData,
|
||||
Count,
|
||||
};
|
||||
|
||||
static_assert(static_cast<uint32_t>(DescriptorBindingKind::Samplers) == 37u);
|
||||
static_assert(static_cast<uint32_t>(DescriptorBindingKind::Count) == 43u);
|
||||
|
||||
constexpr uint32_t NativePushConstantSize = 128;
|
||||
struct PushData {
|
||||
static constexpr uint32_t DwordCount = 32;
|
||||
static constexpr uint32_t NoStart = UINT32_MAX;
|
||||
std::array<uint32_t, DwordCount> dwords {};
|
||||
|
||||
[[nodiscard]] static constexpr bool CanFit(uint32_t start, uint32_t size) {
|
||||
return size != 0 && start <= DwordCount && size <= DwordCount - start;
|
||||
}
|
||||
[[nodiscard]] static constexpr uint32_t StartFor(uint32_t cursor, uint32_t size) {
|
||||
return CanFit(cursor, size) ? cursor : NoStart;
|
||||
}
|
||||
};
|
||||
|
||||
static_assert(sizeof(PushData) == 128);
|
||||
constexpr uint32_t NativePushConstantSize = sizeof(PushData);
|
||||
|
||||
[[nodiscard]] constexpr uint32_t NativeBinding(ShaderType stage, DescriptorBindingKind kind) {
|
||||
return static_cast<uint32_t>(kind) +
|
||||
@@ -375,8 +383,7 @@ struct DescriptorBinding {
|
||||
};
|
||||
|
||||
struct BindingLayout {
|
||||
uint32_t push_constant_offset = 0;
|
||||
uint32_t push_constant_size = 0;
|
||||
uint32_t push_data_start_dword = PushData::NoStart;
|
||||
uint32_t memory_offset_dword = 0;
|
||||
uint32_t memory_offset_count = 0;
|
||||
std::vector<uint32_t> user_data_registers;
|
||||
@@ -385,6 +392,14 @@ struct BindingLayout {
|
||||
[[nodiscard]] uint32_t ShaderDataDwords() const {
|
||||
return memory_offset_dword + (memory_offset_count + 3u) / 4u;
|
||||
}
|
||||
[[nodiscard]] bool UsesPushData() const {
|
||||
return push_data_start_dword != PushData::NoStart;
|
||||
}
|
||||
void AdvancePushData(uint32_t& cursor) const {
|
||||
if (UsesPushData()) {
|
||||
cursor = push_data_start_dword + ShaderDataDwords();
|
||||
}
|
||||
}
|
||||
|
||||
bool operator==(const BindingLayout& other) const = default;
|
||||
};
|
||||
@@ -454,7 +469,47 @@ struct SrtRead {
|
||||
bool operator==(const SrtRead& other) const = default;
|
||||
};
|
||||
|
||||
struct Program {
|
||||
// Stable shader metadata consumed by the renderer after native IR has been discarded.
|
||||
struct CompiledShaderInfo {
|
||||
ShaderType stage = ShaderType::Unknown;
|
||||
uint64_t shader_hash = 0;
|
||||
uint32_t wave_size = 64;
|
||||
uint32_t user_data_base = 0;
|
||||
uint32_t user_data_count = 64;
|
||||
uint32_t scratch_dwords = 0;
|
||||
uint32_t param_export_mask = 0;
|
||||
ShaderInfo info;
|
||||
BindingLayout bindings;
|
||||
};
|
||||
|
||||
// Immutable runtime resource analysis retained by the shader cache. It owns only the native
|
||||
// value graph reachable from descriptors/SRT reads, rather than the translated shader CFG.
|
||||
struct ResourcePlan {
|
||||
ResourcePlan() = default;
|
||||
~ResourcePlan();
|
||||
|
||||
ResourcePlan(const ResourcePlan&) = delete;
|
||||
ResourcePlan& operator=(const ResourcePlan&) = delete;
|
||||
ResourcePlan(ResourcePlan&&) noexcept = default;
|
||||
ResourcePlan& operator=(ResourcePlan&& other) noexcept;
|
||||
|
||||
ShaderType stage = ShaderType::Unknown;
|
||||
uint64_t shader_hash = 0;
|
||||
uint32_t user_data_base = 0;
|
||||
uint32_t user_data_count = 64;
|
||||
std::list<Inst> value_storage;
|
||||
std::vector<MemoryInfo> memory_info;
|
||||
std::vector<DescriptorSource> descriptor_sources;
|
||||
std::vector<uint32_t> materialization_sources;
|
||||
std::vector<SrtRead> srt_reads;
|
||||
std::vector<uint8_t> clean_flat_slots;
|
||||
bool requires_specialization_memory = false;
|
||||
bool srt_plan_complete = false;
|
||||
bool resource_tracking_complete = false;
|
||||
ShaderInfo info;
|
||||
};
|
||||
|
||||
struct Program: ResourcePlan {
|
||||
Program() = default;
|
||||
~Program();
|
||||
|
||||
@@ -462,31 +517,22 @@ struct Program {
|
||||
Program& operator=(const Program&) = delete;
|
||||
Program(Program&&) noexcept = default;
|
||||
Program& operator=(Program&& other) noexcept;
|
||||
CompiledShaderInfo TakeCompiledInfo() &&;
|
||||
|
||||
ShaderType stage = ShaderType::Unknown;
|
||||
uint64_t shader_hash = 0;
|
||||
uint32_t wave_size = 64;
|
||||
uint32_t user_data_base = 0;
|
||||
uint32_t user_data_count = 64;
|
||||
uint32_t scratch_dwords = 0;
|
||||
std::vector<std::unique_ptr<Block>> block_storage;
|
||||
BlockList blocks;
|
||||
uint32_t wave_size = 64;
|
||||
uint32_t scratch_dwords = 0;
|
||||
bool dispatcher_fallback = false;
|
||||
CFG::FailureKind cfg_failure_kind = CFG::FailureKind::None;
|
||||
std::string fallback_reason;
|
||||
std::vector<std::unique_ptr<Block>> block_storage;
|
||||
BlockList blocks;
|
||||
std::vector<BlockInfo> block_info;
|
||||
std::vector<BlockInfo> block_info;
|
||||
// Decoded MIMG/VMEM metadata carries details such as RDNA2 NSA address registers and
|
||||
// storage-image swizzles. Typed memory instructions carry a dense index into these shader-local
|
||||
// tables until those fields are consumed by emission.
|
||||
std::vector<MemoryInfo> memory_info;
|
||||
std::vector<ExportInfo> export_info;
|
||||
std::vector<DescriptorSource> descriptor_sources;
|
||||
std::vector<SrtRead> srt_reads;
|
||||
std::vector<Value> dynamic_reads;
|
||||
bool srt_plan_complete = false;
|
||||
ShaderInfo info;
|
||||
bool resource_tracking_complete = false;
|
||||
bool shader_info_complete = false;
|
||||
bool shader_info_complete = false;
|
||||
BindingLayout bindings;
|
||||
bool binding_layout_complete = false;
|
||||
|
||||
@@ -497,8 +543,8 @@ std::string ProgramToString(const Program& program);
|
||||
|
||||
void ValidateProgram(const Program& program, bool require_ssa);
|
||||
void ResolveControlFlowIdentities(Program& program);
|
||||
bool EquivalentValue(const Program& program, Value left, Value right);
|
||||
Value ResolveInvariantPhi(const Program& program, Value value);
|
||||
bool EquivalentValue(const ResourcePlan& program, Value left, Value right);
|
||||
Value ResolveInvariantPhi(const ResourcePlan& program, Value value);
|
||||
|
||||
} // namespace Libs::Graphics::ShaderRecompiler::IR
|
||||
|
||||
|
||||
@@ -69,24 +69,18 @@ bool UsesGds(const Program& program) {
|
||||
|
||||
} // namespace
|
||||
|
||||
void AllocateBindings(Program& program, uint32_t push_constant_offset) {
|
||||
void AllocateBindings(Program& program, uint32_t push_data_start_dword) {
|
||||
if (!program.shader_info_complete || program.binding_layout_complete) {
|
||||
EXIT("shader binding layout failed: %s", !program.shader_info_complete
|
||||
? "shader info is not ready"
|
||||
: "binding layout already allocated");
|
||||
}
|
||||
if (push_constant_offset > NativePushConstantSize ||
|
||||
push_constant_offset % sizeof(uint32_t) != 0) {
|
||||
EXIT("shader binding layout failed: push-constant offset %u exceeds the Vulkan minimum "
|
||||
"guarantee or is unaligned",
|
||||
push_constant_offset);
|
||||
}
|
||||
|
||||
BindingLayout next;
|
||||
next.push_constant_offset = push_constant_offset;
|
||||
next.user_data_registers = CollectUserData(program);
|
||||
next.memory_offset_dword = static_cast<uint32_t>(next.user_data_registers.size());
|
||||
next.memory_offset_count = static_cast<uint32_t>(program.info.buffers.size());
|
||||
next.push_data_start_dword =
|
||||
PushData::StartFor(push_data_start_dword, next.ShaderDataDwords());
|
||||
|
||||
if (!program.info.buffers.empty()) {
|
||||
std::vector<uint32_t> resources(program.info.buffers.size());
|
||||
@@ -139,17 +133,14 @@ void AllocateBindings(Program& program, uint32_t push_constant_offset) {
|
||||
const bool uses_flattened_runtime =
|
||||
!program.srt_reads.empty() ||
|
||||
std::ranges::any_of(program.info.images, [](const ImageResource& image) {
|
||||
return image.indirect_mapping_capacity != 0u;
|
||||
return image.indirect_search_iterations != 0u;
|
||||
});
|
||||
if (uses_flattened_runtime) {
|
||||
AddBinding(next, DescriptorBindingKind::FlattenedSrt);
|
||||
}
|
||||
|
||||
const auto push_dwords = (NativePushConstantSize - push_constant_offset) / sizeof(uint32_t);
|
||||
if (next.ShaderDataDwords() <= push_dwords) {
|
||||
next.push_constant_size = next.ShaderDataDwords() * sizeof(uint32_t);
|
||||
} else {
|
||||
AddBinding(next, DescriptorBindingKind::UserData);
|
||||
if (next.ShaderDataDwords() != 0 && !next.UsesPushData()) {
|
||||
AddBinding(next, DescriptorBindingKind::ShaderData);
|
||||
}
|
||||
|
||||
program.bindings = std::move(next);
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
namespace Libs::Graphics::ShaderRecompiler::IR {
|
||||
|
||||
void AllocateBindings(Program& program, uint32_t push_constant_offset);
|
||||
void AllocateBindings(Program& program, uint32_t push_data_start_dword = 0);
|
||||
|
||||
const DescriptorBinding* FindBinding(const BindingLayout& layout, DescriptorBindingKind kind);
|
||||
|
||||
|
||||
@@ -7,6 +7,8 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <bit>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <fmt/format.h>
|
||||
#include <functional>
|
||||
@@ -19,9 +21,22 @@ namespace {
|
||||
constexpr uint64_t AddressMask = 0x0000ffffffffffffull;
|
||||
constexpr uint64_t MaxIndirectImageProbes = 65536u;
|
||||
|
||||
[[noreturn]] void SpecializationFail(std::string_view message) {
|
||||
EXIT("shader resource specialization failed: %s", std::string(message).c_str());
|
||||
std::abort();
|
||||
struct IndirectImage {
|
||||
uint32_t resource = 0;
|
||||
std::vector<uint32_t> keys;
|
||||
std::vector<uint32_t> candidates;
|
||||
std::vector<DescriptorValue> descriptors;
|
||||
};
|
||||
|
||||
struct MaterializedSnapshot {
|
||||
ResourceSnapshot resources;
|
||||
std::vector<IndirectImage> indirect_images;
|
||||
};
|
||||
|
||||
bool SpecializationFail(std::string_view message) {
|
||||
std::fprintf(stderr, "shader resource specialization failed: %.*s\n",
|
||||
static_cast<int>(message.size()), message.data());
|
||||
return false;
|
||||
}
|
||||
|
||||
Decoder::ImageDimension DescriptorDimension(const DescriptorValue& descriptor,
|
||||
@@ -94,6 +109,11 @@ bool RequiresPointSampler(const ImageResource& image) {
|
||||
image.conversion_format != Prospero::BufferFormat::kInvalid;
|
||||
}
|
||||
|
||||
bool RequiresPointSampler(const ResourceSpecialization::Image& image) {
|
||||
return image.numeric_class == Prospero::TextureNumericClass::Sint ||
|
||||
image.conversion_format != Prospero::BufferFormat::kInvalid;
|
||||
}
|
||||
|
||||
bool DescriptorIsCube(const DescriptorValue& descriptor) {
|
||||
return static_cast<Prospero::ImageType>((descriptor.dwords[3] >> 28u) & 0xfu) ==
|
||||
Prospero::ImageType::kCube;
|
||||
@@ -116,14 +136,14 @@ bool DecodeBufferDescriptor(const DescriptorValue& descriptor, ShaderBufferResou
|
||||
return true;
|
||||
}
|
||||
|
||||
const DescriptorSource* Source(const Program& program, uint32_t source) {
|
||||
const DescriptorSource* Source(const ResourcePlan& program, uint32_t source) {
|
||||
if (source >= program.descriptor_sources.size()) {
|
||||
return nullptr;
|
||||
}
|
||||
return &program.descriptor_sources[source];
|
||||
}
|
||||
|
||||
void MarkCleanFlatSlots(const Program& program, const DescriptorSource* source,
|
||||
void MarkCleanFlatSlots(const ResourcePlan& program, const DescriptorSource* source,
|
||||
std::span<uint8_t> slots) {
|
||||
if (source == nullptr) {
|
||||
return;
|
||||
@@ -186,8 +206,8 @@ bool ReadScalarBufferWord(const ShaderBufferResource& descriptor, uint32_t dynam
|
||||
|
||||
bool MaterializeIndirectImage(const DescriptorSource::IndirectImage& indirect,
|
||||
const DescriptorValue& material_value,
|
||||
const DescriptorValue& heap_value, const SrtRuntime& runtime,
|
||||
ResourceSnapshot::IndirectImage& result) {
|
||||
const DescriptorValue& heap_value, bool r128,
|
||||
const SrtRuntime& runtime, IndirectImage& result) {
|
||||
ShaderBufferResource material;
|
||||
ShaderBufferResource heap;
|
||||
if (!DecodeBufferDescriptor(material_value, material) ||
|
||||
@@ -212,6 +232,8 @@ bool MaterializeIndirectImage(const DescriptorSource::IndirectImage& indirect,
|
||||
|
||||
std::vector<uint32_t> keys {0u};
|
||||
std::unordered_set<uint32_t> seen {0u};
|
||||
keys.reserve(static_cast<size_t>(probe_count) + 1u);
|
||||
seen.reserve(static_cast<size_t>(probe_count) + 1u);
|
||||
for (uint64_t offset = residue; offset <= limit && probe_count != 0u; offset += step) {
|
||||
uint32_t key = 0;
|
||||
if (!ReadScalarBufferWord(material, static_cast<uint32_t>(offset), 0u, runtime, key)) {
|
||||
@@ -225,9 +247,11 @@ bool MaterializeIndirectImage(const DescriptorSource::IndirectImage& indirect,
|
||||
}
|
||||
}
|
||||
|
||||
ResourceSnapshot::IndirectImage next;
|
||||
next.capacity = static_cast<uint32_t>(probe_count + 1u);
|
||||
next.keys = std::move(keys);
|
||||
IndirectImage next;
|
||||
next.keys = std::move(keys);
|
||||
next.candidates.reserve(next.keys.size());
|
||||
next.descriptors.reserve(
|
||||
std::min(next.keys.size(), static_cast<size_t>(ShaderInfo::MaxImages)));
|
||||
for (const auto key: next.keys) {
|
||||
DescriptorValue candidate;
|
||||
candidate.dword_count = 8u;
|
||||
@@ -238,11 +262,14 @@ bool MaterializeIndirectImage(const DescriptorSource::IndirectImage& indirect,
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (NullImageDescriptor(candidate) || !ValidImageDescriptor(candidate)) {
|
||||
if (NullImageDescriptor(candidate) || !ValidImageDescriptor(candidate, r128)) {
|
||||
candidate.dwords.fill(0);
|
||||
}
|
||||
const auto found = std::ranges::find(next.descriptors, candidate);
|
||||
if (found == next.descriptors.end()) {
|
||||
if (next.descriptors.size() >= ShaderInfo::MaxImages) {
|
||||
return false;
|
||||
}
|
||||
next.descriptors.push_back(candidate);
|
||||
next.candidates.push_back(static_cast<uint32_t>(next.descriptors.size() - 1u));
|
||||
} else {
|
||||
@@ -253,339 +280,122 @@ bool MaterializeIndirectImage(const DescriptorSource::IndirectImage& indirect,
|
||||
return true;
|
||||
}
|
||||
|
||||
size_t FlattenedRuntimeDwords(const Program& program) {
|
||||
size_t size = program.srt_reads.size();
|
||||
for (uint32_t resource = 0; resource < program.info.images.size(); resource++) {
|
||||
const auto& image = program.info.images[resource];
|
||||
if (image.indirect_root == resource) {
|
||||
size = std::max(size, static_cast<size_t>(image.indirect_mapping_offset) + 1u +
|
||||
static_cast<size_t>(image.indirect_mapping_capacity) * 2u);
|
||||
}
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool ValidateResourceSnapshot(const Program& program, const ResourceSnapshot& snapshot) {
|
||||
if (!program.resource_tracking_complete) {
|
||||
return false;
|
||||
}
|
||||
if (snapshot.buffers.size() != program.info.buffers.size() ||
|
||||
snapshot.images.size() != program.info.images.size() ||
|
||||
snapshot.samplers.size() != program.info.samplers.size()) {
|
||||
return false;
|
||||
}
|
||||
if (snapshot.flattened_srt.size() != FlattenedRuntimeDwords(program)) {
|
||||
return false;
|
||||
}
|
||||
if (program.binding_layout_complete) {
|
||||
for (const auto reg: program.bindings.user_data_registers) {
|
||||
if (reg < program.user_data_base ||
|
||||
reg - program.user_data_base >= snapshot.user_data.size()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
for (uint32_t i = 0; i < program.info.buffers.size(); i++) {
|
||||
const auto alias = program.info.buffers[i].image_alias;
|
||||
if (alias != BufferResource::NoImageAlias && alias >= program.info.images.size()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
const auto CheckWidth = [&](const auto& values, uint32_t width) {
|
||||
for (uint32_t i = 0; i < values.size(); i++) {
|
||||
if (values[i].dword_count != width) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
};
|
||||
std::unordered_set<uint32_t> indirect_resources;
|
||||
for (const auto& table: snapshot.indirect_images) {
|
||||
const auto* source = table.resource < program.info.images.size()
|
||||
? Source(program, program.info.images[table.resource].source)
|
||||
: nullptr;
|
||||
if (source == nullptr || !source->indirect_image.has_value() ||
|
||||
program.info.images[table.resource].indirect_root != ImageResource::NoIndirectImage ||
|
||||
table.keys.empty() || table.keys.size() != table.candidates.size() ||
|
||||
table.capacity < table.keys.size() || table.descriptors.empty() ||
|
||||
!indirect_resources.insert(table.resource).second) {
|
||||
return false;
|
||||
}
|
||||
for (const auto candidate: table.candidates) {
|
||||
if (candidate >= table.descriptors.size()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (snapshot.images[table.resource] != table.descriptors[table.candidates[0]]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return CheckWidth(snapshot.buffers, 4) && CheckWidth(snapshot.images, 8) &&
|
||||
CheckWidth(snapshot.samplers, 4);
|
||||
}
|
||||
|
||||
bool ValidateResourceSpecialization(const Program& program, const ResourceSnapshot& snapshot) {
|
||||
if (!ValidateResourceSnapshot(program, snapshot)) {
|
||||
return false;
|
||||
}
|
||||
if (!snapshot.indirect_images.empty()) {
|
||||
return false;
|
||||
}
|
||||
for (uint32_t i = 0; i < program.info.buffers.size(); i++) {
|
||||
const auto& buffer = program.info.buffers[i];
|
||||
ShaderBufferResource descriptor;
|
||||
if (!DecodeBufferDescriptor(snapshot.buffers[i], descriptor)) {
|
||||
return false;
|
||||
}
|
||||
if (buffer.packed_stride != descriptor.PackedStride() ||
|
||||
buffer.descriptor_format != descriptor.Format() ||
|
||||
buffer.descriptor_swizzle != descriptor.DstSelXYZW()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
for (uint32_t i = 0; i < program.info.images.size(); i++) {
|
||||
const auto& image = program.info.images[i];
|
||||
const auto& descriptor = snapshot.images[i];
|
||||
const auto mip_count = StorageMipCount(image, descriptor);
|
||||
if (mip_count == 0u || mip_count != image.mip_count) {
|
||||
return false;
|
||||
}
|
||||
if (NullImageDescriptor(descriptor)) {
|
||||
if (image.indirect_root != ImageResource::NoIndirectImage) {
|
||||
const auto root = image.indirect_root;
|
||||
if (root >= program.info.images.size() ||
|
||||
program.info.images[root].resource_class != image.resource_class ||
|
||||
program.info.images[root].numeric_class != image.numeric_class ||
|
||||
program.info.images[root].dimension != image.dimension ||
|
||||
program.info.images[root].mip_mode != image.mip_mode ||
|
||||
program.info.images[root].mip_count != image.mip_count ||
|
||||
program.info.images[root].conversion_format != image.conversion_format ||
|
||||
program.info.images[root].shader_swizzle != image.shader_swizzle ||
|
||||
program.info.images[root].cube != image.cube) {
|
||||
return false;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
const auto canonical_numeric = image.atomic ? Prospero::TextureNumericClass::Uint
|
||||
: Prospero::TextureNumericClass::Float;
|
||||
if (image.dimension != Decoder::ImageDimension::Dim2D || image.cube ||
|
||||
image.resource_class == ImageResourceClass::None ||
|
||||
image.numeric_class != canonical_numeric) {
|
||||
return false;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
const auto dimension = DescriptorDimension(descriptor, image.dimension);
|
||||
if (dimension == Decoder::ImageDimension::Unknown || dimension != image.dimension ||
|
||||
DescriptorIsCube(descriptor) != image.cube) {
|
||||
return false;
|
||||
}
|
||||
if (image.resource_class == ImageResourceClass::None) {
|
||||
return false;
|
||||
}
|
||||
const bool storage = image.resource_class == ImageResourceClass::Storage;
|
||||
const auto format =
|
||||
static_cast<Prospero::BufferFormat>((descriptor.dwords[1] >> 20u) & 0x1ffu);
|
||||
if (image.atomic && format != Prospero::BufferFormat::k32UInt) {
|
||||
return false;
|
||||
}
|
||||
const auto conversion_format = ImageConversionFormat(format);
|
||||
if (image.conversion_format != conversion_format) {
|
||||
return false;
|
||||
}
|
||||
if ((storage || conversion_format != Prospero::BufferFormat::kInvalid) &&
|
||||
image.shader_swizzle != DescriptorImageSwizzle(descriptor)) {
|
||||
return false;
|
||||
}
|
||||
auto numeric_class = Prospero::SampledTextureNumericClass(format);
|
||||
if (storage) {
|
||||
const bool raw_sint = format == Prospero::BufferFormat::k32SInt && image.written &&
|
||||
!image.read && !image.atomic;
|
||||
if ((!raw_sint && numeric_class == Prospero::TextureNumericClass::Sint) ||
|
||||
numeric_class == Prospero::TextureNumericClass::Unsupported) {
|
||||
return false;
|
||||
}
|
||||
if (raw_sint) {
|
||||
numeric_class = Prospero::TextureNumericClass::Uint;
|
||||
}
|
||||
} else if (image.depth_compare && numeric_class != Prospero::TextureNumericClass::Float) {
|
||||
return false;
|
||||
}
|
||||
if (image.numeric_class != numeric_class) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MaterializeResources(const Program& program, const SrtRuntime& runtime,
|
||||
ResourceSnapshot& snapshot) {
|
||||
static bool MaterializeSnapshot(const ResourcePlan& program, const SrtRuntime& runtime,
|
||||
MaterializedSnapshot& snapshot) {
|
||||
if (!program.resource_tracking_complete) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<DescriptorSourceRequest> requests;
|
||||
std::vector<uint8_t> clean_flat_slots(program.srt_reads.size());
|
||||
requests.reserve(program.info.buffers.size() + program.info.images.size() * 2u +
|
||||
program.info.samplers.size());
|
||||
for (const auto& buffer: program.info.buffers) {
|
||||
requests.push_back({buffer.source});
|
||||
}
|
||||
for (const auto& image: program.info.images) {
|
||||
const auto* source = Source(program, image.source);
|
||||
if (source != nullptr && source->indirect_image.has_value()) {
|
||||
if (runtime.read_specialization_memory == nullptr) {
|
||||
return false;
|
||||
}
|
||||
MarkCleanFlatSlots(program, Source(program, source->indirect_image->material_source),
|
||||
clean_flat_slots);
|
||||
MarkCleanFlatSlots(program, Source(program, source->indirect_image->heap_source),
|
||||
clean_flat_slots);
|
||||
} else {
|
||||
requests.push_back({image.source});
|
||||
}
|
||||
}
|
||||
for (const auto& sampler: program.info.samplers) {
|
||||
requests.push_back({sampler.source});
|
||||
if (program.requires_specialization_memory && runtime.read_specialization_memory == nullptr) {
|
||||
return false;
|
||||
}
|
||||
std::vector<DescriptorValue> values;
|
||||
std::vector<uint32_t> flattened_srt;
|
||||
if (!EvaluateRuntimeSources(program, requests, runtime, values, flattened_srt,
|
||||
clean_flat_slots)) {
|
||||
if (!EvaluateRuntimeSources(program, program.materialization_sources, runtime, values,
|
||||
flattened_srt, program.clean_flat_slots)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ResourceSnapshot next;
|
||||
auto cursor = values.begin();
|
||||
auto& next = snapshot.resources;
|
||||
auto cursor = values.begin();
|
||||
next.buffers.assign(cursor, cursor + program.info.buffers.size());
|
||||
cursor += program.info.buffers.size();
|
||||
for (auto& descriptor: next.buffers) {
|
||||
ShaderBufferResource buffer;
|
||||
if (DecodeBufferDescriptor(descriptor, buffer) && buffer.Type() != 0) {
|
||||
descriptor.dwords.fill(0);
|
||||
}
|
||||
}
|
||||
next.flattened_srt = std::move(flattened_srt);
|
||||
next.flattened_srt.resize(FlattenedRuntimeDwords(program));
|
||||
next.images.resize(program.info.images.size());
|
||||
std::vector<uint8_t> image_written(program.info.images.size());
|
||||
for (uint32_t image_index = 0; image_index < program.info.images.size(); image_index++) {
|
||||
const auto& image = program.info.images[image_index];
|
||||
const auto* source = Source(program, image.source);
|
||||
if (source != nullptr && source->indirect_image.has_value()) {
|
||||
if (image.indirect_root != ImageResource::NoIndirectImage &&
|
||||
image.indirect_root != image_index) {
|
||||
continue;
|
||||
}
|
||||
const std::array requests {
|
||||
DescriptorSourceRequest {source->indirect_image->material_source},
|
||||
DescriptorSourceRequest {source->indirect_image->heap_source}};
|
||||
SrtRuntime clean_runtime = runtime;
|
||||
clean_runtime.read_memory = runtime.read_specialization_memory;
|
||||
const std::array requests {source->indirect_image->material_source,
|
||||
source->indirect_image->heap_source};
|
||||
SrtRuntime clean_runtime = runtime;
|
||||
clean_runtime.read_memory = runtime.read_specialization_memory;
|
||||
std::vector<DescriptorValue> tables;
|
||||
if (!EvaluateDescriptorSources(program, requests, clean_runtime, tables)) {
|
||||
return false;
|
||||
}
|
||||
const auto& material = tables[0];
|
||||
const auto& heap = tables[1];
|
||||
ResourceSnapshot::IndirectImage table;
|
||||
if (!MaterializeIndirectImage(*source->indirect_image, material, heap, runtime,
|
||||
table)) {
|
||||
const auto& material = tables[0];
|
||||
const auto& heap = tables[1];
|
||||
IndirectImage table;
|
||||
if (!MaterializeIndirectImage(*source->indirect_image, material, heap, image.r128,
|
||||
runtime, table)) {
|
||||
return false;
|
||||
}
|
||||
if (image.indirect_root == ImageResource::NoIndirectImage) {
|
||||
next.images[image_index] = table.descriptors[table.candidates[0]];
|
||||
image_written[image_index] = 1u;
|
||||
if (table.descriptors.size() > 1u) {
|
||||
table.resource = image_index;
|
||||
next.indirect_images.push_back(std::move(table));
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (table.keys.size() > image.indirect_mapping_capacity ||
|
||||
table.descriptors.size() > image.indirect_resources.size()) {
|
||||
return false;
|
||||
}
|
||||
for (uint32_t candidate = 0; candidate < image.indirect_resources.size(); candidate++) {
|
||||
const auto resource = image.indirect_resources[candidate];
|
||||
if (resource >= program.info.images.size() ||
|
||||
program.info.images[resource].indirect_root != image_index) {
|
||||
return false;
|
||||
}
|
||||
next.images[resource] =
|
||||
table.descriptors[candidate < table.descriptors.size() ? candidate : 0u];
|
||||
image_written[resource] = 1u;
|
||||
}
|
||||
std::vector<uint32_t> order(table.keys.size());
|
||||
std::iota(order.begin(), order.end(), 0u);
|
||||
std::ranges::sort(order, {}, [&](uint32_t index) { return table.keys[index]; });
|
||||
const auto mapping = image.indirect_mapping_offset;
|
||||
next.flattened_srt[mapping] = static_cast<uint32_t>(table.keys.size());
|
||||
for (uint32_t entry = 0; entry < order.size(); entry++) {
|
||||
const auto source = order[entry];
|
||||
const auto offset = mapping + 1u + entry * 2u;
|
||||
next.flattened_srt[offset] = table.keys[source];
|
||||
next.flattened_srt[offset + 1] = table.candidates[source];
|
||||
next.images[image_index] = table.descriptors[table.candidates[0]];
|
||||
if (table.descriptors.size() > 1u) {
|
||||
table.resource = image_index;
|
||||
snapshot.indirect_images.push_back(std::move(table));
|
||||
}
|
||||
} else {
|
||||
auto descriptor = *cursor++;
|
||||
if (!ValidImageDescriptor(descriptor, image.r128)) {
|
||||
descriptor.dwords.fill(0);
|
||||
}
|
||||
next.images[image_index] = descriptor;
|
||||
image_written[image_index] = 1u;
|
||||
next.images[image_index] = descriptor;
|
||||
}
|
||||
}
|
||||
if (std::ranges::find(image_written, uint8_t {0}) != image_written.end()) {
|
||||
return false;
|
||||
}
|
||||
next.samplers.assign(cursor, cursor + program.info.samplers.size());
|
||||
next.user_data.assign(runtime.user_data.begin(), runtime.user_data.end());
|
||||
if (!ValidateResourceSnapshot(program, next)) {
|
||||
return false;
|
||||
}
|
||||
snapshot = std::move(next);
|
||||
return true;
|
||||
}
|
||||
|
||||
void SpecializeResources(Program& program, ResourceSnapshot& snapshot) {
|
||||
if (!program.resource_tracking_complete || program.shader_info_complete ||
|
||||
program.binding_layout_complete) {
|
||||
return SpecializationFail(!program.resource_tracking_complete
|
||||
? "shader resources were not tracked"
|
||||
: "resource specialization is too late");
|
||||
}
|
||||
if (!ValidateResourceSnapshot(program, snapshot)) {
|
||||
return SpecializationFail("invalid resource snapshot");
|
||||
}
|
||||
struct SamplerPlan {
|
||||
std::array<uint32_t, ShaderInfo::MaxSamplers> point_sampler {};
|
||||
uint32_t sampler_count = 0;
|
||||
};
|
||||
|
||||
auto next = program.info;
|
||||
auto next_snapshot = snapshot;
|
||||
template <typename Images>
|
||||
bool BuildSamplerPlan(const ShaderInfo& base, const Images& images, SamplerPlan& plan);
|
||||
|
||||
static bool BuildResourceSpecialization(const ResourcePlan& program, MaterializedSnapshot snapshot,
|
||||
ResourceSnapshot& specialized_snapshot,
|
||||
ResourceSpecialization& specialization) {
|
||||
auto next_snapshot = std::move(snapshot.resources);
|
||||
ResourceSpecialization next_specialization;
|
||||
next_specialization.buffers.reserve(program.info.buffers.size());
|
||||
size_t image_count = program.info.images.size();
|
||||
size_t mapping_words = 0;
|
||||
for (const auto& table: snapshot.indirect_images) {
|
||||
if (table.resource >= next.images.size() || table.descriptors.size() < 2u ||
|
||||
next.images.size() + table.descriptors.size() - 1u > ShaderInfo::MaxImages) {
|
||||
if (table.resource >= program.info.images.size() || table.descriptors.size() < 2u ||
|
||||
image_count + table.descriptors.size() - 1u > ShaderInfo::MaxImages) {
|
||||
return SpecializationFail(
|
||||
"indirect image candidates exceed the dense image resource limit");
|
||||
}
|
||||
const auto root_image = next.images[table.resource];
|
||||
std::vector<uint32_t> resources(table.descriptors.size());
|
||||
resources[0] = table.resource;
|
||||
image_count += table.descriptors.size() - 1u;
|
||||
mapping_words += 1u + table.keys.size() * 2u;
|
||||
}
|
||||
next_snapshot.images.reserve(image_count);
|
||||
next_snapshot.flattened_srt.reserve(next_snapshot.flattened_srt.size() + mapping_words);
|
||||
next_specialization.images.reserve(image_count);
|
||||
for (const auto& image: program.info.images) {
|
||||
next_specialization.images.push_back({
|
||||
.numeric_class = image.numeric_class,
|
||||
.dimension = image.dimension,
|
||||
.mip_count = image.mip_count,
|
||||
.conversion_format = image.conversion_format,
|
||||
.shader_swizzle = image.shader_swizzle,
|
||||
.indirect_root = image.indirect_root,
|
||||
.indirect_mapping_offset = image.indirect_mapping_offset,
|
||||
.indirect_search_iterations = image.indirect_search_iterations,
|
||||
.cube = image.cube,
|
||||
});
|
||||
}
|
||||
for (const auto& table: snapshot.indirect_images) {
|
||||
const auto root_image = next_specialization.images[table.resource];
|
||||
for (uint32_t candidate = 1; candidate < table.descriptors.size(); candidate++) {
|
||||
resources[candidate] = static_cast<uint32_t>(next.images.size());
|
||||
auto image = root_image;
|
||||
image.indirect_root = table.resource;
|
||||
next.images.push_back(std::move(image));
|
||||
auto image = root_image;
|
||||
image.indirect_root = table.resource;
|
||||
next_specialization.images.push_back(image);
|
||||
next_snapshot.images.push_back(table.descriptors[candidate]);
|
||||
}
|
||||
auto& root = next.images[table.resource];
|
||||
root.indirect_root = table.resource;
|
||||
root.indirect_mapping_offset = static_cast<uint32_t>(next_snapshot.flattened_srt.size());
|
||||
root.indirect_mapping_capacity = table.capacity;
|
||||
root.indirect_resources = std::move(resources);
|
||||
auto& root = next_specialization.images[table.resource];
|
||||
root.indirect_root = table.resource;
|
||||
root.indirect_mapping_offset = static_cast<uint32_t>(next_snapshot.flattened_srt.size());
|
||||
root.indirect_search_iterations = std::bit_width(table.keys.size());
|
||||
next_snapshot.flattened_srt.resize(next_snapshot.flattened_srt.size() + 1u +
|
||||
static_cast<size_t>(table.capacity) * 2u);
|
||||
table.keys.size() * 2u);
|
||||
std::vector<uint32_t> order(table.keys.size());
|
||||
std::iota(order.begin(), order.end(), 0u);
|
||||
std::ranges::sort(order, {}, [&](uint32_t index) { return table.keys[index]; });
|
||||
@@ -599,36 +409,58 @@ void SpecializeResources(Program& program, ResourceSnapshot& snapshot) {
|
||||
}
|
||||
next_snapshot.images[table.resource] = table.descriptors[0];
|
||||
}
|
||||
next_snapshot.indirect_images.clear();
|
||||
for (uint32_t i = 0; i < next.buffers.size(); i++) {
|
||||
for (uint32_t i = 0; i < program.info.buffers.size(); i++) {
|
||||
auto& descriptor_value = next_snapshot.buffers[i];
|
||||
ShaderBufferResource descriptor;
|
||||
if (!DecodeBufferDescriptor(next_snapshot.buffers[i], descriptor)) {
|
||||
if (!DecodeBufferDescriptor(descriptor_value, descriptor)) {
|
||||
return SpecializationFail(fmt::format("buffer descriptor {} has invalid width", i));
|
||||
}
|
||||
next.buffers[i].packed_stride = descriptor.PackedStride();
|
||||
next.buffers[i].descriptor_format = descriptor.Format();
|
||||
next.buffers[i].descriptor_swizzle = descriptor.DstSelXYZW();
|
||||
if (descriptor.Type() != 0) {
|
||||
descriptor_value.dwords.fill(0);
|
||||
descriptor = {};
|
||||
}
|
||||
auto packed_stride = descriptor.PackedStride();
|
||||
const auto stride = packed_stride & 0x3fffu;
|
||||
const bool swizzle = stride != 0u && ((packed_stride >> 14u) & 1u) != 0u;
|
||||
if (stride == 0u) {
|
||||
packed_stride &= ~((1u << 14u) | (3u << 16u));
|
||||
} else if (!swizzle) {
|
||||
packed_stride &= ~(3u << 16u);
|
||||
}
|
||||
next_specialization.buffers.push_back({
|
||||
.packed_stride = packed_stride,
|
||||
.descriptor_format = program.info.buffers[i].formatted
|
||||
? descriptor.Format()
|
||||
: Prospero::BufferFormat::kInvalid,
|
||||
.descriptor_swizzle =
|
||||
program.info.buffers[i].formatted ? descriptor.DstSelXYZW() : DstSel(4, 5, 6, 7),
|
||||
});
|
||||
}
|
||||
for (uint32_t i = 0; i < next.images.size(); i++) {
|
||||
for (uint32_t i = 0; i < next_specialization.images.size(); i++) {
|
||||
const auto& descriptor = next_snapshot.images[i];
|
||||
auto& image = next.images[i];
|
||||
if (image.resource_class == ImageResourceClass::None ||
|
||||
(image.atomic && image.resource_class != ImageResourceClass::Storage)) {
|
||||
auto& image = next_specialization.images[i];
|
||||
const auto base_index = i < program.info.images.size() ? i : image.indirect_root;
|
||||
if (base_index >= program.info.images.size()) {
|
||||
return SpecializationFail(fmt::format("image resource {} has an invalid root", i));
|
||||
}
|
||||
const auto& base = program.info.images[base_index];
|
||||
if (base.resource_class == ImageResourceClass::None ||
|
||||
(base.atomic && base.resource_class != ImageResourceClass::Storage)) {
|
||||
return SpecializationFail(fmt::format("image resource {} has an invalid class", i));
|
||||
}
|
||||
image.mip_count = StorageMipCount(image, descriptor);
|
||||
image.mip_count = StorageMipCount(base, descriptor);
|
||||
if (image.mip_count == 0u) {
|
||||
return SpecializationFail(
|
||||
fmt::format("storage image descriptor {} has an invalid mip range", i));
|
||||
}
|
||||
if (NullImageDescriptor(descriptor)) {
|
||||
image.numeric_class = image.atomic ? Prospero::TextureNumericClass::Uint
|
||||
: Prospero::TextureNumericClass::Float;
|
||||
image.numeric_class = base.atomic ? Prospero::TextureNumericClass::Uint
|
||||
: Prospero::TextureNumericClass::Float;
|
||||
image.dimension = Decoder::ImageDimension::Dim2D;
|
||||
image.cube = false;
|
||||
continue;
|
||||
}
|
||||
const auto descriptor_dimension = DescriptorDimension(descriptor, image.dimension);
|
||||
const auto descriptor_dimension = DescriptorDimension(descriptor, base.dimension);
|
||||
if (descriptor_dimension == Decoder::ImageDimension::Unknown) {
|
||||
return SpecializationFail(fmt::format(
|
||||
"image descriptor {} has unsupported type {}: {:08x},{:08x},{:08x},{:08x},"
|
||||
@@ -641,19 +473,19 @@ void SpecializeResources(Program& program, ResourceSnapshot& snapshot) {
|
||||
image.cube = DescriptorIsCube(descriptor);
|
||||
const auto format =
|
||||
static_cast<Prospero::BufferFormat>((descriptor.dwords[1] >> 20u) & 0x1ffu);
|
||||
if (image.atomic && format != Prospero::BufferFormat::k32UInt) {
|
||||
if (base.atomic && format != Prospero::BufferFormat::k32UInt) {
|
||||
return SpecializationFail(
|
||||
fmt::format("atomic image descriptor {} uses unsupported format {}", i,
|
||||
static_cast<uint32_t>(format)));
|
||||
}
|
||||
const bool storage = image.resource_class == ImageResourceClass::Storage;
|
||||
const bool storage = base.resource_class == ImageResourceClass::Storage;
|
||||
image.conversion_format = ImageConversionFormat(format);
|
||||
if (storage || image.conversion_format != Prospero::BufferFormat::kInvalid) {
|
||||
image.shader_swizzle = DescriptorImageSwizzle(descriptor);
|
||||
}
|
||||
const bool raw_sint_storage = storage && format == Prospero::BufferFormat::k32SInt &&
|
||||
image.written && !image.read && !image.atomic;
|
||||
image.numeric_class = Prospero::SampledTextureNumericClass(format);
|
||||
base.written && !base.read && !base.atomic;
|
||||
image.numeric_class = Prospero::SampledTextureNumericClass(format);
|
||||
if (storage) {
|
||||
if ((!raw_sint_storage && image.numeric_class == Prospero::TextureNumericClass::Sint) ||
|
||||
image.numeric_class == Prospero::TextureNumericClass::Unsupported) {
|
||||
@@ -665,46 +497,49 @@ void SpecializeResources(Program& program, ResourceSnapshot& snapshot) {
|
||||
image.numeric_class = Prospero::TextureNumericClass::Uint;
|
||||
}
|
||||
} else if (image.numeric_class == Prospero::TextureNumericClass::Unsupported ||
|
||||
(image.depth_compare &&
|
||||
(base.depth_compare &&
|
||||
image.numeric_class != Prospero::TextureNumericClass::Float)) {
|
||||
return SpecializationFail(
|
||||
fmt::format("sampled image descriptor {} uses unsupported format {}", i,
|
||||
static_cast<uint32_t>(format)));
|
||||
}
|
||||
}
|
||||
for (uint32_t root_index = 0; root_index < next.images.size(); root_index++) {
|
||||
auto& root = next.images[root_index];
|
||||
for (uint32_t root_index = 0; root_index < next_specialization.images.size(); root_index++) {
|
||||
auto& root = next_specialization.images[root_index];
|
||||
if (root.indirect_root != root_index) {
|
||||
continue;
|
||||
}
|
||||
if (root.indirect_mapping_capacity == 0u || root.indirect_resources.size() < 2u ||
|
||||
const auto key_count = root.indirect_mapping_offset < next_snapshot.flattened_srt.size()
|
||||
? next_snapshot.flattened_srt[root.indirect_mapping_offset]
|
||||
: 0u;
|
||||
if (root.indirect_search_iterations == 0u || key_count < 2u ||
|
||||
static_cast<size_t>(root.indirect_mapping_offset) + 1u +
|
||||
static_cast<size_t>(root.indirect_mapping_capacity) * 2u >
|
||||
static_cast<size_t>(key_count) * 2u >
|
||||
next_snapshot.flattened_srt.size()) {
|
||||
return SpecializationFail("indirect image specialization has an invalid key mapping");
|
||||
}
|
||||
uint32_t exemplar = ImageResource::NoIndirectImage;
|
||||
for (const auto resource: root.indirect_resources) {
|
||||
if (resource >= next.images.size() ||
|
||||
next.images[resource].indirect_root != root_index) {
|
||||
return SpecializationFail("indirect image specialization has an invalid candidate");
|
||||
uint32_t exemplar = ImageResource::NoIndirectImage;
|
||||
uint32_t resource_count = 0;
|
||||
for (uint32_t resource = 0; resource < next_specialization.images.size(); resource++) {
|
||||
if (next_specialization.images[resource].indirect_root != root_index) {
|
||||
continue;
|
||||
}
|
||||
if (!NullImageDescriptor(next_snapshot.images[resource])) {
|
||||
resource_count++;
|
||||
if (exemplar == ImageResource::NoIndirectImage &&
|
||||
!NullImageDescriptor(next_snapshot.images[resource])) {
|
||||
exemplar = resource;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (exemplar == ImageResource::NoIndirectImage) {
|
||||
if (resource_count < 2u || exemplar == ImageResource::NoIndirectImage) {
|
||||
return SpecializationFail("indirect image specialization has no typed candidate");
|
||||
}
|
||||
const auto& image_class = next.images[exemplar];
|
||||
for (uint32_t candidate = 0; candidate < next.images.size(); candidate++) {
|
||||
auto& image = next.images[candidate];
|
||||
const auto& image_class = next_specialization.images[exemplar];
|
||||
for (uint32_t candidate = 0; candidate < next_specialization.images.size(); candidate++) {
|
||||
auto& image = next_specialization.images[candidate];
|
||||
if (image.indirect_root != root_index) {
|
||||
continue;
|
||||
}
|
||||
if (NullImageDescriptor(next_snapshot.images[candidate])) {
|
||||
image.resource_class = image_class.resource_class;
|
||||
image.numeric_class = image_class.numeric_class;
|
||||
image.dimension = image_class.dimension;
|
||||
image.mip_count = image_class.mip_count;
|
||||
@@ -712,65 +547,225 @@ void SpecializeResources(Program& program, ResourceSnapshot& snapshot) {
|
||||
image.shader_swizzle = image_class.shader_swizzle;
|
||||
image.cube = image_class.cube;
|
||||
}
|
||||
if (image.resource_class != image_class.resource_class ||
|
||||
image.numeric_class != image_class.numeric_class ||
|
||||
if (image.numeric_class != image_class.numeric_class ||
|
||||
image.dimension != image_class.dimension ||
|
||||
image.mip_mode != image_class.mip_mode ||
|
||||
image.mip_count != image_class.mip_count ||
|
||||
image.conversion_format != image_class.conversion_format ||
|
||||
image.shader_swizzle != image_class.shader_swizzle ||
|
||||
image.depth_compare != image_class.depth_compare ||
|
||||
image.cube != image_class.cube) {
|
||||
return SpecializationFail(
|
||||
fmt::format("indirect image table at pc 0x{:08x} has incompatible candidates",
|
||||
root.first_use_pc));
|
||||
program.info.images[root_index].first_use_pc));
|
||||
}
|
||||
}
|
||||
}
|
||||
auto memory_info = program.memory_info;
|
||||
SamplerPlan sampler_plan;
|
||||
if (!BuildSamplerPlan(program.info, next_specialization.images, sampler_plan)) {
|
||||
return SpecializationFail("specialized sampler layout exceeds its resource limit");
|
||||
}
|
||||
for (uint32_t index = 0; index < program.info.samplers.size(); index++) {
|
||||
const auto target = sampler_plan.point_sampler[index];
|
||||
if (target != UINT32_MAX && target >= program.info.samplers.size()) {
|
||||
next_snapshot.samplers.push_back(next_snapshot.samplers[index]);
|
||||
}
|
||||
}
|
||||
specialization = std::move(next_specialization);
|
||||
specialized_snapshot = std::move(next_snapshot);
|
||||
return true;
|
||||
}
|
||||
|
||||
struct SamplerUsage {
|
||||
bool native = false;
|
||||
bool point_only = false;
|
||||
};
|
||||
std::vector<SamplerUsage> sampler_usage(next.samplers.size());
|
||||
for (const auto& pair: next.sampled_pairs) {
|
||||
if (pair.image >= next.images.size() || pair.sampler >= next.samplers.size()) {
|
||||
return SpecializationFail(
|
||||
fmt::format("sampled resource pair at pc 0x{:08x} is invalid", pair.first_use_pc));
|
||||
template <typename Images>
|
||||
bool BuildSamplerPlan(const ShaderInfo& base, const Images& images, SamplerPlan& plan) {
|
||||
if (base.samplers.size() > plan.point_sampler.size()) {
|
||||
return false;
|
||||
}
|
||||
std::array<uint8_t, ShaderInfo::MaxSamplers> usage {};
|
||||
plan.point_sampler.fill(UINT32_MAX);
|
||||
plan.sampler_count = static_cast<uint32_t>(base.samplers.size());
|
||||
for (const auto& pair: base.sampled_pairs) {
|
||||
if (pair.image >= images.size() || pair.sampler >= base.samplers.size()) {
|
||||
return false;
|
||||
}
|
||||
auto& usage = sampler_usage[pair.sampler];
|
||||
if (RequiresPointSampler(next.images[pair.image])) {
|
||||
usage.point_only = true;
|
||||
usage[pair.sampler] |= RequiresPointSampler(images[pair.image]) ? 2u : 1u;
|
||||
}
|
||||
for (uint32_t index = 0; index < base.samplers.size(); index++) {
|
||||
if ((usage[index] & 2u) == 0u) {
|
||||
continue;
|
||||
}
|
||||
if ((usage[index] & 1u) == 0u) {
|
||||
plan.point_sampler[index] = index;
|
||||
} else {
|
||||
usage.native = true;
|
||||
if (plan.sampler_count >= ShaderInfo::MaxSamplers) {
|
||||
return false;
|
||||
}
|
||||
plan.point_sampler[index] = plan.sampler_count++;
|
||||
}
|
||||
}
|
||||
std::vector<uint32_t> point_sampler(next.samplers.size(), UINT32_MAX);
|
||||
for (uint32_t i = 0; i < sampler_usage.size(); i++) {
|
||||
if (!sampler_usage[i].point_only) {
|
||||
return true;
|
||||
}
|
||||
|
||||
ResourcePlan ExtractResourcePlan(const Program& program) {
|
||||
ResourcePlan plan;
|
||||
plan.stage = program.stage;
|
||||
plan.shader_hash = program.shader_hash;
|
||||
plan.user_data_base = program.user_data_base;
|
||||
plan.user_data_count = program.user_data_count;
|
||||
plan.info = program.info;
|
||||
plan.memory_info = program.memory_info;
|
||||
plan.srt_plan_complete = program.srt_plan_complete;
|
||||
plan.resource_tracking_complete = program.resource_tracking_complete;
|
||||
|
||||
std::unordered_map<const Inst*, Inst*> cloned;
|
||||
std::function<Value(Value)> Clone = [&](Value value) -> Value {
|
||||
value = value.Resolve();
|
||||
const auto* source = value.TryInstruction();
|
||||
if (source == nullptr) {
|
||||
return value;
|
||||
}
|
||||
if (source->GetOpcode() == ValueOpcode::Phi) {
|
||||
const auto invariant = ResolveInvariantPhi(program, value);
|
||||
if (!invariant.IsEmpty() && invariant != value) {
|
||||
return Clone(invariant);
|
||||
}
|
||||
}
|
||||
if (const auto found = cloned.find(source); found != cloned.end()) {
|
||||
return Value(found->second);
|
||||
}
|
||||
auto& target =
|
||||
plan.value_storage.emplace_back(source->GetOpcode(), source->Flags<uint64_t>());
|
||||
cloned.emplace(source, &target);
|
||||
if (source->GetOpcode() == ValueOpcode::Phi) {
|
||||
for (size_t index = 0; index < source->NumArgs(); index++) {
|
||||
target.AddPhiOperand(nullptr, Clone(source->Arg(index)));
|
||||
}
|
||||
} else {
|
||||
for (size_t index = 0; index < source->NumArgs(); index++) {
|
||||
target.SetArg(index, Clone(source->Arg(index)));
|
||||
}
|
||||
}
|
||||
return Value(&target);
|
||||
};
|
||||
|
||||
plan.descriptor_sources.reserve(program.descriptor_sources.size());
|
||||
for (const auto& source: program.descriptor_sources) {
|
||||
auto& target = plan.descriptor_sources.emplace_back();
|
||||
target.dword_count = source.dword_count;
|
||||
target.indirect_image = source.indirect_image;
|
||||
for (uint32_t dword = 0; dword < source.dword_count; dword++) {
|
||||
target.dwords[dword] = Clone(source.dwords[dword]);
|
||||
}
|
||||
}
|
||||
plan.srt_reads.reserve(program.srt_reads.size());
|
||||
for (const auto& read: program.srt_reads) {
|
||||
plan.srt_reads.push_back({Clone(read.value), read.flat_offset});
|
||||
}
|
||||
plan.materialization_sources.reserve(plan.info.buffers.size() + plan.info.images.size() +
|
||||
plan.info.samplers.size());
|
||||
for (const auto& buffer: plan.info.buffers) {
|
||||
plan.materialization_sources.push_back(buffer.source);
|
||||
}
|
||||
for (const auto& image: plan.info.images) {
|
||||
const auto* source = Source(plan, image.source);
|
||||
if (source != nullptr && source->indirect_image.has_value()) {
|
||||
plan.requires_specialization_memory = true;
|
||||
} else {
|
||||
plan.materialization_sources.push_back(image.source);
|
||||
}
|
||||
}
|
||||
for (const auto& sampler: plan.info.samplers) {
|
||||
plan.materialization_sources.push_back(sampler.source);
|
||||
}
|
||||
plan.clean_flat_slots.resize(plan.srt_reads.size());
|
||||
for (const auto& image: plan.info.images) {
|
||||
const auto* source = Source(plan, image.source);
|
||||
if (source == nullptr || !source->indirect_image.has_value()) {
|
||||
continue;
|
||||
}
|
||||
if (!sampler_usage[i].native) {
|
||||
next.samplers[i].force_point_filtering = true;
|
||||
point_sampler[i] = i;
|
||||
MarkCleanFlatSlots(plan, Source(plan, source->indirect_image->material_source),
|
||||
plan.clean_flat_slots);
|
||||
MarkCleanFlatSlots(plan, Source(plan, source->indirect_image->heap_source),
|
||||
plan.clean_flat_slots);
|
||||
}
|
||||
return plan;
|
||||
}
|
||||
|
||||
bool MaterializeResources(const ResourcePlan& program, const SrtRuntime& runtime,
|
||||
ResourceSnapshot& snapshot, ResourceSpecialization& specialization) {
|
||||
MaterializedSnapshot materialized;
|
||||
if (!MaterializeSnapshot(program, runtime, materialized)) {
|
||||
return false;
|
||||
}
|
||||
return BuildResourceSpecialization(program, std::move(materialized), snapshot, specialization);
|
||||
}
|
||||
|
||||
void ApplyResourceSpecialization(Program& program, const ResourceSpecialization& specialization) {
|
||||
EXIT_IF(!program.resource_tracking_complete || program.shader_info_complete ||
|
||||
program.binding_layout_complete);
|
||||
EXIT_IF(program.info.buffers.size() != specialization.buffers.size() ||
|
||||
program.info.images.size() > specialization.images.size());
|
||||
|
||||
auto buffers = program.info.buffers;
|
||||
for (size_t index = 0; index < buffers.size(); index++) {
|
||||
buffers[index].packed_stride = specialization.buffers[index].packed_stride;
|
||||
buffers[index].descriptor_format = specialization.buffers[index].descriptor_format;
|
||||
buffers[index].descriptor_swizzle = specialization.buffers[index].descriptor_swizzle;
|
||||
}
|
||||
auto images = program.info.images;
|
||||
images.reserve(specialization.images.size());
|
||||
for (uint32_t index = 0; index < specialization.images.size(); index++) {
|
||||
const auto& source = specialization.images[index];
|
||||
if (index >= images.size()) {
|
||||
EXIT_IF(source.indirect_root >= program.info.images.size());
|
||||
images.push_back(program.info.images[source.indirect_root]);
|
||||
}
|
||||
auto& image = images[index];
|
||||
image.numeric_class = source.numeric_class;
|
||||
image.dimension = source.dimension;
|
||||
image.mip_count = source.mip_count;
|
||||
image.conversion_format = source.conversion_format;
|
||||
image.shader_swizzle = source.shader_swizzle;
|
||||
image.indirect_root = source.indirect_root;
|
||||
image.indirect_mapping_offset = source.indirect_mapping_offset;
|
||||
image.indirect_search_iterations = source.indirect_search_iterations;
|
||||
image.cube = source.cube;
|
||||
image.indirect_resources.clear();
|
||||
}
|
||||
for (uint32_t index = 0; index < images.size(); index++) {
|
||||
const auto root = images[index].indirect_root;
|
||||
if (root != ImageResource::NoIndirectImage) {
|
||||
EXIT_IF(root >= images.size());
|
||||
images[root].indirect_resources.push_back(index);
|
||||
}
|
||||
}
|
||||
|
||||
SamplerPlan sampler_plan;
|
||||
EXIT_IF(!BuildSamplerPlan(program.info, images, sampler_plan));
|
||||
auto samplers = program.info.samplers;
|
||||
auto sampled_pairs = program.info.sampled_pairs;
|
||||
samplers.reserve(sampler_plan.sampler_count);
|
||||
for (uint32_t index = 0; index < program.info.samplers.size(); index++) {
|
||||
const auto target = sampler_plan.point_sampler[index];
|
||||
if (target == UINT32_MAX) {
|
||||
continue;
|
||||
}
|
||||
if (next.samplers.size() >= ShaderInfo::MaxSamplers) {
|
||||
return SpecializationFail(
|
||||
"point-filter sampler variants exceed the dense sampler resource limit");
|
||||
}
|
||||
point_sampler[i] = static_cast<uint32_t>(next.samplers.size());
|
||||
auto sampler = next.samplers[i];
|
||||
sampler.force_point_filtering = true;
|
||||
next.samplers.push_back(sampler);
|
||||
next_snapshot.samplers.push_back(next_snapshot.samplers[i]);
|
||||
}
|
||||
for (auto& pair: next.sampled_pairs) {
|
||||
if (RequiresPointSampler(next.images[pair.image])) {
|
||||
pair.sampler = point_sampler[pair.sampler];
|
||||
if (target == index) {
|
||||
samplers[index].force_point_filtering = true;
|
||||
} else {
|
||||
EXIT_IF(target != samplers.size());
|
||||
auto sampler = samplers[index];
|
||||
sampler.force_point_filtering = true;
|
||||
samplers.push_back(std::move(sampler));
|
||||
}
|
||||
}
|
||||
for (auto& pair: sampled_pairs) {
|
||||
if (RequiresPointSampler(images[pair.image])) {
|
||||
EXIT_IF(sampler_plan.point_sampler[pair.sampler] == UINT32_MAX);
|
||||
pair.sampler = sampler_plan.point_sampler[pair.sampler];
|
||||
}
|
||||
samplers[pair.sampler].depth_compare |= images[pair.image].depth_compare;
|
||||
}
|
||||
|
||||
auto memory_info = program.memory_info;
|
||||
for (const auto* block: program.blocks) {
|
||||
for (const auto& inst: *block) {
|
||||
const auto image_opcode = ImageOpcodeInfoOf(inst.GetOpcode());
|
||||
@@ -778,31 +773,24 @@ void SpecializeResources(Program& program, ResourceSnapshot& snapshot) {
|
||||
continue;
|
||||
}
|
||||
const auto index = inst.Flags<MemoryFlags>().index;
|
||||
if (index >= memory_info.size()) {
|
||||
return SpecializationFail(
|
||||
fmt::format("typed image instruction has invalid memory metadata {}", index));
|
||||
}
|
||||
EXIT_IF(index >= memory_info.size());
|
||||
auto& memory = memory_info[index];
|
||||
if (memory.resource >= next.images.size()) {
|
||||
return SpecializationFail(fmt::format(
|
||||
"typed image instruction has invalid resource {}", memory.resource));
|
||||
}
|
||||
const auto& image = next.images[memory.resource];
|
||||
EXIT_IF(memory.resource >= images.size());
|
||||
const auto& image = images[memory.resource];
|
||||
if (image_opcode.needs_sampler && RequiresPointSampler(image) &&
|
||||
memory.sampler < point_sampler.size()) {
|
||||
memory.sampler = point_sampler[memory.sampler];
|
||||
}
|
||||
if (image.indirect_root == memory.resource &&
|
||||
inst.GetOpcode() != ValueOpcode::ImageSampleRaw) {
|
||||
return SpecializationFail(
|
||||
fmt::format("indirect image table at pc 0x{:08x} is used by unsupported {}",
|
||||
inst.Flags<MemoryFlags>().pc, ValueOpcodeName(inst.GetOpcode())));
|
||||
memory.sampler < program.info.samplers.size()) {
|
||||
EXIT_IF(sampler_plan.point_sampler[memory.sampler] == UINT32_MAX);
|
||||
memory.sampler = sampler_plan.point_sampler[memory.sampler];
|
||||
}
|
||||
EXIT_IF(image.indirect_root == memory.resource &&
|
||||
inst.GetOpcode() != ValueOpcode::ImageSampleRaw);
|
||||
}
|
||||
}
|
||||
program.info = std::move(next);
|
||||
program.memory_info = std::move(memory_info);
|
||||
snapshot = std::move(next_snapshot);
|
||||
program.info.buffers = std::move(buffers);
|
||||
program.info.images = std::move(images);
|
||||
program.info.samplers = std::move(samplers);
|
||||
program.info.sampled_pairs = std::move(sampled_pairs);
|
||||
program.memory_info = std::move(memory_info);
|
||||
}
|
||||
|
||||
} // namespace Libs::Graphics::ShaderRecompiler::IR
|
||||
|
||||
@@ -5,34 +5,46 @@
|
||||
|
||||
namespace Libs::Graphics::ShaderRecompiler::IR {
|
||||
|
||||
struct ResourceSnapshot {
|
||||
struct IndirectImage {
|
||||
uint32_t resource = 0;
|
||||
uint32_t capacity = 0;
|
||||
std::vector<uint32_t> keys;
|
||||
std::vector<uint32_t> candidates;
|
||||
std::vector<DescriptorValue> descriptors;
|
||||
// Canonical module-affecting resource state. Runtime addresses and descriptor payloads remain in
|
||||
// ResourceSnapshot and therefore do not create shader permutations.
|
||||
struct ResourceSpecialization {
|
||||
struct Buffer {
|
||||
uint32_t packed_stride = 0;
|
||||
Prospero::BufferFormat descriptor_format = Prospero::BufferFormat::kInvalid;
|
||||
uint32_t descriptor_swizzle = DstSel(4, 5, 6, 7);
|
||||
bool operator==(const Buffer&) const = default;
|
||||
};
|
||||
|
||||
std::vector<DescriptorValue> buffers;
|
||||
std::vector<DescriptorValue> images;
|
||||
std::vector<DescriptorValue> samplers;
|
||||
std::vector<uint32_t> flattened_srt;
|
||||
std::vector<uint32_t> user_data;
|
||||
std::vector<IndirectImage> indirect_images;
|
||||
struct Image {
|
||||
Prospero::TextureNumericClass numeric_class = Prospero::TextureNumericClass::Unsupported;
|
||||
Decoder::ImageDimension dimension = Decoder::ImageDimension::Unknown;
|
||||
uint32_t mip_count = 1;
|
||||
Prospero::BufferFormat conversion_format = Prospero::BufferFormat::kInvalid;
|
||||
uint32_t shader_swizzle = ShaderImageIdentitySwizzle;
|
||||
uint32_t indirect_root = ImageResource::NoIndirectImage;
|
||||
uint32_t indirect_mapping_offset = 0;
|
||||
uint32_t indirect_search_iterations = 0;
|
||||
bool cube = false;
|
||||
bool operator==(const Image&) const = default;
|
||||
};
|
||||
|
||||
std::vector<Buffer> buffers;
|
||||
std::vector<Image> images;
|
||||
|
||||
bool operator==(const ResourceSpecialization&) const = default;
|
||||
};
|
||||
|
||||
bool ValidateResourceSnapshot(const Program& program, const ResourceSnapshot& snapshot);
|
||||
bool ValidateResourceSpecialization(const Program& program, const ResourceSnapshot& snapshot);
|
||||
// Extracts the descriptor/SRT value graph before resource specialization. The returned plan owns
|
||||
// its values and is independent of the translated shader CFG.
|
||||
ResourcePlan ExtractResourcePlan(const Program& program);
|
||||
|
||||
// Resolves the immutable dense resource topology against one runtime user-data/SRT snapshot.
|
||||
// On failure the destination is unchanged.
|
||||
bool MaterializeResources(const Program& program, const SrtRuntime& runtime,
|
||||
ResourceSnapshot& snapshot);
|
||||
// Resolves and specializes the immutable resource plan in one transaction. On failure both
|
||||
// destinations are unchanged.
|
||||
bool MaterializeResources(const ResourcePlan& program, const SrtRuntime& runtime,
|
||||
ResourceSnapshot& snapshot, ResourceSpecialization& specialization);
|
||||
|
||||
// Applies runtime descriptor shape/format facts to a copied dense topology before layout and
|
||||
// emission. Invalid specializations are fatal.
|
||||
void SpecializeResources(Program& program, ResourceSnapshot& snapshot);
|
||||
// Applies an already-derived specialization to native IR before layout and emission.
|
||||
void ApplyResourceSpecialization(Program& program, const ResourceSpecialization& specialization);
|
||||
|
||||
} // namespace Libs::Graphics::ShaderRecompiler::IR
|
||||
|
||||
|
||||
@@ -464,6 +464,16 @@ private:
|
||||
DescriptorSource descriptor;
|
||||
MakeSource(*handle, width, sampler, sample_adjust, descriptor, pc);
|
||||
uint32_t bad_dword = 0;
|
||||
if (expected == ValueOpcode::GetImageResource) {
|
||||
for (; bad_dword < descriptor.dword_count; bad_dword++) {
|
||||
const auto* value = descriptor.dwords[bad_dword].Resolve().TryInstruction();
|
||||
if (value != nullptr && value->GetOpcode() == ValueOpcode::ReadConstBuffer) {
|
||||
Fail(pc, fmt::format("{} dword {} is not a valid runtime value",
|
||||
ValueOpcodeName(expected), bad_dword));
|
||||
}
|
||||
}
|
||||
bad_dword = 0;
|
||||
}
|
||||
if (!ValidateSource(descriptor, bad_dword)) {
|
||||
Fail(pc, fmt::format("{} dword {} is not a valid runtime value",
|
||||
ValueOpcodeName(expected), bad_dword));
|
||||
|
||||
@@ -26,7 +26,7 @@ const char* StageName(ShaderType stage) {
|
||||
}
|
||||
}
|
||||
|
||||
std::string Diagnostic(const Program& program, uint32_t pc, const std::string& message) {
|
||||
std::string Diagnostic(const ResourcePlan& program, uint32_t pc, const std::string& message) {
|
||||
return fmt::format("shader SRT: hash=0x{:016x} stage={} pc=0x{:08x} {}", program.shader_hash,
|
||||
StageName(program.stage), pc, message);
|
||||
}
|
||||
@@ -51,7 +51,7 @@ bool AddSignedAddress(uint64_t base, int64_t offset, uint64_t& result) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool IsRawRead(const Program& values, const Inst& inst) {
|
||||
bool IsRawRead(const ResourcePlan& values, const Inst& inst) {
|
||||
const auto op = inst.GetOpcode();
|
||||
if (op != ValueOpcode::LoadAddressU32 && op != ValueOpcode::ReadConstBuffer) {
|
||||
return false;
|
||||
@@ -134,7 +134,7 @@ bool IsRuntimeUniformOp(ValueOpcode op) {
|
||||
|
||||
class RuntimeValidator {
|
||||
public:
|
||||
explicit RuntimeValidator(const Program& program, Value active_mask = {})
|
||||
explicit RuntimeValidator(const ResourcePlan& program, Value active_mask = {})
|
||||
: m_program(program), m_active_mask(active_mask.Resolve()) {}
|
||||
|
||||
bool Run(Value value) { return Validate(value); }
|
||||
@@ -262,7 +262,7 @@ private:
|
||||
return finish(true);
|
||||
}
|
||||
|
||||
const Program& m_program;
|
||||
const ResourcePlan& m_program;
|
||||
Value m_active_mask;
|
||||
std::unordered_set<const Inst*> m_visiting;
|
||||
};
|
||||
@@ -415,7 +415,7 @@ private:
|
||||
|
||||
class Evaluator {
|
||||
public:
|
||||
Evaluator(const Program& program, const SrtRuntime& runtime,
|
||||
Evaluator(const ResourcePlan& program, const SrtRuntime& runtime,
|
||||
std::span<const uint8_t> clean_flat_slots = {}, Evaluator* clean_evaluator = nullptr,
|
||||
Value active_mask = {})
|
||||
: m_program(program), m_runtime(runtime), m_clean_flat_slots(clean_flat_slots),
|
||||
@@ -435,9 +435,7 @@ private:
|
||||
return std::bit_cast<float>(static_cast<uint32_t>(bits));
|
||||
}
|
||||
|
||||
static uint64_t Float32Bits(float value) {
|
||||
return std::bit_cast<uint32_t>(value);
|
||||
}
|
||||
static uint64_t Float32Bits(float value) { return std::bit_cast<uint32_t>(value); }
|
||||
|
||||
bool EvaluateWide(Value value, uint64_t& result) {
|
||||
value = value.Resolve();
|
||||
@@ -456,6 +454,11 @@ private:
|
||||
if (inst == nullptr) {
|
||||
return false;
|
||||
}
|
||||
if (!m_reserved) {
|
||||
m_cache.reserve(m_program.value_storage.size());
|
||||
m_visiting.reserve(m_program.value_storage.size());
|
||||
m_reserved = true;
|
||||
}
|
||||
if (!m_active_mask.IsEmpty() && IsRuntimeSelect(inst->GetOpcode()) &&
|
||||
inst->NumArgs() == 3 && inst->Arg(0).Resolve() == m_active_mask) {
|
||||
return EvaluateWide(inst->Arg(1), result);
|
||||
@@ -915,24 +918,24 @@ private:
|
||||
return false;
|
||||
}
|
||||
|
||||
const Program& m_program;
|
||||
const ResourcePlan& m_program;
|
||||
const SrtRuntime& m_runtime;
|
||||
std::span<const uint8_t> m_clean_flat_slots;
|
||||
Evaluator* m_clean_evaluator = nullptr;
|
||||
Value m_active_mask;
|
||||
std::unordered_map<const Inst*, uint64_t> m_cache;
|
||||
std::vector<const Inst*> m_visiting;
|
||||
bool m_reserved = false;
|
||||
};
|
||||
|
||||
const DescriptorSource* Source(const Program& program, uint32_t source) {
|
||||
const DescriptorSource* Source(const ResourcePlan& program, uint32_t source) {
|
||||
if (source >= program.descriptor_sources.size()) {
|
||||
return nullptr;
|
||||
}
|
||||
return &program.descriptor_sources[source];
|
||||
}
|
||||
|
||||
bool EvaluateRuntimeSourcesImpl(const Program& program,
|
||||
std::span<const DescriptorSourceRequest> requests,
|
||||
bool EvaluateRuntimeSourcesImpl(const ResourcePlan& program, std::span<const uint32_t> sources,
|
||||
const SrtRuntime& runtime, std::vector<DescriptorValue>& results,
|
||||
std::vector<uint32_t>& flat, bool evaluate_flat,
|
||||
std::span<const uint8_t> clean_flat_slots) {
|
||||
@@ -948,9 +951,9 @@ bool EvaluateRuntimeSourcesImpl(const Program& program
|
||||
Evaluator clean_evaluator(program, clean_runtime);
|
||||
Evaluator evaluator(program, runtime, clean_flat_slots, &clean_evaluator);
|
||||
std::vector<DescriptorValue> evaluated;
|
||||
evaluated.reserve(requests.size());
|
||||
for (const auto& request: requests) {
|
||||
const auto* source = Source(program, request.source);
|
||||
evaluated.reserve(sources.size());
|
||||
for (const auto source_index: sources) {
|
||||
const auto* source = Source(program, source_index);
|
||||
if (source == nullptr) {
|
||||
return false;
|
||||
}
|
||||
@@ -985,7 +988,7 @@ bool EvaluateRuntimeSourcesImpl(const Program& program
|
||||
|
||||
} // namespace
|
||||
|
||||
bool ValidateRuntimeValue(const Program& program, Value value) {
|
||||
bool ValidateRuntimeValue(const ResourcePlan& program, Value value) {
|
||||
return RuntimeValidator(program).Run(value);
|
||||
}
|
||||
|
||||
@@ -998,34 +1001,32 @@ void BuildSrtPlan(Program& program) {
|
||||
program.srt_plan_complete = true;
|
||||
}
|
||||
|
||||
bool EvaluateDescriptorSource(const Program& program, uint32_t source, const SrtRuntime& runtime,
|
||||
DescriptorValue& result) {
|
||||
const DescriptorSourceRequest request {source};
|
||||
std::vector<DescriptorValue> results;
|
||||
if (!EvaluateDescriptorSources(program, std::span {&request, 1}, runtime, results)) {
|
||||
bool EvaluateDescriptorSource(const ResourcePlan& program, uint32_t source,
|
||||
const SrtRuntime& runtime, DescriptorValue& result) {
|
||||
std::vector<DescriptorValue> results;
|
||||
if (!EvaluateDescriptorSources(program, std::span {&source, 1}, runtime, results)) {
|
||||
return false;
|
||||
}
|
||||
result = results.front();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool EvaluateDescriptorSources(const Program& program,
|
||||
std::span<const DescriptorSourceRequest> requests,
|
||||
bool EvaluateDescriptorSources(const ResourcePlan& program, std::span<const uint32_t> sources,
|
||||
const SrtRuntime& runtime, std::vector<DescriptorValue>& results) {
|
||||
std::vector<uint32_t> ignored;
|
||||
return EvaluateRuntimeSourcesImpl(program, requests, runtime, results, ignored, false, {});
|
||||
return EvaluateRuntimeSourcesImpl(program, sources, runtime, results, ignored, false, {});
|
||||
}
|
||||
|
||||
bool EvaluateRuntimeSources(const Program& program,
|
||||
std::span<const DescriptorSourceRequest> requests,
|
||||
bool EvaluateRuntimeSources(const ResourcePlan& program, std::span<const uint32_t> sources,
|
||||
const SrtRuntime& runtime, std::vector<DescriptorValue>& results,
|
||||
std::vector<uint32_t>& flat,
|
||||
std::span<const uint8_t> clean_flat_slots) {
|
||||
return EvaluateRuntimeSourcesImpl(program, requests, runtime, results, flat, true,
|
||||
return EvaluateRuntimeSourcesImpl(program, sources, runtime, results, flat, true,
|
||||
clean_flat_slots);
|
||||
}
|
||||
|
||||
bool WalkSrt(const Program& program, const SrtRuntime& runtime, std::vector<uint32_t>& flat) {
|
||||
bool WalkSrt(const ResourcePlan& program, const SrtRuntime& runtime,
|
||||
std::vector<uint32_t>& flat) {
|
||||
std::vector<DescriptorValue> ignored;
|
||||
return EvaluateRuntimeSources(program, {}, runtime, ignored, flat, {});
|
||||
}
|
||||
|
||||
@@ -19,32 +19,27 @@ struct SrtRuntime {
|
||||
SrtMemoryReader read_specialization_memory = nullptr;
|
||||
};
|
||||
|
||||
struct DescriptorSourceRequest {
|
||||
uint32_t source = 0;
|
||||
};
|
||||
|
||||
// Collects reachable ReadConst values. Immediate offsets receive compact flat-buffer slots;
|
||||
// dynamic offsets remain explicit and are never assigned a fake slot.
|
||||
void BuildSrtPlan(Program& program);
|
||||
bool ValidateRuntimeValue(const Program& program, Value value);
|
||||
bool ValidateRuntimeValue(const ResourcePlan& program, Value value);
|
||||
|
||||
bool EvaluateDescriptorSource(const Program& program, uint32_t source, const SrtRuntime& runtime,
|
||||
DescriptorValue& result);
|
||||
bool EvaluateDescriptorSource(const ResourcePlan& program, uint32_t source,
|
||||
const SrtRuntime& runtime, DescriptorValue& result);
|
||||
|
||||
// Evaluates one runtime snapshot transactionally. Scalar values and ReadConst results shared by
|
||||
// several descriptors are memoized once across the batch.
|
||||
bool EvaluateDescriptorSources(const Program& program,
|
||||
std::span<const DescriptorSourceRequest> requests,
|
||||
bool EvaluateDescriptorSources(const ResourcePlan& program, std::span<const uint32_t> sources,
|
||||
const SrtRuntime& runtime, std::vector<DescriptorValue>& results);
|
||||
|
||||
// Evaluates descriptor sources and the flattened immediate SRT with one memoized scalar walk.
|
||||
// On failure neither destination is changed.
|
||||
bool EvaluateRuntimeSources(const Program& program,
|
||||
std::span<const DescriptorSourceRequest> requests,
|
||||
bool EvaluateRuntimeSources(const ResourcePlan& program, std::span<const uint32_t> sources,
|
||||
const SrtRuntime& runtime, std::vector<DescriptorValue>& results,
|
||||
std::vector<uint32_t>& flat, std::span<const uint8_t> clean_flat_slots);
|
||||
|
||||
bool WalkSrt(const Program& program, const SrtRuntime& runtime, std::vector<uint32_t>& flat);
|
||||
bool WalkSrt(const ResourcePlan& program, const SrtRuntime& runtime,
|
||||
std::vector<uint32_t>& flat);
|
||||
|
||||
} // namespace Libs::Graphics::ShaderRecompiler::IR
|
||||
|
||||
|
||||
@@ -48,7 +48,7 @@ std::vector<Parameter> GetParameters(const ShaderVertexInputInfo& vertex_info,
|
||||
std::vector<Parameter> parameters;
|
||||
for (const auto input: active_inputs) {
|
||||
const auto input_location = ShaderPixelParameterMappedLocation(*pixel_info, input);
|
||||
if ((vertex_info.param_export_mask & (1u << input_location)) != 0) {
|
||||
if ((vertex_info.stage.program->param_export_mask & (1u << input_location)) != 0) {
|
||||
parameters.push_back({input_location,
|
||||
ShaderPixelParameterLocation(*pixel_info, active_inputs, input),
|
||||
ShaderPixelParameterIsFlat(*pixel_info, input)});
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
#include "common/assert.h"
|
||||
#include "common/common.h"
|
||||
#include "common/emulatorConfig.h"
|
||||
#include "common/file.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/magicEnum.h"
|
||||
#include "common/profiler.h"
|
||||
@@ -12,23 +11,15 @@
|
||||
#include "graphics/guest_gpu/graphicsRun.h"
|
||||
#include "graphics/guest_gpu/hardwareContext.h"
|
||||
#include "graphics/host_gpu/renderer/renderContext.h"
|
||||
#include "graphics/shader/recompiler/ShaderRecompiler.h"
|
||||
#include "graphics/shader/recompiler/frontend/decode/ShaderDecoder.h"
|
||||
#include "graphics/shader/recompiler/ir/ShaderIR.h"
|
||||
#include "graphics/shader/shaderCompiler.h"
|
||||
#include "graphics/shader/shaderVertexMetadata.h"
|
||||
#include "kernel/memory.h"
|
||||
#include "libs/errno.h"
|
||||
#include "spirv-tools/libspirv.h"
|
||||
#include "spirv-tools/libspirv.hpp"
|
||||
#include "spirv-tools/optimizer.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
#include <bit>
|
||||
#include <cstdio>
|
||||
#include <filesystem>
|
||||
#include <fmt/format.h>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <span>
|
||||
@@ -49,15 +40,6 @@
|
||||
|
||||
namespace Libs::Graphics {
|
||||
|
||||
namespace {
|
||||
|
||||
bool ReadShaderGuestMemory(void*, uint64_t address, uint32_t* value) {
|
||||
return value != nullptr &&
|
||||
Libs::LibKernel::Memory::TryReadGpuCleanBacking(address, value, sizeof(*value));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
struct ShaderBinaryInfo {
|
||||
uint8_t signature[7];
|
||||
uint8_t version;
|
||||
@@ -107,62 +89,6 @@ static ShaderMappedData ShaderGetMappedData(uint64_t addr, const char* label) {
|
||||
EXIT("%s shader=0x%016" PRIx64 " is missing from ShaderMap\n", label, addr);
|
||||
}
|
||||
|
||||
static bool SpirvDisassemble(const uint32_t* src_binary, size_t src_binary_size,
|
||||
std::string* dst_disassembly) {
|
||||
if (dst_disassembly != nullptr) {
|
||||
spvtools::SpirvTools core(SPV_ENV_VULKAN_1_2);
|
||||
|
||||
std::string disassembly;
|
||||
if (!core.Disassemble(src_binary, src_binary_size, &disassembly,
|
||||
static_cast<uint32_t>(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER) |
|
||||
static_cast<uint32_t>(SPV_BINARY_TO_TEXT_OPTION_FRIENDLY_NAMES) |
|
||||
static_cast<uint32_t>(SPV_BINARY_TO_TEXT_OPTION_COMMENT) |
|
||||
static_cast<uint32_t>(SPV_BINARY_TO_TEXT_OPTION_INDENT) |
|
||||
static_cast<uint32_t>(SPV_BINARY_TO_TEXT_OPTION_COLOR))) {
|
||||
*dst_disassembly = disassembly.c_str();
|
||||
|
||||
LOGF("Disassemble failed\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
*dst_disassembly = disassembly.c_str();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool SpirvValidateBinary(const char* label, uint64_t shader_hash,
|
||||
const std::vector<uint32_t>& spirv) {
|
||||
if (!Config::ShaderValidationEnabled()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
spvtools::SpirvTools core(SPV_ENV_VULKAN_1_3);
|
||||
std::string messages;
|
||||
core.SetMessageConsumer([&messages](spv_message_level_t /*level*/, const char* /*source*/,
|
||||
const spv_position_t& position, const char* message) {
|
||||
messages += fmt::format("{}: {} ({}) {}\n", static_cast<int>(position.line),
|
||||
static_cast<int>(position.column), static_cast<int>(position.index),
|
||||
message);
|
||||
});
|
||||
|
||||
if (core.Validate(spirv)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string disassembly;
|
||||
SpirvDisassemble(spirv.data(), spirv.size(), &disassembly);
|
||||
LOGF_COLOR(Log::Color::BrightRed, "%s SPIR-V validation failed hash=0x%016" PRIx64 ":\n%s",
|
||||
label, shader_hash, messages.c_str());
|
||||
LOGF("%s\n", disassembly.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
static void ExitShaderRecompilerFailure(const char* label, uint64_t shader_hash,
|
||||
const char* reason) {
|
||||
EXIT("%s failed hash=0x%016" PRIx64 ": %s\n", label, shader_hash,
|
||||
reason != nullptr ? reason : "");
|
||||
}
|
||||
|
||||
static const ShaderBinaryInfo* GetBinaryInfo(const uint32_t* code) {
|
||||
EXIT_IF(code == nullptr);
|
||||
|
||||
@@ -695,7 +621,6 @@ static bool ShaderGetStaticInputInfoVS(const HW::VertexShaderInfo& regs,
|
||||
|
||||
info = {};
|
||||
|
||||
info.export_count = static_cast<int>(sh.GetExportCount());
|
||||
info.pa_cl_vs_out_cntl = sh.m_paClVsOutCntl;
|
||||
|
||||
EXIT_NOT_IMPLEMENTED(regs.es_regs.data_addr == 0);
|
||||
@@ -746,18 +671,12 @@ static bool ShaderGetStaticInputInfoVS(const HW::VertexShaderInfo& regs,
|
||||
|
||||
static void ShaderGetStaticInputInfoPS(
|
||||
const HW::PixelShaderInfo& regs, const HW::ShaderRegisters& sh,
|
||||
const ShaderVertexInputInfo& vs_info,
|
||||
std::span<const Prospero::ColorComponentMapping, 8> target_export_mapping,
|
||||
const ShaderMappedData& data, ShaderPixelInputInfo& ps_info) {
|
||||
KYTY_PROFILER_FUNCTION();
|
||||
|
||||
ps_info = {};
|
||||
ps_info.scratch_size_dwords = data.scratch_size_dwords;
|
||||
ps_info.push_constant_offset =
|
||||
vs_info.stage.program != nullptr
|
||||
? vs_info.stage.program->bindings.push_constant_offset +
|
||||
vs_info.stage.program->bindings.push_constant_size
|
||||
: 0;
|
||||
|
||||
// SPI_PS_IN_CONTROL.NUM_INTERP occupies bits 5:0. Keep the remaining control
|
||||
// flags in the hardware state and extract only the input count here.
|
||||
@@ -801,7 +720,6 @@ static void ShaderGetStaticInputInfoPS(
|
||||
? target_export_mapping[i]
|
||||
: Prospero::ColorComponentMapping {};
|
||||
}
|
||||
ps_info.mrt_output_mask = 0;
|
||||
}
|
||||
|
||||
static void ShaderGetStaticInputInfoCS(const HW::ComputeShaderInfo& regs,
|
||||
@@ -828,73 +746,12 @@ static void ShaderGetStaticInputInfoCS(const HW::ComputeShaderInfo& regs,
|
||||
info.workgroup_register = regs.cs_regs.user_sgpr;
|
||||
}
|
||||
|
||||
static void ApplyVertexOutputs(ShaderVertexInputInfo& info,
|
||||
const ShaderRecompiler::IR::Program& program) {
|
||||
info.export_count = 0;
|
||||
info.param_export_mask = 0;
|
||||
for (const auto& output: program.info.outputs) {
|
||||
if (output.kind == ShaderRecompiler::IR::StageOutputKind::Parameter && output.index < 32) {
|
||||
info.param_export_mask |= 1u << output.index;
|
||||
info.export_count = std::max(info.export_count, static_cast<int>(output.index + 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void ApplyPixelOutputs(ShaderPixelInputInfo& info,
|
||||
const ShaderRecompiler::IR::Program& program) {
|
||||
info.mrt_output_mask = 0;
|
||||
for (const auto& output: program.info.outputs) {
|
||||
if (output.kind == ShaderRecompiler::IR::StageOutputKind::Mrt && output.index < 8) {
|
||||
info.mrt_output_mask |= 1u << output.index;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static bool LogPermutationMismatch(const ShaderRecompiler::IR::Program* program,
|
||||
const char* stage) {
|
||||
static std::atomic<uint32_t> log_count {0};
|
||||
if (Config::GraphicsDebugDumpEnabled() &&
|
||||
log_count.fetch_add(1, std::memory_order_relaxed) < 64) {
|
||||
LOGF("ShaderProgramCache native runtime mismatch %s shader=0x%016" PRIx64 "\n",
|
||||
stage, program != nullptr ? program->shader_hash : 0);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool MaterializeProgram(const std::shared_ptr<const ShaderRecompiler::IR::Program>& program,
|
||||
const ShaderParams& params, ShaderVertexInputInfo& info) {
|
||||
if (!ShaderMaterializeStageRuntime(program, params.user_data, params.Base(), info.stage,
|
||||
ReadShaderGuestMemory)) {
|
||||
return LogPermutationMismatch(program.get(), "VS");
|
||||
}
|
||||
ApplyVertexOutputs(info, *program);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MaterializeProgram(const std::shared_ptr<const ShaderRecompiler::IR::Program>& program,
|
||||
const ShaderParams& params, ShaderPixelInputInfo& info) {
|
||||
if (!ShaderMaterializeStageRuntime(program, params.user_data, params.Base(), info.stage,
|
||||
ReadShaderGuestMemory)) {
|
||||
return LogPermutationMismatch(program.get(), "PS");
|
||||
}
|
||||
ApplyPixelOutputs(info, *program);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MaterializeProgram(const std::shared_ptr<const ShaderRecompiler::IR::Program>& program,
|
||||
const ShaderParams& params, ShaderComputeInputInfo& info) {
|
||||
if (!ShaderMaterializeStageRuntime(program, params.user_data, params.Base(), info.stage,
|
||||
ReadShaderGuestMemory)) {
|
||||
return LogPermutationMismatch(program.get(), "CS");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void BuildStageStaticKey(const ShaderVertexInputInfo& info, std::vector<uint32_t>& key) {
|
||||
EXIT_IF(info.resources_num < 0 || info.resources_num > ShaderVertexInputInfo::RES_MAX ||
|
||||
info.buffers_num < 0 || info.buffers_num > ShaderVertexInputInfo::RES_MAX);
|
||||
EXIT_IF(info.resources_num < 0 || info.resources_num > ShaderVertexInputInfo::RES_MAX);
|
||||
key.clear();
|
||||
key.push_back(static_cast<uint32_t>(info.fetch_embedded));
|
||||
key.push_back(static_cast<uint32_t>(info.fetch_attrib_reg));
|
||||
key.push_back(static_cast<uint32_t>(info.fetch_buffer_reg));
|
||||
key.push_back(info.resources_num);
|
||||
key.push_back(info.scratch_size_dwords);
|
||||
key.push_back(info.pa_cl_vs_out_cntl);
|
||||
@@ -917,6 +774,7 @@ void BuildStageStaticKey(const ShaderVertexInputInfo& info, std::vector<uint32_t
|
||||
key.push_back(destination.register_start);
|
||||
key.push_back(destination.registers_num);
|
||||
key.push_back(destination.fetch_index);
|
||||
key.push_back(static_cast<uint32_t>(destination.attr_id));
|
||||
key.push_back(resource.Stride());
|
||||
key.push_back(static_cast<uint32_t>(resource.SwizzleEnabled()));
|
||||
key.push_back(resource.DstSelX());
|
||||
@@ -927,28 +785,11 @@ void BuildStageStaticKey(const ShaderVertexInputInfo& info, std::vector<uint32_t
|
||||
key.push_back(resource.OutOfBounds());
|
||||
key.push_back(static_cast<uint32_t>(resource.AddTid()));
|
||||
}
|
||||
|
||||
key.push_back(info.buffers_num);
|
||||
int attributes_num = 0;
|
||||
for (int i = 0; i < info.buffers_num; i++) {
|
||||
const auto& buffer = info.buffers[i];
|
||||
EXIT_IF(buffer.attr_num < 0 || buffer.attr_num > ShaderVertexInputBuffer::ATTR_MAX);
|
||||
attributes_num += buffer.attr_num;
|
||||
EXIT_IF(attributes_num > ShaderVertexInputInfo::RES_MAX);
|
||||
key.push_back(buffer.attr_num);
|
||||
key.push_back(buffer.stride);
|
||||
key.push_back(buffer.fetch_index);
|
||||
for (int j = 0; j < buffer.attr_num; j++) {
|
||||
key.push_back(buffer.attr_indices[j]);
|
||||
key.push_back(buffer.attr_offsets[j]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void BuildStageStaticKey(const ShaderPixelInputInfo& info, std::vector<uint32_t>& key) {
|
||||
EXIT_IF(info.input_num > std::size(info.interpolator_settings));
|
||||
key.clear();
|
||||
key.push_back(info.push_constant_offset);
|
||||
key.push_back(info.scratch_size_dwords);
|
||||
key.push_back(info.input_num);
|
||||
key.push_back(info.ps_system_input_base);
|
||||
@@ -1006,11 +847,10 @@ ShaderParams PrepareProgram(const HW::VertexShaderInfo& regs, const HW::ShaderRe
|
||||
|
||||
ShaderParams PrepareProgram(
|
||||
const HW::PixelShaderInfo& regs, const HW::ShaderRegisters& sh,
|
||||
const ShaderVertexInputInfo& vs_info,
|
||||
std::span<const Prospero::ColorComponentMapping, 8> target_export_mapping,
|
||||
ShaderPixelInputInfo& ps_info) {
|
||||
const auto data = ShaderGetMappedData(regs.ps_regs.data_addr, "ShaderGetInputInfoPS():");
|
||||
ShaderGetStaticInputInfoPS(regs, sh, vs_info, target_export_mapping, data, ps_info);
|
||||
ShaderGetStaticInputInfoPS(regs, sh, target_export_mapping, data, ps_info);
|
||||
return GetShaderParams(
|
||||
regs.ps_regs.data_addr, "ShaderRecompiler PS", GetDeclaredShaderHash(regs.ps_regs.data_addr),
|
||||
std::span<const uint32_t>(regs.ps_user_sgpr.value, regs.ps_regs.rsrc2.user_sgpr), data);
|
||||
@@ -1032,11 +872,8 @@ void ShaderDbgDumpInputInfo(const ShaderVertexInputInfo& info) {
|
||||
LOGF("ShaderDbgDumpInputInfo()\n");
|
||||
|
||||
LOGF("\t fetch_external = %s\n"
|
||||
"\t fetch_embedded = %s\n"
|
||||
"\t param_export_mask = 0x%08" PRIx32 "\n"
|
||||
"\t export_count = %d\n",
|
||||
info.fetch_external ? "true" : "false", info.fetch_embedded ? "true" : "false",
|
||||
info.param_export_mask, info.export_count);
|
||||
"\t fetch_embedded = %s\n",
|
||||
info.fetch_external ? "true" : "false", info.fetch_embedded ? "true" : "false");
|
||||
|
||||
for (int i = 0; i < info.resources_num; i++) {
|
||||
LOGF("\t input %d\n", i);
|
||||
@@ -1134,205 +971,6 @@ void ShaderDbgDumpInputInfo(const ShaderComputeInputInfo& info) {
|
||||
info.group_id[1] ? "true" : "false", info.group_id[2] ? "true" : "false");
|
||||
}
|
||||
|
||||
static bool ShaderRecompilerTextDumpEnabled() {
|
||||
// Graphics debug dump already writes SPIR-V binaries to _Shaders. Keep the very large
|
||||
// disassembly/IR text behind the shader-log switch so file logging cannot stall boot.
|
||||
return Config::GetShaderLogDirection() != Config::ShaderLogDirection::Silent;
|
||||
}
|
||||
|
||||
static void DumpShaderRecompilerSpirv(const char* type, uint64_t shader_hash,
|
||||
const std::vector<uint32_t>& bin) {
|
||||
if (!Config::GraphicsDebugDumpEnabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
static std::atomic_int id = 0;
|
||||
|
||||
const auto base_name = Config::GetShaderLogFolder() /
|
||||
fmt::format("{:04d}_new_shader_{}_{:016x}", id++, type, shader_hash);
|
||||
Common::File::CreateDirectories(base_name.parent_path());
|
||||
|
||||
Common::File spv_file;
|
||||
auto spv_name = base_name;
|
||||
spv_name += ".spv";
|
||||
spv_file.Create(spv_name);
|
||||
if (spv_file.IsInvalid()) {
|
||||
auto spv_name_text = Common::PathToString(spv_name);
|
||||
LOGF_COLOR(Log::Color::BrightRed, "Can't create file: %s\n", spv_name_text.c_str());
|
||||
} else {
|
||||
spv_file.Write(bin.data(), bin.size() * 4);
|
||||
spv_file.Close();
|
||||
}
|
||||
|
||||
return;
|
||||
|
||||
std::string text;
|
||||
if (!SpirvDisassemble(bin.data(), bin.size(), &text)) {
|
||||
auto spv_name_text = Common::PathToString(spv_name);
|
||||
LOGF_COLOR(Log::Color::BrightRed, "SpirvDisassemble() failed for %s\n",
|
||||
spv_name_text.c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
Common::File asm_file;
|
||||
auto asm_name = base_name;
|
||||
asm_name += ".spvasm";
|
||||
asm_file.Create(asm_name);
|
||||
if (asm_file.IsInvalid()) {
|
||||
auto asm_name_text = Common::PathToString(asm_name);
|
||||
LOGF_COLOR(Log::Color::BrightRed, "Can't create file: %s\n", asm_name_text.c_str());
|
||||
} else {
|
||||
asm_file.Printf("%s", text.c_str());
|
||||
asm_file.Close();
|
||||
}
|
||||
}
|
||||
|
||||
static void DumpShaderRecompilerOriginal(const char* type, uint64_t shader_hash,
|
||||
std::span<const uint32_t> code,
|
||||
const std::string& decoded_dump) {
|
||||
if (!Config::GraphicsDebugDumpEnabled()) {
|
||||
return;
|
||||
}
|
||||
EXIT_IF(code.empty());
|
||||
|
||||
static std::atomic_int id = 0;
|
||||
|
||||
const auto base_name = Config::GetShaderLogFolder() / "original" /
|
||||
fmt::format("{:04d}_new_shader_{}_{:016x}", id++, type, shader_hash);
|
||||
Common::File::CreateDirectories(base_name.parent_path());
|
||||
|
||||
Common::File bin_file;
|
||||
auto bin_name = base_name;
|
||||
bin_name += ".bin";
|
||||
bin_file.Create(bin_name);
|
||||
if (bin_file.IsInvalid()) {
|
||||
auto bin_name_text = Common::PathToString(bin_name);
|
||||
LOGF_COLOR(Log::Color::BrightRed, "Can't create file: %s\n", bin_name_text.c_str());
|
||||
} else {
|
||||
bin_file.Write(code.data(), code.size_bytes());
|
||||
bin_file.Close();
|
||||
}
|
||||
if (decoded_dump.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Common::File text_file;
|
||||
auto text_name = base_name;
|
||||
text_name += ".rdna2";
|
||||
text_file.Create(text_name);
|
||||
if (text_file.IsInvalid()) {
|
||||
auto text_name_text = Common::PathToString(text_name);
|
||||
LOGF_COLOR(Log::Color::BrightRed, "Can't create file: %s\n", text_name_text.c_str());
|
||||
} else {
|
||||
text_file.Printf("%s", decoded_dump.c_str());
|
||||
text_file.Close();
|
||||
}
|
||||
}
|
||||
|
||||
static const char* ShaderStageName(ShaderType stage) {
|
||||
switch (stage) {
|
||||
case ShaderType::Vertex: return "vs";
|
||||
case ShaderType::Pixel: return "ps";
|
||||
case ShaderType::Compute: return "cs";
|
||||
default: EXIT("invalid shader stage\n");
|
||||
}
|
||||
}
|
||||
|
||||
static const char* ShaderStageLabel(ShaderType stage) {
|
||||
switch (stage) {
|
||||
case ShaderType::Vertex: return "ShaderRecompiler VS";
|
||||
case ShaderType::Pixel: return "ShaderRecompiler PS";
|
||||
case ShaderType::Compute: return "ShaderRecompiler CS";
|
||||
default: EXIT("invalid shader stage\n");
|
||||
}
|
||||
}
|
||||
|
||||
static ShaderRecompiler::CompileOptions MakeCompileOptions(const ShaderParams& params,
|
||||
ShaderType stage) {
|
||||
ShaderRecompiler::CompileOptions options;
|
||||
options.stage = stage;
|
||||
options.shader_hash = params.hash;
|
||||
options.user_data = params.user_data;
|
||||
options.read_specialization_memory = ReadShaderGuestMemory;
|
||||
options.dump_ir = ShaderRecompilerTextDumpEnabled();
|
||||
options.early_dump = options.dump_ir;
|
||||
options.dump_label = ShaderStageLabel(stage);
|
||||
return options;
|
||||
}
|
||||
|
||||
static vk::ShaderModule CompileModule(vk::Device device, const ShaderParams& params,
|
||||
ShaderRecompiler::CompileOptions options,
|
||||
ShaderStageRuntime& stage) {
|
||||
const auto* stage_name = ShaderStageName(options.stage);
|
||||
const auto* label = ShaderStageLabel(options.stage);
|
||||
auto result = ShaderRecompiler::Recompile(params.code, options);
|
||||
|
||||
DumpShaderRecompilerOriginal(stage_name, options.shader_hash, params.code, result.decoded_dump);
|
||||
if (!SpirvValidateBinary(label, options.shader_hash, result.spirv)) {
|
||||
DumpShaderRecompilerSpirv(stage_name, options.shader_hash, result.spirv);
|
||||
ExitShaderRecompilerFailure(label, options.shader_hash, "SPIR-V validation failed");
|
||||
}
|
||||
DumpShaderRecompilerSpirv(stage_name, options.shader_hash, result.spirv);
|
||||
|
||||
stage.program =
|
||||
std::make_shared<const ShaderRecompiler::IR::Program>(std::move(result.program));
|
||||
stage.resources =
|
||||
std::make_shared<const ShaderRecompiler::IR::ResourceSnapshot>(std::move(result.resources));
|
||||
|
||||
vk::ShaderModuleCreateInfo create_info {};
|
||||
create_info.sType = vk::StructureType::eShaderModuleCreateInfo;
|
||||
create_info.codeSize = result.spirv.size() * sizeof(uint32_t);
|
||||
create_info.pCode = result.spirv.data();
|
||||
vk::ShaderModule module = nullptr;
|
||||
RequireVulkanSuccess(device.createShaderModule(&create_info, nullptr, &module),
|
||||
"create recompiled shader module");
|
||||
EXIT_IF(module == nullptr);
|
||||
|
||||
if (options.dump_ir) {
|
||||
if (!options.early_dump) {
|
||||
LOGF("%s decoded RDNA2:\n%s", label, result.decoded_dump.c_str());
|
||||
LOGF("%s IR:\n%s", label, result.ir_dump.c_str());
|
||||
}
|
||||
LOGF("%s SPIR-V words=%" PRIu64 " wave_size=%u\n", label,
|
||||
static_cast<uint64_t>(result.spirv.size()), options.wave_size);
|
||||
}
|
||||
return module;
|
||||
}
|
||||
|
||||
vk::ShaderModule CompileProgram(vk::Device device, const ShaderParams& params,
|
||||
ShaderVertexInputInfo& input_info) {
|
||||
KYTY_PROFILER_FUNCTION(profiler::colors::Amber300);
|
||||
auto options = MakeCompileOptions(params, ShaderType::Vertex);
|
||||
options.user_data_base = 8;
|
||||
options.scratch_dwords = input_info.scratch_size_dwords;
|
||||
options.input_info.vertex = &input_info;
|
||||
const auto module = CompileModule(device, params, options, input_info.stage);
|
||||
ApplyVertexOutputs(input_info, *input_info.stage.program);
|
||||
return module;
|
||||
}
|
||||
|
||||
vk::ShaderModule CompileProgram(vk::Device device, const ShaderParams& params,
|
||||
ShaderPixelInputInfo& input_info) {
|
||||
KYTY_PROFILER_FUNCTION(profiler::colors::Blue300);
|
||||
auto options = MakeCompileOptions(params, ShaderType::Pixel);
|
||||
options.scratch_dwords = input_info.scratch_size_dwords;
|
||||
options.push_constant_offset = input_info.push_constant_offset;
|
||||
options.input_info.pixel = &input_info;
|
||||
const auto module = CompileModule(device, params, options, input_info.stage);
|
||||
ApplyPixelOutputs(input_info, *input_info.stage.program);
|
||||
return module;
|
||||
}
|
||||
|
||||
vk::ShaderModule CompileProgram(vk::Device device, const ShaderParams& params,
|
||||
ShaderComputeInputInfo& input_info) {
|
||||
KYTY_PROFILER_FUNCTION(profiler::colors::CyanA700);
|
||||
auto options = MakeCompileOptions(params, ShaderType::Compute);
|
||||
options.scratch_dwords = input_info.scratch_size_dwords;
|
||||
options.wave_size = input_info.wave_size;
|
||||
options.input_info.compute = &input_info;
|
||||
return CompileModule(device, params, options, input_info.stage);
|
||||
}
|
||||
|
||||
bool ShaderAddressValid(uint64_t addr) {
|
||||
return reinterpret_cast<const uint32_t*>(addr) != nullptr;
|
||||
}
|
||||
|
||||
@@ -4,10 +4,10 @@
|
||||
#include "common/abi.h"
|
||||
#include "common/common.h"
|
||||
#include "graphics/guest_gpu/gpu_defs.h"
|
||||
#include "graphics/shader/recompiler/ir/ResourceSnapshot.h"
|
||||
#include "graphics/shader/shaderBindings.h"
|
||||
|
||||
#include <array>
|
||||
#include <memory>
|
||||
#include <span>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
@@ -36,30 +36,18 @@ struct ShaderRegisters;
|
||||
enum class ShaderType { Unknown, Vertex, Pixel, Fetch, Compute };
|
||||
|
||||
namespace ShaderRecompiler::IR {
|
||||
struct Program;
|
||||
struct ResourceSnapshot;
|
||||
struct CompiledShaderInfo;
|
||||
} // namespace ShaderRecompiler::IR
|
||||
|
||||
struct ShaderStageRuntime {
|
||||
std::shared_ptr<const ShaderRecompiler::IR::Program> program;
|
||||
std::shared_ptr<const ShaderRecompiler::IR::ResourceSnapshot> resources;
|
||||
const ShaderRecompiler::IR::CompiledShaderInfo* program = nullptr;
|
||||
ShaderRecompiler::IR::ResourceSnapshot resources;
|
||||
|
||||
[[nodiscard]] explicit operator bool() const {
|
||||
return program != nullptr && resources != nullptr;
|
||||
return program != nullptr;
|
||||
}
|
||||
};
|
||||
|
||||
using ShaderSpecializationMemoryReader = bool (*)(void* userdata, uint64_t address,
|
||||
uint32_t* value);
|
||||
|
||||
// Resolves an immutable native shader plan against current user data. The prior stage is preserved
|
||||
// if any ReadConst, snapshot, or specialization check fails.
|
||||
bool ShaderMaterializeStageRuntime(
|
||||
std::shared_ptr<const ShaderRecompiler::IR::Program> program,
|
||||
std::span<const uint32_t> user_data, uint64_t shader_base, ShaderStageRuntime& stage,
|
||||
ShaderSpecializationMemoryReader read_specialization_memory = nullptr,
|
||||
void* read_memory_data = nullptr);
|
||||
|
||||
constexpr uint32_t DstSel(uint32_t x, uint32_t y = 0, uint32_t z = 0, uint32_t w = 0) {
|
||||
return x | (y << 3u) | (z << 6u) | (w << 9u);
|
||||
}
|
||||
@@ -87,9 +75,7 @@ struct ShaderVertexInputInfo {
|
||||
int fetch_attrib_reg = 0;
|
||||
int fetch_buffer_reg = 0;
|
||||
int buffers_num = 0;
|
||||
int export_count = 0;
|
||||
uint32_t scratch_size_dwords = 0;
|
||||
uint32_t param_export_mask = 0;
|
||||
uint32_t pa_cl_vs_out_cntl = 0;
|
||||
ShaderClipSpaceTransform clip_space;
|
||||
bool fetch_external = false;
|
||||
@@ -119,8 +105,6 @@ struct ShaderPixelInputInfo {
|
||||
uint32_t ps_perspective_center_vgpr = UINT32_MAX;
|
||||
uint8_t target_output_mode[8] = {};
|
||||
std::array<Prospero::ColorComponentMapping, 8> target_export_mapping = {};
|
||||
uint32_t mrt_output_mask = 0;
|
||||
uint32_t push_constant_offset = 0;
|
||||
uint32_t scratch_size_dwords = 0;
|
||||
bool ps_pos_x = false;
|
||||
bool ps_pos_y = false;
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
#ifndef EMULATOR_INCLUDE_EMULATOR_GRAPHICS_SHADER_SHADERCOMPILER_H_
|
||||
#define EMULATOR_INCLUDE_EMULATOR_GRAPHICS_SHADER_SHADERCOMPILER_H_
|
||||
|
||||
#include "graphics/host_gpu/vulkanCommon.h"
|
||||
#include "graphics/shader/shader.h"
|
||||
|
||||
#include <memory>
|
||||
#include <span>
|
||||
#include <vector>
|
||||
|
||||
@@ -28,26 +26,11 @@ ShaderParams PrepareProgram(const HW::VertexShaderInfo& regs, const HW::ShaderRe
|
||||
ShaderVertexInputInfo& input_info);
|
||||
ShaderParams PrepareProgram(
|
||||
const HW::PixelShaderInfo& regs, const HW::ShaderRegisters& sh,
|
||||
const ShaderVertexInputInfo& vertex_info,
|
||||
std::span<const Prospero::ColorComponentMapping, 8> target_export_mapping,
|
||||
ShaderPixelInputInfo& input_info);
|
||||
ShaderParams PrepareProgram(const HW::ComputeShaderInfo& regs, const HW::ShaderRegisters& sh,
|
||||
ShaderComputeInputInfo& input_info);
|
||||
|
||||
bool MaterializeProgram(const std::shared_ptr<const ShaderRecompiler::IR::Program>& program,
|
||||
const ShaderParams& params, ShaderVertexInputInfo& input_info);
|
||||
bool MaterializeProgram(const std::shared_ptr<const ShaderRecompiler::IR::Program>& program,
|
||||
const ShaderParams& params, ShaderPixelInputInfo& input_info);
|
||||
bool MaterializeProgram(const std::shared_ptr<const ShaderRecompiler::IR::Program>& program,
|
||||
const ShaderParams& params, ShaderComputeInputInfo& input_info);
|
||||
|
||||
vk::ShaderModule CompileProgram(vk::Device device, const ShaderParams& params,
|
||||
ShaderVertexInputInfo& input_info);
|
||||
vk::ShaderModule CompileProgram(vk::Device device, const ShaderParams& params,
|
||||
ShaderPixelInputInfo& input_info);
|
||||
vk::ShaderModule CompileProgram(vk::Device device, const ShaderParams& params,
|
||||
ShaderComputeInputInfo& input_info);
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
|
||||
#endif /* EMULATOR_INCLUDE_EMULATOR_GRAPHICS_SHADER_SHADERCOMPILER_H_ */
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
#include "graphics/shader/recompiler/ir/passes/ResourceMaterialization.h"
|
||||
#include "graphics/shader/shader.h"
|
||||
|
||||
#include <utility>
|
||||
|
||||
namespace Libs::Graphics {
|
||||
|
||||
bool ShaderMaterializeStageRuntime(std::shared_ptr<const ShaderRecompiler::IR::Program> program,
|
||||
std::span<const uint32_t> user_data, uint64_t shader_base,
|
||||
ShaderStageRuntime& stage,
|
||||
ShaderSpecializationMemoryReader read_specialization_memory,
|
||||
void* read_memory_data) {
|
||||
if (program == nullptr) {
|
||||
return false;
|
||||
}
|
||||
ShaderRecompiler::IR::SrtRuntime runtime;
|
||||
runtime.user_data = user_data;
|
||||
runtime.shader_base = shader_base;
|
||||
runtime.read_specialization_memory = read_specialization_memory;
|
||||
runtime.userdata = read_memory_data;
|
||||
ShaderRecompiler::IR::ResourceSnapshot snapshot;
|
||||
if (!ShaderRecompiler::IR::MaterializeResources(*program, runtime, snapshot) ||
|
||||
!ShaderRecompiler::IR::ValidateResourceSpecialization(*program, snapshot)) {
|
||||
return false;
|
||||
}
|
||||
auto resources =
|
||||
std::make_shared<const ShaderRecompiler::IR::ResourceSnapshot>(std::move(snapshot));
|
||||
stage = {std::move(program), std::move(resources)};
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
Reference in New Issue
Block a user