mirror of
https://github.com/KytyPS5/KytyPS5.git
synced 2026-10-20 02:47:02 +02:00
graphics: specialize invariant indirect image tables
This commit is contained in:
@@ -901,6 +901,7 @@ bool TryRecompile(std::span<const uint32_t> code, const CompileOptions& options,
|
||||
if (!IR::TrackResources(ir, error)) {
|
||||
return false;
|
||||
}
|
||||
IR::EliminateDeadCode(ir.values->blocks);
|
||||
if (options.stage == ShaderType::Vertex) {
|
||||
ir.info.vertex_offset_sgpr = embedded_fetch.vertex_offset_sgpr;
|
||||
}
|
||||
@@ -922,6 +923,7 @@ bool TryRecompile(std::span<const uint32_t> code, const CompileOptions& options,
|
||||
runtime.shader_base = options.shader_base != 0 ? options.shader_base
|
||||
: reinterpret_cast<uint64_t>(code.data());
|
||||
runtime.read_memory = options.read_memory;
|
||||
runtime.read_specialization_memory = options.read_specialization_memory;
|
||||
if (runtime.read_memory == nullptr && options.user_data == nullptr) {
|
||||
runtime.read_memory = ReadZeroMemory;
|
||||
}
|
||||
|
||||
@@ -21,18 +21,19 @@ struct CompileOptions {
|
||||
uint64_t shader_hash = 0;
|
||||
uint64_t shader_base = 0;
|
||||
std::optional<uint64_t> flat_memory_base;
|
||||
uint32_t descriptor_set = 0;
|
||||
uint32_t push_constant_offset = 0;
|
||||
bool dump_ir = true;
|
||||
bool early_dump = false;
|
||||
const char* dump_label = nullptr;
|
||||
const uint32_t* user_data = nullptr;
|
||||
IR::SrtMemoryReader read_memory = nullptr;
|
||||
void* read_memory_data = nullptr;
|
||||
const IR::ResourceSnapshot* resource_snapshot = nullptr;
|
||||
const ShaderVertexInputInfo* vertex_input_info = nullptr;
|
||||
const ShaderPixelInputInfo* pixel_input_info = nullptr;
|
||||
const ShaderComputeInputInfo* compute_input_info = nullptr;
|
||||
uint32_t descriptor_set = 0;
|
||||
uint32_t push_constant_offset = 0;
|
||||
bool dump_ir = true;
|
||||
bool early_dump = false;
|
||||
const char* dump_label = nullptr;
|
||||
const uint32_t* user_data = nullptr;
|
||||
IR::SrtMemoryReader read_memory = nullptr;
|
||||
IR::SrtMemoryReader read_specialization_memory = nullptr;
|
||||
void* read_memory_data = nullptr;
|
||||
const IR::ResourceSnapshot* resource_snapshot = nullptr;
|
||||
const ShaderVertexInputInfo* vertex_input_info = nullptr;
|
||||
const ShaderPixelInputInfo* pixel_input_info = nullptr;
|
||||
const ShaderComputeInputInfo* compute_input_info = nullptr;
|
||||
};
|
||||
|
||||
struct CompileResult {
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include "graphics/shader/recompiler/ir/ShaderIR.h"
|
||||
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
@@ -19,8 +20,18 @@ struct ValueBlockInfo {
|
||||
};
|
||||
|
||||
struct DescriptorSource {
|
||||
std::array<Value, 8> dwords {};
|
||||
uint32_t dword_count = 0;
|
||||
struct IndirectImage {
|
||||
uint32_t material_source = 0;
|
||||
uint32_t heap_source = 0;
|
||||
uint32_t selector_stride = 0;
|
||||
uint32_t selector_offset = 0;
|
||||
|
||||
bool operator==(const IndirectImage& other) const = default;
|
||||
};
|
||||
|
||||
std::array<Value, 8> dwords {};
|
||||
uint32_t dword_count = 0;
|
||||
std::optional<IndirectImage> indirect_image;
|
||||
|
||||
bool operator==(const DescriptorSource& other) const = default;
|
||||
};
|
||||
|
||||
@@ -5,13 +5,18 @@
|
||||
#include "graphics/shader/shaderBindings.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cstring>
|
||||
#include <fmt/format.h>
|
||||
#include <functional>
|
||||
#include <numeric>
|
||||
#include <unordered_set>
|
||||
|
||||
namespace Libs::Graphics::ShaderRecompiler::IR {
|
||||
namespace {
|
||||
|
||||
constexpr uint64_t AddressMask = 0x0000ffffffffffffull;
|
||||
constexpr uint64_t AddressMask = 0x0000ffffffffffffull;
|
||||
constexpr uint64_t MaxIndirectImageProbes = 65536u;
|
||||
|
||||
Decoder::ImageDimension DescriptorDimension(const DescriptorValue& descriptor,
|
||||
Decoder::ImageDimension requested) {
|
||||
@@ -89,6 +94,167 @@ bool DecodeBufferDescriptor(const DescriptorValue& descriptor, ShaderBufferResou
|
||||
return true;
|
||||
}
|
||||
|
||||
const DescriptorSource* Source(const Program& program, uint32_t source) {
|
||||
if (program.values == nullptr || source >= program.values->descriptor_sources.size()) {
|
||||
return nullptr;
|
||||
}
|
||||
return &program.values->descriptor_sources[source];
|
||||
}
|
||||
|
||||
void MarkCleanFlatSlots(const Program& program, const DescriptorSource* source,
|
||||
std::span<uint8_t> slots) {
|
||||
if (source == nullptr) {
|
||||
return;
|
||||
}
|
||||
std::vector<Value> pending(source->dwords.begin(),
|
||||
source->dwords.begin() + source->dword_count);
|
||||
std::vector<const Inst*> visited;
|
||||
while (!pending.empty()) {
|
||||
auto value = pending.back().Resolve();
|
||||
pending.pop_back();
|
||||
const auto* inst = value.TryInstruction();
|
||||
if (inst == nullptr || std::ranges::find(visited, inst) != visited.end()) {
|
||||
continue;
|
||||
}
|
||||
visited.push_back(inst);
|
||||
if (inst->GetOpcode() == ValueOpcode::ReadConst) {
|
||||
const auto slot = inst->Arg(1).Resolve();
|
||||
if (slot.IsImmediate() && slot.GetType() == Type::U32 && slot.U32() < slots.size()) {
|
||||
slots[slot.U32()] = 1u;
|
||||
pending.push_back(program.values->srt_reads[slot.U32()].value);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
for (size_t arg = 0; arg < inst->NumArgs(); arg++) {
|
||||
pending.push_back(inst->Arg(arg));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uint64_t ScalarBufferSize(const ShaderBufferResource& descriptor) {
|
||||
return descriptor.Stride() == 0u
|
||||
? descriptor.NumRecords()
|
||||
: static_cast<uint64_t>(descriptor.Stride()) * descriptor.NumRecords();
|
||||
}
|
||||
|
||||
bool ReadSpecializationWord(const SrtRuntime& runtime, uint64_t address, uint32_t& word) {
|
||||
return runtime.read_specialization_memory != nullptr &&
|
||||
runtime.read_specialization_memory(runtime.userdata, address, &word);
|
||||
}
|
||||
|
||||
bool ReadScalarBufferWord(const ShaderBufferResource& descriptor, uint32_t dynamic_offset,
|
||||
uint32_t immediate_offset, const SrtRuntime& runtime, uint32_t& word,
|
||||
std::string* error, uint32_t use_pc) {
|
||||
const auto byte_offset = static_cast<uint64_t>(dynamic_offset) + immediate_offset;
|
||||
const auto aligned = byte_offset & ~uint64_t {3};
|
||||
const auto size = ScalarBufferSize(descriptor);
|
||||
if (aligned > size || size - aligned < sizeof(uint32_t)) {
|
||||
word = 0;
|
||||
return true;
|
||||
}
|
||||
const auto base = descriptor.Base48() & ~uint64_t {3};
|
||||
if (aligned > AddressMask - base) {
|
||||
if (error != nullptr) {
|
||||
*error = fmt::format("indirect image scalar read at pc 0x{:08x} exceeds the "
|
||||
"48-bit address space",
|
||||
use_pc);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
const auto address = base + aligned;
|
||||
if (!ReadSpecializationWord(runtime, address, word)) {
|
||||
if (error != nullptr) {
|
||||
*error = fmt::format("indirect image scalar read at pc 0x{:08x} failed at "
|
||||
"0x{:016x}",
|
||||
use_pc, address);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MaterializeIndirectImage(const DescriptorSource::IndirectImage& indirect,
|
||||
const DescriptorValue& material_value,
|
||||
const DescriptorValue& heap_value, const SrtRuntime& runtime,
|
||||
DescriptorValue& result, std::string* error, uint32_t use_pc) {
|
||||
ShaderBufferResource material;
|
||||
ShaderBufferResource heap;
|
||||
if (!DecodeBufferDescriptor(material_value, material) ||
|
||||
!DecodeBufferDescriptor(heap_value, heap)) {
|
||||
if (error != nullptr) {
|
||||
*error = fmt::format("indirect image tables at pc 0x{:08x} have invalid descriptors",
|
||||
use_pc);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if (material.Stride() != indirect.selector_stride) {
|
||||
if (error != nullptr) {
|
||||
*error =
|
||||
fmt::format("indirect image material table at pc 0x{:08x} changed stride", use_pc);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// S_BUFFER_LOAD ignores vector-buffer swizzle/add-thread fields. The shader computes the
|
||||
// record stride explicitly; enumerate every wrapped 32-bit offset that can pass bounds.
|
||||
const auto period = uint64_t {1} << 32u;
|
||||
const auto step = std::gcd<uint64_t>(indirect.selector_stride, period);
|
||||
const auto residue = static_cast<uint64_t>(indirect.selector_offset) % step;
|
||||
const auto size = ScalarBufferSize(material);
|
||||
const auto limit = std::min<uint64_t>(UINT32_MAX, size + 3u);
|
||||
const auto probe_count = residue <= limit ? (limit - residue) / step + 1u : 0u;
|
||||
if (probe_count > MaxIndirectImageProbes) {
|
||||
if (error != nullptr) {
|
||||
*error = fmt::format("indirect image material table at pc 0x{:08x} requires {} "
|
||||
"bounded probes",
|
||||
use_pc, probe_count);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<uint32_t> keys {0u};
|
||||
std::unordered_set<uint32_t> seen {0u};
|
||||
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, error,
|
||||
use_pc)) {
|
||||
return false;
|
||||
}
|
||||
if (seen.insert(key).second) {
|
||||
keys.push_back(key);
|
||||
}
|
||||
if (limit - offset < step) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
DescriptorValue invariant;
|
||||
bool have_invariant = false;
|
||||
for (const auto key: keys) {
|
||||
DescriptorValue candidate;
|
||||
candidate.dword_count = 8u;
|
||||
const auto heap_offset = key << 5u;
|
||||
for (uint32_t dword = 0; dword < candidate.dword_count; dword++) {
|
||||
if (!ReadScalarBufferWord(heap, heap_offset, dword * sizeof(uint32_t), runtime,
|
||||
candidate.dwords[dword], error, use_pc)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (!have_invariant) {
|
||||
invariant = candidate;
|
||||
have_invariant = true;
|
||||
} else if (candidate != invariant) {
|
||||
if (error != nullptr) {
|
||||
*error =
|
||||
fmt::format("indirect image table at pc 0x{:08x} is not invariant", use_pc);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
result = invariant;
|
||||
return true;
|
||||
}
|
||||
|
||||
uint64_t AddressSpecialization(const AddressResource& resource,
|
||||
const ResourceSnapshot::Address& snapshot) {
|
||||
return resource.kind == ResourceKind::Flat || resource.unbased
|
||||
@@ -305,13 +471,30 @@ bool MaterializeResources(const Program& program, const SrtRuntime& runtime,
|
||||
}
|
||||
|
||||
std::vector<DescriptorSourceRequest> requests;
|
||||
requests.reserve(program.info.buffers.size() + program.info.images.size() +
|
||||
std::vector<uint8_t> clean_flat_slots(program.values->srt_reads.size());
|
||||
requests.reserve(program.info.buffers.size() + program.info.images.size() * 2u +
|
||||
program.info.samplers.size() + program.info.addresses.size());
|
||||
for (const auto& buffer: program.info.buffers) {
|
||||
requests.push_back({buffer.source, buffer.first_use_pc});
|
||||
}
|
||||
for (const auto& image: program.info.images) {
|
||||
requests.push_back({image.source, image.first_use_pc});
|
||||
const auto* source = Source(program, image.source);
|
||||
if (source != nullptr && source->indirect_image.has_value()) {
|
||||
if (runtime.read_specialization_memory == nullptr) {
|
||||
if (error != nullptr) {
|
||||
*error = fmt::format("indirect image table at pc 0x{:08x} has no clean "
|
||||
"memory reader",
|
||||
image.first_use_pc);
|
||||
}
|
||||
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, image.first_use_pc});
|
||||
}
|
||||
}
|
||||
for (const auto& sampler: program.info.samplers) {
|
||||
requests.push_back({sampler.source, sampler.first_use_pc});
|
||||
@@ -324,7 +507,8 @@ bool MaterializeResources(const Program& program, const SrtRuntime& runtime,
|
||||
|
||||
std::vector<DescriptorValue> values;
|
||||
std::vector<uint32_t> flattened_srt;
|
||||
if (!EvaluateRuntimeSources(program, requests, runtime, values, flattened_srt, error)) {
|
||||
if (!EvaluateRuntimeSources(program, requests, runtime, values, flattened_srt, clean_flat_slots,
|
||||
error)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -338,12 +522,34 @@ bool MaterializeResources(const Program& program, const SrtRuntime& runtime,
|
||||
descriptor.dwords.fill(0);
|
||||
}
|
||||
}
|
||||
next.images.assign(cursor, cursor + program.info.images.size());
|
||||
cursor += program.info.images.size();
|
||||
for (auto& descriptor: next.images) {
|
||||
next.images.reserve(program.info.images.size());
|
||||
for (const auto& image: program.info.images) {
|
||||
const auto* source = Source(program, image.source);
|
||||
DescriptorValue descriptor;
|
||||
if (source != nullptr && source->indirect_image.has_value()) {
|
||||
const std::array requests {
|
||||
DescriptorSourceRequest {source->indirect_image->material_source,
|
||||
image.first_use_pc},
|
||||
DescriptorSourceRequest {source->indirect_image->heap_source, image.first_use_pc}};
|
||||
SrtRuntime clean_runtime = runtime;
|
||||
clean_runtime.read_memory = runtime.read_specialization_memory;
|
||||
std::vector<DescriptorValue> tables;
|
||||
if (!EvaluateDescriptorSources(program, requests, clean_runtime, tables, error)) {
|
||||
return false;
|
||||
}
|
||||
const auto& material = tables[0];
|
||||
const auto& heap = tables[1];
|
||||
if (!MaterializeIndirectImage(*source->indirect_image, material, heap, runtime,
|
||||
descriptor, error, image.first_use_pc)) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
descriptor = *cursor++;
|
||||
}
|
||||
if (!ValidImageDescriptor(descriptor)) {
|
||||
descriptor.dwords.fill(0);
|
||||
}
|
||||
next.images.push_back(descriptor);
|
||||
}
|
||||
next.samplers.assign(cursor, cursor + program.info.samplers.size());
|
||||
cursor += program.info.samplers.size();
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include <fmt/format.h>
|
||||
#include <span>
|
||||
#include <unordered_set>
|
||||
#include <utility>
|
||||
|
||||
@@ -148,6 +149,7 @@ public:
|
||||
if (!m_program.srt_plan_complete) {
|
||||
return Fail(0, error, "SRT plan is not ready");
|
||||
}
|
||||
PlanIndirectImages();
|
||||
for (auto* block: m_values.blocks) {
|
||||
for (auto& inst: *block) {
|
||||
if (!Collect(inst, error)) {
|
||||
@@ -166,6 +168,20 @@ public:
|
||||
memory.sampler = patch.sampler;
|
||||
}
|
||||
}
|
||||
for (const auto& plan: m_indirect_images) {
|
||||
for (uint32_t dword = 0; dword < plan.roots.size(); dword++) {
|
||||
plan.handle->SetArg(dword, plan.roots[dword]);
|
||||
}
|
||||
for (const auto index: plan.memory) {
|
||||
m_values.memory_info[index].planning_only = true;
|
||||
}
|
||||
}
|
||||
std::erase_if(m_values.dynamic_reads, [&](Value value) {
|
||||
const auto* inst = value.Resolve().TryInstruction();
|
||||
return std::ranges::any_of(m_indirect_images, [&](const IndirectImagePlan& plan) {
|
||||
return std::ranges::find(plan.reads, inst) != plan.reads.end();
|
||||
});
|
||||
});
|
||||
m_values.descriptor_sources = std::move(m_sources);
|
||||
m_program.info = std::move(m_info);
|
||||
m_program.resource_tracking_complete = true;
|
||||
@@ -185,6 +201,14 @@ private:
|
||||
bool has_sampler = false;
|
||||
};
|
||||
|
||||
struct IndirectImagePlan {
|
||||
Inst* handle = nullptr;
|
||||
uint32_t source = 0;
|
||||
std::array<Value, 8> roots {};
|
||||
std::array<uint32_t, 9> memory {};
|
||||
std::array<const Inst*, 9> reads {};
|
||||
};
|
||||
|
||||
bool Fail(uint32_t pc, std::string* error, const std::string& reason) const {
|
||||
if (error != nullptr) {
|
||||
*error = fmt::format("shader resource tracking: hash=0x{:016x} stage={} pc=0x{:08x} {}",
|
||||
@@ -310,7 +334,8 @@ private:
|
||||
uint32_t InternSource(const DescriptorSource& descriptor) {
|
||||
for (uint32_t candidate = 0; candidate < m_sources.size(); candidate++) {
|
||||
const auto& current = m_sources[candidate];
|
||||
if (current.dword_count != descriptor.dword_count) {
|
||||
if (current.dword_count != descriptor.dword_count ||
|
||||
current.indirect_image != descriptor.indirect_image) {
|
||||
continue;
|
||||
}
|
||||
bool same = true;
|
||||
@@ -325,6 +350,236 @@ private:
|
||||
return static_cast<uint32_t>(m_sources.size() - 1);
|
||||
}
|
||||
|
||||
static bool ImmediateU32(Value value, uint32_t& result) {
|
||||
value = value.Resolve();
|
||||
if (!value.IsImmediate() || value.GetType() != Type::U32) {
|
||||
return false;
|
||||
}
|
||||
result = value.U32();
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool UsesOnly(const Inst& value, std::span<const Inst* const> users) {
|
||||
return !value.Uses().empty() && std::ranges::all_of(value.Uses(), [&](const Use& use) {
|
||||
return std::ranges::find(users, use.user) != users.end();
|
||||
});
|
||||
}
|
||||
|
||||
const MemoryInfo* ScalarReadMemory(const Inst& read, uint32_t& index) const {
|
||||
if (read.GetOpcode() != ValueOpcode::ReadConstBuffer || read.NumArgs() != 2u) {
|
||||
return nullptr;
|
||||
}
|
||||
index = read.Flags<MemoryFlags>().index;
|
||||
if (index >= m_values.memory_info.size()) {
|
||||
return nullptr;
|
||||
}
|
||||
const auto& memory = m_values.memory_info[index];
|
||||
return memory.kind == ResourceKind::ScalarBuffer && memory.data_bits == 32u &&
|
||||
memory.data_dwords == 1u
|
||||
? &memory
|
||||
: nullptr;
|
||||
}
|
||||
|
||||
bool MemoryIndexBelongsTo(uint32_t index, const Inst& owner) const {
|
||||
for (const auto* block: m_values.blocks) {
|
||||
for (const auto& inst: *block) {
|
||||
const auto op = inst.GetOpcode();
|
||||
if ((!IsBuffer(op) && !IsAddress(op) && !IsImage(op)) || &inst == &owner) {
|
||||
continue;
|
||||
}
|
||||
if (inst.Flags<MemoryFlags>().index == index) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MakeRuntimeBufferSource(const Inst& handle, uint32_t pc, uint32_t& source,
|
||||
DescriptorSource& descriptor) {
|
||||
if (handle.GetOpcode() != ValueOpcode::GetBufferResource ||
|
||||
!MakeSource(handle, 4u, false, descriptor, pc, nullptr)) {
|
||||
return false;
|
||||
}
|
||||
std::string reason;
|
||||
uint32_t bad_dword = 0;
|
||||
if (!ValidateSource(descriptor, reason, bad_dword)) {
|
||||
return false;
|
||||
}
|
||||
source = InternSource(descriptor);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MatchMaterialOffset(Value value, Value& selector, uint32_t& stride,
|
||||
uint32_t& offset) const {
|
||||
value = value.Resolve();
|
||||
offset = 0;
|
||||
auto* candidate = value.TryInstruction();
|
||||
if (candidate != nullptr && candidate->GetOpcode() == ValueOpcode::IAdd32 &&
|
||||
candidate->NumArgs() == 2u) {
|
||||
uint32_t immediate = 0;
|
||||
if (ImmediateU32(candidate->Arg(0), immediate)) {
|
||||
value = candidate->Arg(1).Resolve();
|
||||
} else if (ImmediateU32(candidate->Arg(1), immediate)) {
|
||||
value = candidate->Arg(0).Resolve();
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
offset = immediate;
|
||||
}
|
||||
const auto* multiply = value.TryInstruction();
|
||||
if (multiply == nullptr || multiply->GetOpcode() != ValueOpcode::IMul32 ||
|
||||
multiply->NumArgs() != 2u) {
|
||||
return false;
|
||||
}
|
||||
if (ImmediateU32(multiply->Arg(0), stride)) {
|
||||
selector = multiply->Arg(1).Resolve();
|
||||
} else if (ImmediateU32(multiply->Arg(1), stride)) {
|
||||
selector = multiply->Arg(0).Resolve();
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
const auto* selector_inst = selector.TryInstruction();
|
||||
return stride != 0u && selector_inst != nullptr &&
|
||||
selector_inst->GetOpcode() == ValueOpcode::ReadFirstLane;
|
||||
}
|
||||
|
||||
bool TryMakeIndirectImage(Inst& handle, uint32_t pc, IndirectImagePlan& plan) {
|
||||
if (handle.GetOpcode() != ValueOpcode::GetImageResource || handle.NumArgs() != 8u) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::array<Inst*, 8> heap_reads {};
|
||||
Inst* heap_handle = nullptr;
|
||||
Value heap_offset;
|
||||
for (uint32_t dword = 0; dword < heap_reads.size(); dword++) {
|
||||
heap_reads[dword] = handle.Arg(dword).Resolve().TryInstruction();
|
||||
if (heap_reads[dword] == nullptr) {
|
||||
return false;
|
||||
}
|
||||
uint32_t memory_index = 0;
|
||||
const auto* memory = ScalarReadMemory(*heap_reads[dword], memory_index);
|
||||
if (memory == nullptr || memory->offset != dword * sizeof(uint32_t) ||
|
||||
!MemoryIndexBelongsTo(memory_index, *heap_reads[dword])) {
|
||||
return false;
|
||||
}
|
||||
auto* current_handle = heap_reads[dword]->Arg(0).Resolve().TryInstruction();
|
||||
if (current_handle == nullptr ||
|
||||
(heap_handle != nullptr && current_handle != heap_handle)) {
|
||||
return false;
|
||||
}
|
||||
heap_handle = current_handle;
|
||||
if (dword == 0u) {
|
||||
heap_offset = heap_reads[dword]->Arg(1).Resolve();
|
||||
} else if (!EquivalentValue(m_values, heap_offset, heap_reads[dword]->Arg(1))) {
|
||||
return false;
|
||||
}
|
||||
plan.memory[dword + 1u] = memory_index;
|
||||
plan.reads[dword + 1u] = heap_reads[dword];
|
||||
}
|
||||
|
||||
const auto* shift = heap_offset.TryInstruction();
|
||||
uint32_t shift_amount = 0;
|
||||
if (shift == nullptr || shift->GetOpcode() != ValueOpcode::ShiftLeftLogical32 ||
|
||||
shift->NumArgs() != 2u || !ImmediateU32(shift->Arg(1), shift_amount) ||
|
||||
shift_amount != 5u) {
|
||||
return false;
|
||||
}
|
||||
auto* material_read = shift->Arg(0).Resolve().TryInstruction();
|
||||
if (material_read == nullptr) {
|
||||
return false;
|
||||
}
|
||||
uint32_t material_memory_index = 0;
|
||||
const auto* material_memory = ScalarReadMemory(*material_read, material_memory_index);
|
||||
if (material_memory == nullptr || material_memory->offset != 0u ||
|
||||
!MemoryIndexBelongsTo(material_memory_index, *material_read)) {
|
||||
return false;
|
||||
}
|
||||
auto* material_handle = material_read->Arg(0).Resolve().TryInstruction();
|
||||
if (material_handle == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Value selector;
|
||||
uint32_t selector_stride = 0;
|
||||
uint32_t selector_offset = 0;
|
||||
if (!MatchMaterialOffset(material_read->Arg(1), selector, selector_stride,
|
||||
selector_offset)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const std::array<const Inst*, 1> material_users {shift};
|
||||
std::array<const Inst*, 8> heap_users {};
|
||||
std::copy(heap_reads.begin(), heap_reads.end(), heap_users.begin());
|
||||
const std::array<const Inst*, 1> image_users {&handle};
|
||||
if (!UsesOnly(*material_read, material_users) || !UsesOnly(*shift, heap_users)) {
|
||||
return false;
|
||||
}
|
||||
for (const auto* read: heap_reads) {
|
||||
if (!UsesOnly(*read, image_users)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
DescriptorSource material_source;
|
||||
DescriptorSource heap_source;
|
||||
uint32_t material_source_index = 0;
|
||||
uint32_t heap_source_index = 0;
|
||||
if (!MakeRuntimeBufferSource(*material_handle, pc, material_source_index,
|
||||
material_source) ||
|
||||
!MakeRuntimeBufferSource(*heap_handle, pc, heap_source_index, heap_source)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
DescriptorSource image_source;
|
||||
image_source.dword_count = 8u;
|
||||
std::copy(material_source.dwords.begin(), material_source.dwords.begin() + 4u,
|
||||
image_source.dwords.begin());
|
||||
std::copy(heap_source.dwords.begin(), heap_source.dwords.begin() + 4u,
|
||||
image_source.dwords.begin() + 4u);
|
||||
image_source.indirect_image = DescriptorSource::IndirectImage {
|
||||
material_source_index, heap_source_index, selector_stride, selector_offset};
|
||||
|
||||
plan.handle = &handle;
|
||||
plan.source = InternSource(image_source);
|
||||
plan.roots = image_source.dwords;
|
||||
plan.memory[0] = material_memory_index;
|
||||
plan.reads[0] = material_read;
|
||||
return true;
|
||||
}
|
||||
|
||||
const IndirectImagePlan* FindIndirectImage(const Inst& handle) const {
|
||||
const auto found =
|
||||
std::ranges::find_if(m_indirect_images, [&](const IndirectImagePlan& plan) {
|
||||
return plan.handle == &handle;
|
||||
});
|
||||
return found == m_indirect_images.end() ? nullptr : &*found;
|
||||
}
|
||||
|
||||
bool IsIndirectPlanningMemory(uint32_t index) const {
|
||||
return std::ranges::any_of(m_indirect_images, [&](const IndirectImagePlan& plan) {
|
||||
return std::ranges::find(plan.memory, index) != plan.memory.end();
|
||||
});
|
||||
}
|
||||
|
||||
void PlanIndirectImages() {
|
||||
for (auto* block: m_values.blocks) {
|
||||
for (auto& inst: *block) {
|
||||
if (!IsImage(inst.GetOpcode()) || inst.NumArgs() == 0u) {
|
||||
continue;
|
||||
}
|
||||
auto* handle = inst.Arg(0).Resolve().TryInstruction();
|
||||
if (handle == nullptr || FindIndirectImage(*handle) != nullptr) {
|
||||
continue;
|
||||
}
|
||||
IndirectImagePlan plan;
|
||||
if (TryMakeIndirectImage(*handle, inst.Flags<MemoryFlags>().pc, plan)) {
|
||||
m_indirect_images.push_back(std::move(plan));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool GetHandle(Value value, ValueOpcode expected, uint32_t width, uint32_t pc, Inst*& handle,
|
||||
uint32_t& source, std::string* error, bool sampler = false) {
|
||||
handle = value.Resolve().TryInstruction();
|
||||
@@ -560,7 +815,7 @@ private:
|
||||
return Fail(flags.pc, error, "memory operation has no resource handle");
|
||||
}
|
||||
const auto& memory = m_values.memory_info[flags.index];
|
||||
if (memory.planning_only) {
|
||||
if (memory.planning_only || IsIndirectPlanningMemory(flags.index)) {
|
||||
return true;
|
||||
}
|
||||
Inst* handle = nullptr;
|
||||
@@ -592,8 +847,12 @@ private:
|
||||
AddMemoryPatch(flags.index, resource, 0, false, flags.pc, error);
|
||||
}
|
||||
|
||||
if (!GetHandle(inst.Arg(0), ValueOpcode::GetImageResource, 8, flags.pc, handle, source,
|
||||
error)) {
|
||||
handle = inst.Arg(0).Resolve().TryInstruction();
|
||||
const auto* indirect = handle != nullptr ? FindIndirectImage(*handle) : nullptr;
|
||||
if (indirect != nullptr) {
|
||||
source = indirect->source;
|
||||
} else if (!GetHandle(inst.Arg(0), ValueOpcode::GetImageResource, 8, flags.pc, handle,
|
||||
source, error)) {
|
||||
return false;
|
||||
}
|
||||
resource = AddImage(source, memory, op, flags.pc);
|
||||
@@ -638,7 +897,8 @@ private:
|
||||
}
|
||||
for (uint32_t image = 0; image < m_info.images.size(); image++) {
|
||||
const auto* image_source = Source(m_info.images[image].source);
|
||||
if (image_source == nullptr || image_source->dword_count != 8) {
|
||||
if (image_source == nullptr || image_source->dword_count != 8 ||
|
||||
image_source->indirect_image.has_value()) {
|
||||
continue;
|
||||
}
|
||||
bool alias = true;
|
||||
@@ -654,12 +914,13 @@ private:
|
||||
}
|
||||
}
|
||||
|
||||
Program& m_program;
|
||||
ValueProgram& m_values;
|
||||
ShaderInfo m_info;
|
||||
std::vector<DescriptorSource> m_sources;
|
||||
std::vector<HandlePatch> m_handle_patches;
|
||||
std::vector<MemoryPatch> m_memory_patches;
|
||||
Program& m_program;
|
||||
ValueProgram& m_values;
|
||||
ShaderInfo m_info;
|
||||
std::vector<DescriptorSource> m_sources;
|
||||
std::vector<HandlePatch> m_handle_patches;
|
||||
std::vector<MemoryPatch> m_memory_patches;
|
||||
std::vector<IndirectImagePlan> m_indirect_images;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
@@ -396,8 +396,10 @@ private:
|
||||
|
||||
class Evaluator {
|
||||
public:
|
||||
Evaluator(const Program& program, const SrtRuntime& runtime)
|
||||
: m_program(program), m_values(*program.values), m_runtime(runtime) {}
|
||||
Evaluator(const Program& program, const SrtRuntime& runtime,
|
||||
std::span<const uint8_t> clean_flat_slots = {}, Evaluator* clean_evaluator = nullptr)
|
||||
: m_program(program), m_values(*program.values), m_runtime(runtime),
|
||||
m_clean_flat_slots(clean_flat_slots), m_clean_evaluator(clean_evaluator) {}
|
||||
|
||||
void SetUsePc(uint32_t pc) { m_use_pc = pc; }
|
||||
|
||||
@@ -596,6 +598,12 @@ private:
|
||||
slot.U32() >= m_values.srt_reads.size()) {
|
||||
return Fail(error, "invalid flattened SRT index");
|
||||
}
|
||||
if (slot.U32() < m_clean_flat_slots.size() &&
|
||||
m_clean_flat_slots[slot.U32()] != 0u && m_clean_evaluator != nullptr) {
|
||||
m_clean_evaluator->SetUsePc(m_use_pc);
|
||||
return m_clean_evaluator->EvaluateWide(m_values.srt_reads[slot.U32()].value,
|
||||
result, error);
|
||||
}
|
||||
return EvaluateWide(m_values.srt_reads[slot.U32()].value, result, error);
|
||||
}
|
||||
case ValueOpcode::LoadAddressU32:
|
||||
@@ -842,7 +850,9 @@ private:
|
||||
const Program& m_program;
|
||||
const ValueProgram& m_values;
|
||||
const SrtRuntime& m_runtime;
|
||||
uint32_t m_use_pc = 0;
|
||||
std::span<const uint8_t> m_clean_flat_slots;
|
||||
Evaluator* m_clean_evaluator = nullptr;
|
||||
uint32_t m_use_pc = 0;
|
||||
std::unordered_map<const Inst*, uint64_t> m_cache;
|
||||
std::vector<const Inst*> m_visiting;
|
||||
};
|
||||
@@ -858,14 +868,24 @@ bool EvaluateRuntimeSourcesImpl(const Program& program
|
||||
std::span<const DescriptorSourceRequest> requests,
|
||||
const SrtRuntime& runtime, std::vector<DescriptorValue>& results,
|
||||
std::vector<uint32_t>& flat, bool evaluate_flat,
|
||||
std::string* error) {
|
||||
std::span<const uint8_t> clean_flat_slots, std::string* error) {
|
||||
if (program.values == nullptr || !program.srt_plan_complete) {
|
||||
if (error != nullptr) {
|
||||
*error = Diagnostic(program, 0, "typed SRT plan is not ready");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
Evaluator evaluator(program, runtime);
|
||||
if (std::ranges::any_of(clean_flat_slots, [](uint8_t clean) { return clean != 0u; }) &&
|
||||
runtime.read_specialization_memory == nullptr) {
|
||||
if (error != nullptr) {
|
||||
*error = Diagnostic(program, 0, "clean flattened SRT read has no memory reader");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
SrtRuntime clean_runtime = runtime;
|
||||
clean_runtime.read_memory = runtime.read_specialization_memory;
|
||||
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) {
|
||||
@@ -891,9 +911,12 @@ bool EvaluateRuntimeSourcesImpl(const Program& program
|
||||
if (evaluate_flat) {
|
||||
flattened.resize(program.values->srt_reads.size());
|
||||
for (const auto& read: program.values->srt_reads) {
|
||||
evaluator.SetUsePc(read.use_pc);
|
||||
const bool clean = read.flat_offset < clean_flat_slots.size() &&
|
||||
clean_flat_slots[read.flat_offset] != 0u;
|
||||
auto& selected = clean ? clean_evaluator : evaluator;
|
||||
selected.SetUsePc(read.use_pc);
|
||||
if (read.flat_offset >= flattened.size() ||
|
||||
!evaluator.Evaluate(read.value, flattened[read.flat_offset], error)) {
|
||||
!selected.Evaluate(read.value, flattened[read.flat_offset], error)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -948,20 +971,23 @@ bool EvaluateDescriptorSources(const Program& program,
|
||||
const SrtRuntime& runtime, std::vector<DescriptorValue>& results,
|
||||
std::string* error) {
|
||||
std::vector<uint32_t> ignored;
|
||||
return EvaluateRuntimeSourcesImpl(program, requests, runtime, results, ignored, false, error);
|
||||
return EvaluateRuntimeSourcesImpl(program, requests, runtime, results, ignored, false, {},
|
||||
error);
|
||||
}
|
||||
|
||||
bool EvaluateRuntimeSources(const Program& program,
|
||||
std::span<const DescriptorSourceRequest> requests,
|
||||
const SrtRuntime& runtime, std::vector<DescriptorValue>& results,
|
||||
std::vector<uint32_t>& flat, std::string* error) {
|
||||
return EvaluateRuntimeSourcesImpl(program, requests, runtime, results, flat, true, error);
|
||||
std::vector<uint32_t>& flat, std::span<const uint8_t> clean_flat_slots,
|
||||
std::string* error) {
|
||||
return EvaluateRuntimeSourcesImpl(program, requests, runtime, results, flat, true,
|
||||
clean_flat_slots, error);
|
||||
}
|
||||
|
||||
bool WalkSrt(const Program& program, const SrtRuntime& runtime, std::vector<uint32_t>& flat,
|
||||
std::string* error) {
|
||||
std::vector<DescriptorValue> ignored;
|
||||
return EvaluateRuntimeSources(program, {}, runtime, ignored, flat, error);
|
||||
return EvaluateRuntimeSources(program, {}, runtime, ignored, flat, {}, error);
|
||||
}
|
||||
|
||||
} // namespace Libs::Graphics::ShaderRecompiler::IR
|
||||
|
||||
@@ -18,6 +18,7 @@ struct SrtRuntime {
|
||||
SrtMemoryReader read_memory = nullptr;
|
||||
void* userdata = nullptr;
|
||||
std::optional<uint64_t> flat_memory_base;
|
||||
SrtMemoryReader read_specialization_memory = nullptr;
|
||||
};
|
||||
|
||||
struct DescriptorSourceRequest {
|
||||
@@ -46,7 +47,8 @@ bool EvaluateDescriptorSources(const Program& program,
|
||||
bool EvaluateRuntimeSources(const Program& program,
|
||||
std::span<const DescriptorSourceRequest> requests,
|
||||
const SrtRuntime& runtime, std::vector<DescriptorValue>& results,
|
||||
std::vector<uint32_t>& flat, std::string* error);
|
||||
std::vector<uint32_t>& flat, std::span<const uint8_t> clean_flat_slots,
|
||||
std::string* error);
|
||||
|
||||
bool WalkSrt(const Program& program, const SrtRuntime& runtime, std::vector<uint32_t>& flat,
|
||||
std::string* error);
|
||||
|
||||
Reference in New Issue
Block a user