Preserve uniform conditional sampler choices in resource plans

Related work: https://github.com/KytyPS5/KytyPS5/pull/558

Co-authored-by: Çağatay Güçlü <33038942+defektu@users.noreply.github.com>
This commit is contained in:
nmzik
2026-09-16 17:45:19 +04:00
parent 349fae00d9
commit af3a3f4bf6
6 changed files with 230 additions and 29 deletions
@@ -112,6 +112,10 @@ ResourcePlan& ResourcePlan::operator=(ResourcePlan&& other) noexcept {
}
Program::~Program() {
// Planning expressions can refer to block values but outlive block storage in the base class.
for (auto& inst: value_storage) {
inst.Invalidate();
}
// 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.
for (auto* block: blocks) {
@@ -184,6 +188,22 @@ Value ResolveInvariantPhi(const ResourcePlan& program, Value value) {
return invariant;
}
bool HasShaderMemoryWrites(const Program& program) {
for (const auto* block: program.blocks) {
for (const auto& inst: *block) {
const auto op = inst.GetOpcode();
const auto buffer = BufferAccessOf(op);
const auto image = ImageOpcodeInfoOf(op).access;
if (buffer == BufferAccess::Write || buffer == BufferAccess::Atomic ||
image == ImageAccess::Write || image == ImageAccess::Atomic ||
AddressOpcodeInfoOf(op).access == AddressAccess::Write) {
return true;
}
}
}
return false;
}
void ValidateProgram(const Program& program, bool require_ssa) {
if (program.blocks.size() != program.block_info.size() ||
program.blocks.size() != program.block_storage.size()) {
@@ -563,6 +563,7 @@ struct Program: ResourcePlan {
};
std::string ProgramToString(const Program& program);
bool HasShaderMemoryWrites(const Program& program);
void ValidateProgram(const Program& program, bool require_ssa);
void ResolveControlFlowIdentities(Program& program);
@@ -647,7 +647,8 @@ bool BuildSamplerPlan(const ShaderInfo& base, const Images& images, SamplerPlan&
}
static std::vector<ResourceBlock> ResourceControlFlow(const Program& program) {
if (program.blocks.size() != program.block_info.size()) {
// Any shader write may alias a scalar predicate read, including on a later loop visit.
if (program.blocks.size() != program.block_info.size() || HasShaderMemoryWrites(program)) {
return {};
}
std::unordered_map<uint32_t, uint32_t> indices;
@@ -687,12 +688,6 @@ static std::vector<ResourceBlock> ResourceControlFlow(const Program& program) {
const auto op = inst.GetOpcode();
const auto buffer = BufferAccessOf(op);
const auto image = ImageOpcodeInfoOf(op);
// Any shader write may alias a scalar predicate read, including on a later loop visit.
if (buffer == BufferAccess::Write || buffer == BufferAccess::Atomic ||
image.access == ImageAccess::Write || image.access == ImageAccess::Atomic ||
AddressOpcodeInfoOf(op).access == AddressAccess::Write) {
return {};
}
if (buffer == BufferAccess::None && image.access == ImageAccess::None) {
continue;
}
@@ -88,6 +88,7 @@ public:
m_info.samplers.clear();
m_info.sampled_pairs.clear();
m_info.uses_dma = false;
m_shader_writes = HasShaderMemoryWrites(program);
}
void Run() {
@@ -168,15 +169,71 @@ private:
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();
for (uint32_t first = 0; first < 2; first++) {
const auto* branch = phi->PhiBlock(first);
const auto* alternate = phi->PhiBlock(first ^ 1u);
if (branch == nullptr || alternate == nullptr || merge == nullptr || branch == merge ||
alternate == merge || branch == alternate || branch->ImmSuccessors().size() != 2u ||
alternate->ImmPredecessors().size() != 1u ||
alternate->ImmPredecessors()[0] != branch ||
alternate->ImmSuccessors().size() != 1u || alternate->ImmSuccessors()[0] != merge) {
continue;
}
const auto branch_it = std::ranges::find(m_program.blocks, branch);
const auto alternate_it = std::ranges::find(m_program.blocks, alternate);
const auto merge_it = std::ranges::find(m_program.blocks, merge);
if (branch_it == m_program.blocks.end() || alternate_it == m_program.blocks.end() ||
merge_it == m_program.blocks.end()) {
continue;
}
const auto& info = m_program.block_info[branch_it - m_program.blocks.begin()];
const auto alternate_id =
m_program.block_info[alternate_it - m_program.blocks.begin()].id;
const auto merge_id = m_program.block_info[merge_it - m_program.blocks.begin()].id;
const auto& term = info.terminator;
if (term.kind != CFG::TerminatorKind::ConditionalBranch ||
!((term.true_block == merge_id && term.false_block == alternate_id) ||
(term.false_block == merge_id && term.true_block == alternate_id)) ||
!ValidateRuntimeValue(m_program, info.condition, RuntimeValueType::Integer) ||
!ValidateRuntimeValue(m_program, phi->Arg(0)) ||
!ValidateRuntimeValue(m_program, phi->Arg(1))) {
continue;
}
// Retain a host expression; replacing the GPU Phi would break SSA dominance.
const auto true_arg = term.true_block == merge_id ? first : first ^ 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);
}
return value;
}
void MakeSource(const Inst& handle, uint32_t width, bool sampler, bool sample_adjust,
DescriptorSource& descriptor, uint32_t pc) const {
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] = handle.Arg(i).Resolve();
descriptor.dwords[i] = LowerDescriptorPhi(handle.Arg(i));
}
if (sample_adjust) {
descriptor.dwords[3] = CanonicalizeSampleAdjustDword3(descriptor.dwords[3]);
@@ -744,12 +801,14 @@ private:
}
}
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;
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
@@ -16,6 +16,13 @@ namespace {
constexpr uint64_t AddressMask = 0x0000ffffffffffffull;
SrtRuntime CleanRuntime(SrtRuntime runtime) {
runtime.read_memory = runtime.read_specialization_memory != nullptr
? runtime.read_specialization_memory
: +[](void*, uint64_t, uint32_t*) { return false; };
return runtime;
}
const char* StageName(ShaderType stage) {
switch (stage) {
case ShaderType::Vertex: return "vertex";
@@ -646,8 +653,10 @@ private:
case ValueOpcode::GetShaderBase: result = m_runtime.shader_base; return true;
case ValueOpcode::Phi: return EvaluatePhi(inst, result);
case ValueOpcode::ReadFirstLane: {
Evaluator active(m_program, m_runtime, m_clean_flat_slots, m_clean_evaluator,
inst.Arg(1));
const auto clean_runtime = CleanRuntime(m_runtime);
Evaluator clean_active(m_program, clean_runtime, {}, nullptr, inst.Arg(1));
Evaluator active(m_program, m_runtime, m_clean_flat_slots, &clean_active,
inst.Arg(1));
return active.EvaluateWide(inst.Arg(0), result);
}
case ValueOpcode::BitCastU32F32:
@@ -892,12 +901,13 @@ private:
}
case ValueOpcode::SelectU32:
case ValueOpcode::SelectU1:
case ValueOpcode::SelectF32:
if (ternary()) {
result = a != 0u ? b : c;
return true;
case ValueOpcode::SelectF32: {
auto& predicate = m_clean_evaluator != nullptr ? *m_clean_evaluator : *this;
if (predicate.EvaluateWide(inst.Arg(0), a)) {
return Arg(inst, a != 0u ? 1u : 2u, result);
}
return false;
}
case ValueOpcode::IEqual32:
if (binary()) {
result = static_cast<uint32_t>(a) == static_cast<uint32_t>(b);
@@ -977,7 +987,7 @@ bool EvaluateRuntimeSourcesImpl(const ResourcePlan& program, std::span<const uin
const SrtRuntime& runtime, std::vector<DescriptorValue>& results,
std::vector<uint32_t>& flat, bool evaluate_flat,
std::span<const uint8_t> clean_flat_slots,
std::vector<uint8_t>& active_sources) {
std::vector<uint8_t>& active_sources) {
if (!program.srt_plan_complete) {
return false;
}
@@ -985,8 +995,7 @@ bool EvaluateRuntimeSourcesImpl(const ResourcePlan& program, std::span<const uin
runtime.read_specialization_memory == nullptr) {
return false;
}
SrtRuntime clean_runtime = runtime;
clean_runtime.read_memory = runtime.read_specialization_memory;
const auto clean_runtime = CleanRuntime(runtime);
Evaluator clean_evaluator(program, clean_runtime);
Evaluator evaluator(program, runtime, clean_flat_slots, &clean_evaluator);
std::vector<uint8_t> active;
@@ -1081,11 +1090,8 @@ bool EvaluateUniformValues(const ResourcePlan& program, std::span<const Value> v
if (values.size() != results.size()) {
return false;
}
auto clean = runtime;
clean.read_memory = runtime.read_specialization_memory != nullptr
? runtime.read_specialization_memory
: +[](void*, uint64_t, uint32_t*) { return false; };
Evaluator evaluator(program, clean);
const auto clean = CleanRuntime(runtime);
Evaluator evaluator(program, clean);
for (size_t i = 0; i < values.size(); ++i) {
if (!evaluator.Evaluate(values[i], results[i])) {
return false;