mirror of
https://github.com/KytyPS5/KytyPS5.git
synced 2026-10-20 02:47:02 +02:00
b2a80a3a96
Co-authored-by: Bipin <87369440+bipinkrish@users.noreply.github.com>
852 lines
30 KiB
C++
852 lines
30 KiB
C++
#include "graphics/shader/recompiler/ir/passes/ResourceTracking.h"
|
|
|
|
#include "common/assert.h"
|
|
#include "graphics/shader/recompiler/ir/ShaderIR.h"
|
|
#include "graphics/shader/recompiler/ir/passes/SrtWalker.h"
|
|
|
|
#include <algorithm>
|
|
#include <fmt/format.h>
|
|
#include <span>
|
|
#include <utility>
|
|
|
|
namespace Libs::Graphics::ShaderRecompiler::IR {
|
|
namespace {
|
|
|
|
constexpr uint32_t SamplerBorderClampMask = (1u << 2u) | (1u << 5u) | (1u << 8u);
|
|
constexpr uint32_t SamplerDword3ReservedMask = 0x3ffff000u;
|
|
|
|
uint32_t PossibleU32Bits(Value value) {
|
|
value = value.Resolve();
|
|
if (value.IsImmediate()) {
|
|
return value.GetType() == Type::U32 ? value.U32() : UINT32_MAX;
|
|
}
|
|
const auto* inst = value.TryInstruction();
|
|
if (inst == nullptr) {
|
|
return UINT32_MAX;
|
|
}
|
|
switch (inst->GetOpcode()) {
|
|
case ValueOpcode::BitwiseAnd32:
|
|
return PossibleU32Bits(inst->Arg(0)) & PossibleU32Bits(inst->Arg(1));
|
|
case ValueOpcode::BitwiseOr32:
|
|
return PossibleU32Bits(inst->Arg(0)) | PossibleU32Bits(inst->Arg(1));
|
|
case ValueOpcode::ShiftLeftLogical32: {
|
|
const auto shift = inst->Arg(1).Resolve();
|
|
return shift.IsImmediate() && shift.GetType() == Type::U32
|
|
? PossibleU32Bits(inst->Arg(0)) << (shift.U32() & 31u)
|
|
: UINT32_MAX;
|
|
}
|
|
default: return UINT32_MAX;
|
|
}
|
|
}
|
|
|
|
Value CanonicalizeSampleAdjustDword3(Value value) {
|
|
for (;;) {
|
|
value = value.Resolve();
|
|
const auto* inst = value.TryInstruction();
|
|
if (inst == nullptr || inst->GetOpcode() != ValueOpcode::BitwiseOr32) {
|
|
return value;
|
|
}
|
|
const auto left = inst->Arg(0).Resolve();
|
|
const auto right = inst->Arg(1).Resolve();
|
|
const bool left_reserved = (PossibleU32Bits(left) & ~SamplerDword3ReservedMask) == 0;
|
|
const bool right_reserved = (PossibleU32Bits(right) & ~SamplerDword3ReservedMask) == 0;
|
|
if (left_reserved && right_reserved) {
|
|
return Value(0u);
|
|
}
|
|
if (left_reserved) {
|
|
value = right;
|
|
} else if (right_reserved) {
|
|
value = left;
|
|
} else {
|
|
return value;
|
|
}
|
|
}
|
|
}
|
|
|
|
const char* StageName(ShaderType stage) {
|
|
switch (stage) {
|
|
case ShaderType::Vertex: return "vertex";
|
|
case ShaderType::Pixel: return "pixel";
|
|
case ShaderType::Fetch: return "fetch";
|
|
case ShaderType::Compute: return "compute";
|
|
default: return "unknown";
|
|
}
|
|
}
|
|
|
|
uint32_t ByteExtent(const MemoryInfo& memory) {
|
|
const auto bytes = std::max((memory.data_bits + 7u) / 8u, 1u);
|
|
const auto count = std::max(memory.data_dwords, 1u);
|
|
const auto end = static_cast<uint64_t>(memory.offset) + static_cast<uint64_t>(bytes) * count;
|
|
return end > UINT32_MAX ? UINT32_MAX : static_cast<uint32_t>(end);
|
|
}
|
|
|
|
class Tracker {
|
|
public:
|
|
explicit Tracker(Program& program): m_program(program), m_info(program.info) {
|
|
m_info.buffers.clear();
|
|
m_info.images.clear();
|
|
m_info.samplers.clear();
|
|
m_info.sampled_pairs.clear();
|
|
m_info.uses_dma = false;
|
|
m_shader_writes = HasShaderMemoryWrites(program);
|
|
}
|
|
|
|
void Run() {
|
|
if (m_program.resource_tracking_complete) {
|
|
Fail(0, "resources already tracked");
|
|
}
|
|
if (!m_program.srt_plan_complete) {
|
|
Fail(0, "SRT plan is not ready");
|
|
}
|
|
PlanIndirectImages();
|
|
for (auto* block: m_program.blocks) {
|
|
for (auto& inst: *block) {
|
|
Collect(inst);
|
|
}
|
|
}
|
|
LinkImageAliases();
|
|
for (const auto& patch: m_handle_patches) {
|
|
patch.handle->SetFlags<uint32_t>(patch.resource);
|
|
}
|
|
for (const auto& patch: m_memory_patches) {
|
|
auto& memory = m_program.memory_info[patch.index];
|
|
memory.resource = patch.resource;
|
|
if (patch.has_sampler) {
|
|
memory.sampler = patch.sampler;
|
|
}
|
|
}
|
|
for (const auto& plan: m_indirect_images) {
|
|
plan.handle->SetArg(0, plan.key);
|
|
for (uint32_t dword = 0; dword < 4u; dword++) {
|
|
plan.handle->SetArg(dword + 1u, plan.roots[dword + 4u]);
|
|
}
|
|
for (uint32_t dword = 5u; dword < plan.roots.size(); dword++) {
|
|
plan.handle->SetArg(dword, plan.key);
|
|
}
|
|
for (const auto index: plan.memory) {
|
|
m_program.memory_info[index].planning_only = true;
|
|
}
|
|
}
|
|
std::erase_if(m_program.dynamic_reads, [&](Value value) {
|
|
const auto* inst = value.Resolve().TryInstruction();
|
|
return std::any_of(m_indirect_images.begin(), m_indirect_images.end(),
|
|
[&](const IndirectImagePlan& plan) {
|
|
return std::ranges::find(plan.reads, inst) != plan.reads.end();
|
|
});
|
|
});
|
|
m_program.descriptor_sources = std::move(m_sources);
|
|
m_program.info = std::move(m_info);
|
|
m_program.resource_tracking_complete = true;
|
|
}
|
|
|
|
private:
|
|
struct HandlePatch {
|
|
Inst* handle = nullptr;
|
|
uint32_t resource = 0;
|
|
};
|
|
|
|
struct MemoryPatch {
|
|
uint32_t index = 0;
|
|
uint32_t resource = 0;
|
|
uint32_t sampler = 0;
|
|
bool has_sampler = false;
|
|
};
|
|
|
|
struct IndirectImagePlan {
|
|
Inst* handle = nullptr;
|
|
uint32_t source = 0;
|
|
Value key;
|
|
std::array<Value, 8> roots {};
|
|
std::array<uint32_t, 8> memory {};
|
|
std::array<const Inst*, 8> reads {};
|
|
};
|
|
|
|
[[noreturn]] void Fail(uint32_t pc, const std::string& reason) const {
|
|
const auto message =
|
|
fmt::format("shader resource tracking: hash=0x{:016x} stage={} pc=0x{:08x} {}",
|
|
m_program.shader_hash, StageName(m_program.stage), pc, reason);
|
|
EXIT("%s", message.c_str());
|
|
std::abort();
|
|
}
|
|
|
|
Value LowerDescriptorPhi(Value value) {
|
|
value = value.Resolve();
|
|
const auto* phi = value.TryInstruction();
|
|
if (m_shader_writes || phi == nullptr || phi->GetOpcode() != ValueOpcode::Phi ||
|
|
phi->NumArgs() != 2u || phi->NumPhiBlocks() != 2u || phi->GetType() != Type::U32 ||
|
|
m_program.blocks.size() != m_program.block_info.size()) {
|
|
return value;
|
|
}
|
|
for (const auto& [original, selected]: m_descriptor_selections) {
|
|
if (original == phi) {
|
|
return selected;
|
|
}
|
|
}
|
|
const auto* merge = phi->Parent();
|
|
const auto* branch = phi->PhiBlock(0);
|
|
if (merge == nullptr || branch == nullptr || phi->PhiBlock(1) == nullptr ||
|
|
branch == phi->PhiBlock(1)) {
|
|
return value;
|
|
}
|
|
if (branch->ImmSuccessors().size() != 2u) {
|
|
if (branch->ImmPredecessors().size() != 1u) {
|
|
return value;
|
|
}
|
|
branch = branch->ImmPredecessors()[0];
|
|
}
|
|
if (branch == merge || branch->ImmSuccessors().size() != 2u) {
|
|
return value;
|
|
}
|
|
std::array<uint32_t, 2> target_ids;
|
|
for (uint32_t arm = 0; arm < 2; arm++) {
|
|
const auto* incoming = phi->PhiBlock(arm);
|
|
if (incoming == merge ||
|
|
(incoming != branch &&
|
|
(incoming->ImmPredecessors().size() != 1u ||
|
|
incoming->ImmPredecessors()[0] != branch ||
|
|
incoming->ImmSuccessors().size() != 1u ||
|
|
incoming->ImmSuccessors()[0] != merge))) {
|
|
return value;
|
|
}
|
|
const auto* target = incoming == branch ? merge : incoming;
|
|
const auto it = std::ranges::find(m_program.blocks, target);
|
|
if (it == m_program.blocks.end()) {
|
|
return value;
|
|
}
|
|
target_ids[arm] = m_program.block_info[it - m_program.blocks.begin()].id;
|
|
}
|
|
const auto branch_it = std::ranges::find(m_program.blocks, branch);
|
|
if (branch_it == m_program.blocks.end()) {
|
|
return value;
|
|
}
|
|
const auto& info = m_program.block_info[branch_it - m_program.blocks.begin()];
|
|
const auto& term = info.terminator;
|
|
if (term.kind != CFG::TerminatorKind::ConditionalBranch ||
|
|
!((term.true_block == target_ids[0] && term.false_block == target_ids[1]) ||
|
|
(term.false_block == target_ids[0] && term.true_block == target_ids[1])) ||
|
|
!ValidateRuntimeValue(m_program, info.condition, RuntimeValueType::Integer) ||
|
|
!ValidateRuntimeValue(m_program, phi->Arg(0)) ||
|
|
!ValidateRuntimeValue(m_program, phi->Arg(1))) {
|
|
return value;
|
|
}
|
|
// Retain a host expression; replacing the GPU Phi would break SSA dominance.
|
|
const auto true_arg = term.true_block == target_ids[0] ? 0u : 1u;
|
|
auto& selected = m_program.value_storage.emplace_back(ValueOpcode::SelectU32);
|
|
selected.SetArg(0, info.condition);
|
|
selected.SetArg(1, phi->Arg(true_arg));
|
|
selected.SetArg(2, phi->Arg(true_arg ^ 1u));
|
|
m_descriptor_selections.emplace_back(phi, Value(&selected));
|
|
return Value(&selected);
|
|
}
|
|
|
|
void MakeSource(const Inst& handle, uint32_t width, bool sampler, bool sample_adjust,
|
|
DescriptorSource& descriptor, uint32_t pc) {
|
|
if (handle.NumArgs() != width) {
|
|
Fail(pc, fmt::format("{} has {} descriptor dwords, expected {}",
|
|
ValueOpcodeName(handle.GetOpcode()), handle.NumArgs(), width));
|
|
}
|
|
descriptor.dword_count = width;
|
|
for (uint32_t i = 0; i < width; i++) {
|
|
descriptor.dwords[i] = LowerDescriptorPhi(handle.Arg(i));
|
|
}
|
|
if (sample_adjust) {
|
|
descriptor.dwords[3] = CanonicalizeSampleAdjustDword3(descriptor.dwords[3]);
|
|
}
|
|
const auto dword0 = descriptor.dwords[0].Resolve();
|
|
if (sampler && dword0.IsImmediate() && dword0.GetType() == Type::U32 &&
|
|
(dword0.U32() & SamplerBorderClampMask) == 0) {
|
|
// Border color and its table index are unused unless a clamp axis selects border mode.
|
|
descriptor.dwords[3] = Value(0u);
|
|
}
|
|
}
|
|
|
|
bool ValidateSource(const DescriptorSource& descriptor, uint32_t& bad_dword) const {
|
|
for (uint32_t i = 0; i < descriptor.dword_count; i++) {
|
|
bad_dword = i;
|
|
if (descriptor.dwords[i].Resolve().GetType() != Type::U32) {
|
|
return false;
|
|
}
|
|
if (!ValidateRuntimeValue(m_program, descriptor.dwords[i])) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
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 ||
|
|
current.indirect_image != descriptor.indirect_image) {
|
|
continue;
|
|
}
|
|
bool same = true;
|
|
for (uint32_t i = 0; i < descriptor.dword_count; i++) {
|
|
same = same && EquivalentValue(m_program, current.dwords[i], descriptor.dwords[i]);
|
|
}
|
|
if (same) {
|
|
return candidate;
|
|
}
|
|
}
|
|
m_sources.push_back(descriptor);
|
|
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_program.memory_info.size()) {
|
|
return nullptr;
|
|
}
|
|
const auto& memory = m_program.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_program.blocks) {
|
|
for (const auto& inst: *block) {
|
|
const auto op = inst.GetOpcode();
|
|
if ((BufferAccessOf(op) == BufferAccess::None &&
|
|
AddressOpcodeInfoOf(op).access == AddressAccess::None &&
|
|
ImageOpcodeInfoOf(op).access == ImageAccess::None) ||
|
|
&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) {
|
|
return false;
|
|
}
|
|
MakeSource(handle, 4u, false, false, descriptor, pc);
|
|
uint32_t bad_dword = 0;
|
|
if (!ValidateSource(descriptor, 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_program, heap_offset, heap_reads[dword]->Arg(1))) {
|
|
return false;
|
|
}
|
|
plan.memory[dword] = memory_index;
|
|
plan.reads[dword] = 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, 0u};
|
|
|
|
plan.handle = &handle;
|
|
plan.source = InternSource(image_source);
|
|
plan.key = Value(material_read);
|
|
plan.roots = image_source.dwords;
|
|
return true;
|
|
}
|
|
|
|
const IndirectImagePlan* FindIndirectImage(const Inst& handle) const {
|
|
const auto found =
|
|
std::find_if(m_indirect_images.begin(), m_indirect_images.end(),
|
|
[&](const IndirectImagePlan& plan) {
|
|
return plan.handle == &handle;
|
|
});
|
|
return found == m_indirect_images.end() ? nullptr : &*found;
|
|
}
|
|
|
|
bool IsIndirectPlanningMemory(uint32_t index) const {
|
|
return std::any_of(m_indirect_images.begin(), m_indirect_images.end(),
|
|
[&](const IndirectImagePlan& plan) {
|
|
return std::ranges::find(plan.memory, index) != plan.memory.end();
|
|
});
|
|
}
|
|
|
|
void PlanIndirectImages() {
|
|
for (auto* block: m_program.blocks) {
|
|
for (auto& inst: *block) {
|
|
if (ImageOpcodeInfoOf(inst.GetOpcode()).access == ImageAccess::None ||
|
|
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, bool sampler = false, bool sample_adjust = false) {
|
|
handle = value.Resolve().TryInstruction();
|
|
if (handle == nullptr || handle->GetOpcode() != expected) {
|
|
Fail(pc, fmt::format("memory operation requires {}", ValueOpcodeName(expected)));
|
|
}
|
|
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)) {
|
|
if (expected == ValueOpcode::GetBufferResource &&
|
|
std::all_of(descriptor.dwords.begin(), descriptor.dwords.begin() + width,
|
|
[](Value word) { return word.Resolve().GetType() == Type::U32; })) {
|
|
return false;
|
|
}
|
|
Fail(pc, fmt::format("{} dword {} is not a valid runtime value",
|
|
ValueOpcodeName(expected), bad_dword));
|
|
}
|
|
source = InternSource(descriptor);
|
|
return true;
|
|
}
|
|
|
|
void ValidateAddressHandle(Value value, uint32_t pc) const {
|
|
const auto* handle = value.Resolve().TryInstruction();
|
|
if (handle == nullptr || handle->GetOpcode() != ValueOpcode::GetAddressResource) {
|
|
Fail(pc, "address operation requires GetAddressResource");
|
|
}
|
|
if (handle->NumArgs() != 2) {
|
|
Fail(pc, "GetAddressResource must have two address dwords");
|
|
}
|
|
}
|
|
|
|
uint32_t AddBuffer(uint32_t source, const MemoryInfo& memory, ValueOpcode op, uint32_t pc) {
|
|
for (uint32_t i = 0; i < m_info.buffers.size(); i++) {
|
|
if (m_info.buffers[i].source == source) {
|
|
Merge(m_info.buffers[i], memory, op, pc);
|
|
return i;
|
|
}
|
|
}
|
|
if (m_info.buffers.size() >= ShaderInfo::MaxBuffers) {
|
|
return UINT32_MAX;
|
|
}
|
|
BufferResource resource;
|
|
resource.source = source;
|
|
resource.first_use_pc = pc;
|
|
Merge(resource, memory, op, pc);
|
|
m_info.buffers.push_back(resource);
|
|
return static_cast<uint32_t>(m_info.buffers.size() - 1);
|
|
}
|
|
|
|
static void Merge(BufferResource& resource, const MemoryInfo& memory, ValueOpcode op,
|
|
uint32_t pc) {
|
|
const auto access = BufferAccessOf(op);
|
|
const bool atomic = access == BufferAccess::Atomic;
|
|
const bool write = access == BufferAccess::Write || atomic;
|
|
resource.first_use_pc = std::min(resource.first_use_pc, pc);
|
|
resource.max_byte_extent = std::max(resource.max_byte_extent, ByteExtent(memory));
|
|
resource.read = resource.read || !write || atomic;
|
|
resource.written = resource.written || write;
|
|
resource.atomic = resource.atomic || atomic;
|
|
resource.formatted = resource.formatted || memory.formatted;
|
|
resource.scalar = resource.scalar || op == ValueOpcode::ReadConstBuffer ||
|
|
memory.kind == ResourceKind::ScalarBuffer;
|
|
}
|
|
|
|
uint32_t AddImage(uint32_t source, const MemoryInfo& memory, ValueOpcode op, uint32_t pc) {
|
|
const auto resource_class = ImageOpcodeInfoOf(op).resource_class;
|
|
const auto mip = resource_class == ImageResourceClass::Storage && memory.image_has_mip
|
|
? ImageMipMode::DynamicStorage
|
|
: ImageMipMode::None;
|
|
const bool depth = (memory.image_sample_flags & Decoder::ImageSampleFlagCompare) != 0;
|
|
for (uint32_t i = 0; i < m_info.images.size(); i++) {
|
|
auto& image = m_info.images[i];
|
|
if (image.source == source && image.resource_class == resource_class &&
|
|
image.dimension == memory.image_dimension && image.mip_mode == mip &&
|
|
image.depth_compare == depth && image.r128 == memory.image_r128) {
|
|
Merge(image, op, pc);
|
|
return i;
|
|
}
|
|
}
|
|
if (m_info.images.size() >= ShaderInfo::MaxImages) {
|
|
return UINT32_MAX;
|
|
}
|
|
ImageResource image;
|
|
image.source = source;
|
|
image.first_use_pc = pc;
|
|
image.resource_class = resource_class;
|
|
image.dimension = memory.image_dimension;
|
|
image.mip_mode = mip;
|
|
image.depth_compare = depth;
|
|
image.r128 = memory.image_r128;
|
|
Merge(image, op, pc);
|
|
m_info.images.push_back(image);
|
|
return static_cast<uint32_t>(m_info.images.size() - 1);
|
|
}
|
|
|
|
static void Merge(ImageResource& image, ValueOpcode op, uint32_t pc) {
|
|
const auto access = ImageOpcodeInfoOf(op).access;
|
|
const bool atomic = access == ImageAccess::Atomic;
|
|
const bool write = access == ImageAccess::Write || atomic;
|
|
image.first_use_pc = std::min(image.first_use_pc, pc);
|
|
image.read = image.read || !write || atomic;
|
|
image.written = image.written || write;
|
|
image.atomic = image.atomic || atomic;
|
|
}
|
|
|
|
uint32_t AddSampler(uint32_t source, uint32_t pc) {
|
|
for (uint32_t i = 0; i < m_info.samplers.size(); i++) {
|
|
if (m_info.samplers[i].source == source) {
|
|
m_info.samplers[i].first_use_pc = std::min(m_info.samplers[i].first_use_pc, pc);
|
|
return i;
|
|
}
|
|
}
|
|
if (m_info.samplers.size() >= ShaderInfo::MaxSamplers) {
|
|
return UINT32_MAX;
|
|
}
|
|
m_info.samplers.push_back({source, pc});
|
|
return static_cast<uint32_t>(m_info.samplers.size() - 1);
|
|
}
|
|
|
|
void AddSampledPair(uint32_t image, uint32_t sampler, uint32_t pc) {
|
|
for (auto& pair: m_info.sampled_pairs) {
|
|
if (pair.image == image && pair.sampler == sampler) {
|
|
pair.first_use_pc = std::min(pair.first_use_pc, pc);
|
|
return;
|
|
}
|
|
}
|
|
if (m_info.sampled_pairs.size() >= ShaderInfo::MaxSampledPairs) {
|
|
Fail(pc, "sampled image/sampler pair limit exceeded");
|
|
}
|
|
m_info.sampled_pairs.push_back({image, sampler, pc});
|
|
}
|
|
|
|
void AddHandlePatch(Inst* handle, uint32_t resource, uint32_t pc) {
|
|
for (const auto& patch: m_handle_patches) {
|
|
if (patch.handle == handle) {
|
|
if (patch.resource != resource) {
|
|
Fail(pc, fmt::format("{} is reused with incompatible resource classes",
|
|
ValueOpcodeName(handle->GetOpcode())));
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
m_handle_patches.push_back({handle, resource});
|
|
}
|
|
|
|
void AddMemoryPatch(uint32_t index, uint32_t resource, uint32_t sampler, bool has_sampler,
|
|
uint32_t pc) {
|
|
for (auto& patch: m_memory_patches) {
|
|
if (patch.index != index) {
|
|
continue;
|
|
}
|
|
if (patch.resource != resource ||
|
|
(has_sampler && patch.has_sampler && patch.sampler != sampler)) {
|
|
Fail(pc, "memory metadata is reused with incompatible resources");
|
|
}
|
|
if (has_sampler) {
|
|
patch.sampler = sampler;
|
|
patch.has_sampler = true;
|
|
}
|
|
return;
|
|
}
|
|
m_memory_patches.push_back({index, resource, sampler, has_sampler});
|
|
}
|
|
|
|
void Collect(Inst& inst) {
|
|
const auto op = inst.GetOpcode();
|
|
const auto buffer = BufferAccessOf(op);
|
|
const auto address_info = AddressOpcodeInfoOf(op);
|
|
const auto image_info = ImageOpcodeInfoOf(op);
|
|
if (buffer == BufferAccess::None && address_info.access == AddressAccess::None &&
|
|
image_info.access == ImageAccess::None) {
|
|
return;
|
|
}
|
|
const auto flags = inst.Flags<MemoryFlags>();
|
|
if (flags.index >= m_program.memory_info.size()) {
|
|
Fail(flags.pc, fmt::format("memory metadata index {} is out of range", flags.index));
|
|
}
|
|
if (inst.NumArgs() == 0) {
|
|
Fail(flags.pc, "memory operation has no resource handle");
|
|
}
|
|
const auto& memory = m_program.memory_info[flags.index];
|
|
if (memory.planning_only || IsIndirectPlanningMemory(flags.index)) {
|
|
return;
|
|
}
|
|
Inst* handle = nullptr;
|
|
uint32_t source = 0;
|
|
uint32_t resource = 0;
|
|
|
|
if (buffer != BufferAccess::None) {
|
|
if (!GetHandle(inst.Arg(0), ValueOpcode::GetBufferResource, 4, flags.pc, handle,
|
|
source)) {
|
|
if (memory.kind != ResourceKind::Buffer || memory.formatted || memory.typed ||
|
|
(op != ValueOpcode::LoadBufferU32x2 && op != ValueOpcode::LoadBufferU32x4)) {
|
|
Fail(flags.pc,
|
|
"buffer descriptor is not a valid runtime value; GPU-selected access "
|
|
"requires a raw DWORD x2/x4 load");
|
|
}
|
|
m_program.memory_info[flags.index].kind = ResourceKind::IndirectBuffer;
|
|
m_info.uses_dma = true;
|
|
return;
|
|
}
|
|
resource = AddBuffer(source, memory, op, flags.pc);
|
|
if (resource == UINT32_MAX) {
|
|
Fail(flags.pc, "buffer resource limit exceeded");
|
|
}
|
|
AddHandlePatch(handle, resource, flags.pc);
|
|
AddMemoryPatch(flags.index, resource, 0, false, flags.pc);
|
|
return;
|
|
}
|
|
if (address_info.access != AddressAccess::None) {
|
|
if (!IsAddressResourceKind(memory.kind)) {
|
|
Fail(flags.pc, "address operation has invalid resource kind");
|
|
}
|
|
if (memory.kind == ResourceKind::Scratch) {
|
|
handle = inst.Arg(0).Resolve().TryInstruction();
|
|
if (handle == nullptr || handle->GetOpcode() != ValueOpcode::GetScratchResource ||
|
|
handle->NumArgs() != 0) {
|
|
Fail(flags.pc, "scratch operation requires GetScratchResource");
|
|
}
|
|
if (m_program.scratch_dwords == 0) {
|
|
Fail(flags.pc, "scratch operation requires a nonzero AGC per-thread size");
|
|
}
|
|
return;
|
|
}
|
|
ValidateAddressHandle(inst.Arg(0), flags.pc);
|
|
m_info.uses_dma = true;
|
|
return;
|
|
}
|
|
|
|
if (memory.kind != ResourceKind::Image ||
|
|
image_info.resource_class == ImageResourceClass::None) {
|
|
Fail(flags.pc, "image operation has invalid resource kind");
|
|
}
|
|
handle = inst.Arg(0).Resolve().TryInstruction();
|
|
const auto* indirect = handle != nullptr ? FindIndirectImage(*handle) : nullptr;
|
|
if (indirect != nullptr) {
|
|
source = indirect->source;
|
|
} else {
|
|
GetHandle(inst.Arg(0), ValueOpcode::GetImageResource, 8, flags.pc, handle, source);
|
|
}
|
|
resource = AddImage(source, memory, op, flags.pc);
|
|
if (resource == UINT32_MAX) {
|
|
Fail(flags.pc, "image resource limit exceeded");
|
|
}
|
|
AddHandlePatch(handle, resource, flags.pc);
|
|
uint32_t sampler = 0;
|
|
if (image_info.needs_sampler) {
|
|
if (inst.NumArgs() < 2) {
|
|
Fail(flags.pc, "sampled image operation has no sampler handle");
|
|
}
|
|
Inst* sampler_handle = nullptr;
|
|
uint32_t sampler_source = 0;
|
|
const bool sample_adjust =
|
|
(memory.image_sample_flags & Decoder::ImageSampleFlagAdjust) != 0;
|
|
GetHandle(inst.Arg(1), ValueOpcode::GetSamplerResource, 4, flags.pc, sampler_handle,
|
|
sampler_source, true, sample_adjust);
|
|
sampler = AddSampler(sampler_source, flags.pc);
|
|
if (sampler == UINT32_MAX) {
|
|
Fail(flags.pc, "sampler resource limit exceeded");
|
|
}
|
|
AddHandlePatch(sampler_handle, sampler, flags.pc);
|
|
AddSampledPair(resource, sampler, flags.pc);
|
|
}
|
|
AddMemoryPatch(flags.index, resource, sampler, image_info.needs_sampler, flags.pc);
|
|
}
|
|
|
|
const DescriptorSource* Source(uint32_t source) const {
|
|
return source < m_sources.size() ? &m_sources[source] : nullptr;
|
|
}
|
|
|
|
void LinkImageAliases() {
|
|
for (auto& buffer: m_info.buffers) {
|
|
const auto* buffer_source = Source(buffer.source);
|
|
if (buffer_source == nullptr || buffer_source->dword_count != 4) {
|
|
continue;
|
|
}
|
|
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 ||
|
|
image_source->indirect_image.has_value()) {
|
|
continue;
|
|
}
|
|
bool alias = true;
|
|
for (uint32_t dword = 0; dword < 4; dword++) {
|
|
alias = alias && EquivalentValue(m_program, buffer_source->dwords[dword],
|
|
image_source->dwords[dword]);
|
|
}
|
|
if (alias) {
|
|
buffer.image_alias = image;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Program& m_program;
|
|
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;
|
|
std::vector<std::pair<const Inst*, Value>> m_descriptor_selections;
|
|
bool m_shader_writes = false;
|
|
};
|
|
|
|
} // namespace
|
|
|
|
void TrackResources(Program& program) {
|
|
Tracker(program).Run();
|
|
}
|
|
|
|
} // namespace Libs::Graphics::ShaderRecompiler::IR
|