mirror of
https://github.com/KytyPS5/KytyPS5.git
synced 2026-10-20 19:07:01 +02:00
shader: share finite descriptor planning with GPU-selected inline samplers
This commit is contained in:
@@ -860,7 +860,11 @@ CompileResult CompileProgram(TranslateResult translated, const CompileOptions& o
|
||||
const auto resource = inst.Flags<uint32_t>();
|
||||
first = resource < ir.info.images.size() &&
|
||||
ir.info.images[resource].indirect_root == resource ? 1u : 0u;
|
||||
} else if (op != IR::ValueOpcode::GetSamplerResource) {
|
||||
} else if (op == IR::ValueOpcode::GetSamplerResource) {
|
||||
const auto resource = inst.Flags<uint32_t>();
|
||||
first = resource < ir.info.samplers.size() &&
|
||||
!ir.info.samplers[resource].indirect_resources.empty() ? 1u : 0u;
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
for (size_t index = first; index < inst.NumArgs(); index++) {
|
||||
|
||||
@@ -137,6 +137,15 @@ uint32_t ImageViewSizeType(EmitterState& state, ImageDimension dimension) {
|
||||
}
|
||||
}
|
||||
|
||||
namespace {
|
||||
void DecorateNonUniformDescriptor(EmitterState& state, uint32_t id) {
|
||||
state.builder.RequireExtension("SPV_EXT_descriptor_indexing");
|
||||
state.builder.RequireCapability(spv::CapabilityShaderNonUniform);
|
||||
state.builder.RequireCapability(spv::CapabilitySampledImageArrayNonUniformIndexing);
|
||||
state.builder.AddAnnotation(spv::OpDecorate, id, spv::DecorationNonUniform);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
uint32_t LoadImageDescriptor(EmitterState& state, uint32_t resource, uint32_t mip,
|
||||
uint32_t array_index) {
|
||||
const auto pointer = ImageDescriptorPointer(state, resource, mip, array_index);
|
||||
@@ -145,29 +154,28 @@ uint32_t LoadImageDescriptor(EmitterState& state, uint32_t resource, uint32_t mi
|
||||
state.builder.AddFunction(spv::OpLoad, state.images[IR::ImageBindingIndex(kind)].type,
|
||||
image, pointer);
|
||||
if (array_index != 0u) {
|
||||
state.builder.RequireExtension("SPV_EXT_descriptor_indexing");
|
||||
state.builder.RequireCapability(spv::CapabilityShaderNonUniform);
|
||||
state.builder.RequireCapability(spv::CapabilitySampledImageArrayNonUniformIndexing);
|
||||
state.builder.AddAnnotation(spv::OpDecorate, image, spv::DecorationNonUniform);
|
||||
DecorateNonUniformDescriptor(state, image);
|
||||
}
|
||||
return image;
|
||||
}
|
||||
|
||||
uint32_t LoadSamplerDescriptor(EmitterState& state, uint32_t sampler) {
|
||||
uint32_t LoadSamplerDescriptor(EmitterState& state, uint32_t sampler, uint32_t array_index) {
|
||||
if (sampler >= state.program.info.samplers.size()) {
|
||||
ExitDescriptorBindingFailure(state, IR::DescriptorBindingKind::Samplers, sampler,
|
||||
"sampler resource index is out of range");
|
||||
}
|
||||
const auto pointer = DescriptorElementPointer(
|
||||
state, state.sampler_pointer_type, state.sampler_variable, ConstantU32(state, sampler),
|
||||
state, state.sampler_pointer_type, state.sampler_variable,
|
||||
array_index == 0u ? ConstantU32(state, sampler) : array_index,
|
||||
IR::DescriptorBindingKind::Samplers, sampler, "sampler descriptor array was not emitted");
|
||||
const auto sampler_id = state.builder.AllocateId();
|
||||
state.builder.AddFunction(spv::OpLoad, state.sampler_type, sampler_id, pointer);
|
||||
if (array_index != 0u) DecorateNonUniformDescriptor(state, sampler_id);
|
||||
return sampler_id;
|
||||
}
|
||||
|
||||
uint32_t MakeSampledImage(EmitterState& state, uint32_t resource, uint32_t sampler_id, uint32_t mip,
|
||||
uint32_t array_index) {
|
||||
uint32_t array_index, bool sampler_dynamic) {
|
||||
const auto& image_resource = state.program.info.images.at(resource);
|
||||
EXIT_IF(image_resource.resource_class != IR::ImageResourceClass::Sampled);
|
||||
const auto image = LoadImageDescriptor(state, resource, mip, array_index);
|
||||
@@ -179,8 +187,8 @@ uint32_t MakeSampledImage(EmitterState& state, uint32_t resource, uint32_t sampl
|
||||
spv::OpTypeSampledImage, state.images[IR::ImageBindingIndex(kind)].type);
|
||||
}
|
||||
state.builder.AddFunction(spv::OpSampledImage, sampled_type, sampled_image, image, sampler_id);
|
||||
if (array_index != 0u) {
|
||||
state.builder.AddAnnotation(spv::OpDecorate, sampled_image, spv::DecorationNonUniform);
|
||||
if (array_index != 0u || sampler_dynamic) {
|
||||
DecorateNonUniformDescriptor(state, sampled_image);
|
||||
}
|
||||
return sampled_image;
|
||||
}
|
||||
|
||||
@@ -898,14 +898,25 @@ void EmitImage(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
operands[1] = AddressF32(ctx, mem, *address, layout.bias);
|
||||
operand_count = 2;
|
||||
}
|
||||
const auto sampler_id = LoadSamplerDescriptor(state, mem.sampler);
|
||||
const auto& samplers = state.program.info.samplers.at(mem.sampler).indirect_resources;
|
||||
uint32_t sampler_index = 0;
|
||||
if (!samplers.empty()) {
|
||||
const auto* handle = inst.Arg(1).ResolveInstruction();
|
||||
if (handle == nullptr || handle->GetOpcode() != IR::ValueOpcode::GetSamplerResource)
|
||||
ctx.Fail(inst, "has invalid finite sampler key provenance");
|
||||
sampler_index = EmitIndexSwitch(
|
||||
state, ctx.Def(handle->Arg(0)), static_cast<uint32_t>(samplers.size()),
|
||||
TypeU32(state), [&](uint32_t ordinal) { return ConstantU32(state, samplers[ordinal]); });
|
||||
}
|
||||
const auto sampler_id = LoadSamplerDescriptor(state, mem.sampler, sampler_index);
|
||||
const auto EmitSample = [&](uint32_t resource, uint32_t array_index) {
|
||||
const auto& candidate = state.program.info.images[resource];
|
||||
const auto coord =
|
||||
CoordF32(ctx, mem, *address, layout.coord,
|
||||
ImageDimensionInfoFor(candidate.dimension).coordinate_components,
|
||||
candidate.cube);
|
||||
const auto sampled = MakeSampledImage(state, resource, sampler_id, 0u, array_index);
|
||||
const auto sampled = MakeSampledImage(state, resource, sampler_id, 0u, array_index,
|
||||
sampler_index != 0u);
|
||||
const auto sample = state.builder.AllocateId();
|
||||
state.builder.AddFunction(opcode, result_type, sample, sampled, coord,
|
||||
std::span(&dref_value, dref ? 1u : 0u),
|
||||
|
||||
@@ -322,10 +322,11 @@ uint32_t ImageDescriptorPointer(EmitterState& state, uint32_t resource, uint32_t
|
||||
uint32_t LoadImageDescriptor(EmitterState& state, uint32_t resource, uint32_t mip = 0,
|
||||
uint32_t array_index = 0);
|
||||
|
||||
uint32_t LoadSamplerDescriptor(EmitterState& state, uint32_t sampler);
|
||||
uint32_t LoadSamplerDescriptor(EmitterState& state, uint32_t sampler, uint32_t array_index = 0);
|
||||
|
||||
uint32_t MakeSampledImage(EmitterState& state, uint32_t resource, uint32_t sampler_id,
|
||||
uint32_t mip = 0, uint32_t array_index = 0);
|
||||
uint32_t mip = 0, uint32_t array_index = 0,
|
||||
bool sampler_dynamic = false);
|
||||
|
||||
void EmitStorageImageWrite(EmitterState& state, uint32_t resource, uint32_t mip_lod, uint32_t coord,
|
||||
uint32_t texel);
|
||||
|
||||
@@ -161,6 +161,7 @@ struct SamplerResource {
|
||||
uint32_t first_use_pc = 0;
|
||||
// Native filtering/border variants share the original sampler's runtime descriptor.
|
||||
uint32_t snapshot_index = 0;
|
||||
std::vector<uint32_t> indirect_resources;
|
||||
bool force_point_filtering = false;
|
||||
bool depth_compare = false;
|
||||
bool integer_border = false;
|
||||
|
||||
@@ -635,6 +635,10 @@ bool BuildSamplerPlan(const ShaderInfo& base, SamplerPlan& plan) {
|
||||
return false;
|
||||
}
|
||||
usage[pair.sampler] |= 1u << static_cast<uint32_t>(ClassifySampler(base.images[pair.image]));
|
||||
for (const auto candidate: base.samplers[pair.sampler].indirect_resources) {
|
||||
if (candidate >= base.samplers.size()) return false;
|
||||
usage[candidate] |= usage[pair.sampler];
|
||||
}
|
||||
}
|
||||
for (uint32_t index = 0; index < base.samplers.size(); index++) {
|
||||
auto& mapping = plan.mapping[index];
|
||||
@@ -1224,7 +1228,14 @@ bool MaterializeResources(const ResourcePlan& program, const SrtRuntime& runtime
|
||||
}
|
||||
snapshot.samplers.resize(program.info.samplers.size());
|
||||
for (uint32_t i = 0; i < program.info.samplers.size(); ++i) {
|
||||
if (!evaluate(program.info.samplers[i].source, snapshot.samplers[i])) {
|
||||
auto source = program.info.samplers[i].source;
|
||||
if (source >= program.descriptor_sources.size()) return false;
|
||||
const auto& descriptor = program.descriptor_sources[source];
|
||||
if (descriptor.indirect_descriptor) {
|
||||
if (descriptor.indirect_descriptor->sources.empty()) return false;
|
||||
source = descriptor.indirect_descriptor->sources[0];
|
||||
}
|
||||
if (!evaluate(source, snapshot.samplers[i])) {
|
||||
return false;
|
||||
}
|
||||
if (program.info.samplers[i].gather_lod) {
|
||||
@@ -1316,11 +1327,20 @@ void ApplyResourceSpecialization(Program& program, const ResourceSpecialization&
|
||||
samplers[index].force_point_filtering = binding.type == SamplerClass::PointInteger;
|
||||
samplers[index].integer_border = binding.type != SamplerClass::Float;
|
||||
}
|
||||
for (uint32_t index = 0; index < sampler_plan.sampler_count; index++) {
|
||||
const auto& binding = sampler_plan.bindings[index];
|
||||
for (auto& candidate: samplers[index].indirect_resources) {
|
||||
candidate = sampler_plan.mapping[candidate][static_cast<uint32_t>(binding.type)];
|
||||
EXIT_IF(candidate == UINT32_MAX);
|
||||
}
|
||||
}
|
||||
for (auto& pair: sampled_pairs) {
|
||||
const auto type = static_cast<uint32_t>(ClassifySampler(images[pair.image]));
|
||||
pair.sampler = sampler_plan.mapping[pair.sampler][type];
|
||||
EXIT_IF(pair.sampler == UINT32_MAX);
|
||||
samplers[pair.sampler].depth_compare |= images[pair.image].depth_compare;
|
||||
for (const auto candidate: samplers[pair.sampler].indirect_resources)
|
||||
samplers[candidate].depth_compare |= images[pair.image].depth_compare;
|
||||
}
|
||||
|
||||
auto& memory_info = program.memory_info;
|
||||
|
||||
@@ -430,7 +430,7 @@ public:
|
||||
plan.handle->SetArg(word, Value(0u));
|
||||
continue;
|
||||
}
|
||||
if (plan.handle->GetOpcode() == ValueOpcode::GetBufferResource) {
|
||||
if (plan.handle->NumArgs() == 4u) {
|
||||
plan.handle->SetArg(0, plan.key);
|
||||
for (uint32_t word = 1; word < 4u; ++word) plan.handle->SetArg(word, Value(0u));
|
||||
} else {
|
||||
@@ -1944,17 +1944,21 @@ private:
|
||||
return found;
|
||||
}
|
||||
|
||||
bool TryMakeFiniteImage(Inst& handle, IndirectDescriptorPlan& plan) {
|
||||
if (handle.GetOpcode() != ValueOpcode::GetImageResource || handle.NumArgs() != 8u)
|
||||
bool TryMakeFiniteDescriptor(Inst& handle, IndirectDescriptorPlan& plan) {
|
||||
const bool sampler = handle.GetOpcode() == ValueOpcode::GetSamplerResource;
|
||||
if ((!sampler && handle.GetOpcode() != ValueOpcode::GetImageResource) ||
|
||||
handle.NumArgs() != (sampler ? 4u : 8u))
|
||||
return false;
|
||||
if (std::ranges::any_of(handle.Uses(), [](const Use& use) {
|
||||
if (std::ranges::any_of(handle.Uses(), [&](const Use& use) {
|
||||
const auto op = use.user->GetOpcode();
|
||||
return op != ValueOpcode::ImageSampleRaw && op != ValueOpcode::ImageGatherRaw;
|
||||
if (op != ValueOpcode::ImageSampleRaw) return sampler || op != ValueOpcode::ImageGatherRaw;
|
||||
return sampler && (m_program.memory_info[use.user->Flags<MemoryFlags>().index]
|
||||
.image_sample_flags & Decoder::ImageSampleFlagAdjust) != 0u;
|
||||
})) return false;
|
||||
bool has_phi = false;
|
||||
for (size_t word = 0; word < handle.NumArgs(); ++word) has_phi |= handle.Arg(word).Resolve().IsPhi();
|
||||
if (!has_phi) return false;
|
||||
// Normalize the eight synchronized descriptor words to one GPU ordinal.
|
||||
// Normalize synchronized descriptor words to one GPU ordinal.
|
||||
// Build the complete graph before changing IR so an unsupported leaf is transactional.
|
||||
struct Choice {
|
||||
DescriptorSource descriptor;
|
||||
@@ -1970,7 +1974,15 @@ private:
|
||||
const auto index = static_cast<uint32_t>(choices.size());
|
||||
choices.push_back({descriptor});
|
||||
uint32_t bad = 0;
|
||||
if (ValidateSource(descriptor, bad)) return index;
|
||||
if (ValidateSource(descriptor, bad)) {
|
||||
// Finite inline samplers have literal leaves; live descriptor tables use the
|
||||
// existing direct sampler planning path.
|
||||
if (sampler && !std::ranges::all_of(
|
||||
std::span(descriptor.dwords).first(descriptor.dword_count),
|
||||
[](Value word) { return word.IsImmediate() && word.GetType() == Type::U32; }))
|
||||
return UINT32_MAX;
|
||||
return index;
|
||||
}
|
||||
const auto* branch = descriptor.dwords[bad].TryInstruction();
|
||||
if (branch == nullptr || branch->Parent() == nullptr ||
|
||||
branch->GetOpcode() != ValueOpcode::Phi ||
|
||||
@@ -2007,14 +2019,14 @@ private:
|
||||
return index;
|
||||
};
|
||||
DescriptorSource root;
|
||||
root.dword_count = 8u;
|
||||
root.dword_count = handle.NumArgs();
|
||||
for (uint32_t word = 0; word < root.dword_count; ++word) root.dwords[word] = handle.Arg(word);
|
||||
if (visit(visit, root) == UINT32_MAX || choices.front().branch == nullptr) return false;
|
||||
DescriptorSource image_source;
|
||||
image_source.dword_count = 8u;
|
||||
image_source.dwords.fill(Value(0u));
|
||||
image_source.indirect_descriptor.emplace(DescriptorSource::IndirectDescriptor{});
|
||||
auto& sources = image_source.indirect_descriptor->sources;
|
||||
DescriptorSource finite_source;
|
||||
finite_source.dword_count = root.dword_count;
|
||||
finite_source.dwords.fill(Value(0u));
|
||||
finite_source.indirect_descriptor.emplace(DescriptorSource::IndirectDescriptor{});
|
||||
auto& sources = finite_source.indirect_descriptor->sources;
|
||||
for (auto& choice: choices) {
|
||||
if (choice.branch != nullptr) continue;
|
||||
const auto source = InternSource(choice.descriptor);
|
||||
@@ -2043,9 +2055,9 @@ private:
|
||||
}
|
||||
}
|
||||
plan.handle = &handle;
|
||||
plan.source = InternSource(image_source);
|
||||
plan.source = InternSource(finite_source);
|
||||
plan.key = choices.front().key;
|
||||
plan.roots = image_source.dwords;
|
||||
plan.roots = finite_source.dwords;
|
||||
plan.reads.fill(nullptr);
|
||||
return true;
|
||||
}
|
||||
@@ -2076,27 +2088,33 @@ private:
|
||||
inst.NumArgs() == 0u) {
|
||||
continue;
|
||||
}
|
||||
auto* handle = inst.Arg(0).Resolve().TryInstruction();
|
||||
if (handle == nullptr || FindIndirectDescriptor(*handle) != nullptr) {
|
||||
continue;
|
||||
}
|
||||
const auto flags = inst.Flags<MemoryFlags>();
|
||||
const auto& memory = m_program.memory_info[flags.index];
|
||||
DescriptorSource descriptor;
|
||||
MakeSource(*handle, inst.GetOpcode() == ValueOpcode::StoreBufferU32 ? 4u : 8u,
|
||||
false, false, memory.resource * 4u, descriptor, flags.pc);
|
||||
uint32_t bad_dword = 0;
|
||||
if (ValidateSource(descriptor, bad_dword)) continue;
|
||||
IndirectDescriptorPlan plan;
|
||||
if (TryMakeIndirectImage(*handle, descriptor, plan) || TryMakeFiniteImage(*handle, plan) ||
|
||||
TryMakeIndirectBuffer(*handle, descriptor, plan)) {
|
||||
for (const auto& previous: m_indirect_descriptors) {
|
||||
if (plan.reads[0] != nullptr && previous.reads[0] == plan.reads[0] &&
|
||||
!EquivalentValue(m_program, previous.key, plan.key))
|
||||
Fail(flags.pc, "shared descriptor read has incompatible keys");
|
||||
const auto plan_handle = [&](Value value, uint32_t width, uint32_t base, bool sampler) {
|
||||
auto* handle = value.Resolve().TryInstruction();
|
||||
if (handle == nullptr || FindIndirectDescriptor(*handle) != nullptr) return;
|
||||
DescriptorSource descriptor;
|
||||
MakeSource(*handle, width, sampler, false, base, descriptor, flags.pc);
|
||||
uint32_t bad_dword = 0;
|
||||
if (ValidateSource(descriptor, bad_dword)) return;
|
||||
IndirectDescriptorPlan plan;
|
||||
if (sampler ? TryMakeFiniteDescriptor(*handle, plan)
|
||||
: (TryMakeIndirectImage(*handle, descriptor, plan) ||
|
||||
TryMakeFiniteDescriptor(*handle, plan) ||
|
||||
TryMakeIndirectBuffer(*handle, descriptor, plan))) {
|
||||
for (const auto& previous: m_indirect_descriptors) {
|
||||
if (plan.reads[0] != nullptr && previous.reads[0] == plan.reads[0] &&
|
||||
!EquivalentValue(m_program, previous.key, plan.key))
|
||||
Fail(flags.pc, "shared descriptor read has incompatible keys");
|
||||
}
|
||||
m_indirect_descriptors.push_back(std::move(plan));
|
||||
}
|
||||
m_indirect_descriptors.push_back(std::move(plan));
|
||||
}
|
||||
};
|
||||
plan_handle(inst.Arg(0), inst.GetOpcode() == ValueOpcode::StoreBufferU32 ? 4u : 8u,
|
||||
memory.resource * 4u, false);
|
||||
if (inst.GetOpcode() == ValueOpcode::ImageSampleRaw &&
|
||||
(memory.image_sample_flags & Decoder::ImageSampleFlagAdjust) == 0u)
|
||||
plan_handle(inst.Arg(1), 4u, memory.sampler * 4u, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2225,7 +2243,20 @@ private:
|
||||
return UINT32_MAX;
|
||||
}
|
||||
m_info.samplers.push_back({source, pc});
|
||||
return static_cast<uint32_t>(m_info.samplers.size() - 1);
|
||||
const auto root = static_cast<uint32_t>(m_info.samplers.size() - 1);
|
||||
const auto* descriptor = Source(source);
|
||||
if (descriptor != nullptr && descriptor->indirect_descriptor) {
|
||||
const auto& sources = descriptor->indirect_descriptor->sources;
|
||||
if (sources.empty()) Fail(pc, "finite sampler has no candidates");
|
||||
std::vector<uint32_t> resources {root};
|
||||
for (size_t i = 1; i < sources.size(); ++i) {
|
||||
const auto candidate = AddSampler(sources[i], pc);
|
||||
if (candidate == UINT32_MAX) return UINT32_MAX;
|
||||
resources.push_back(candidate);
|
||||
}
|
||||
m_info.samplers[root].indirect_resources = std::move(resources);
|
||||
}
|
||||
return root;
|
||||
}
|
||||
|
||||
void AddSampledPair(uint32_t image, uint32_t sampler, uint32_t pc) {
|
||||
@@ -2385,8 +2416,15 @@ private:
|
||||
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,
|
||||
memory.sampler * 4u, sampler_handle, sampler_source, true, sample_adjust);
|
||||
sampler_handle = inst.Arg(1).Resolve().TryInstruction();
|
||||
const auto* selected_sampler = sampler_handle != nullptr
|
||||
? FindIndirectDescriptor(*sampler_handle) : nullptr;
|
||||
if (selected_sampler != nullptr) {
|
||||
sampler_source = selected_sampler->source;
|
||||
} else {
|
||||
GetHandle(inst.Arg(1), ValueOpcode::GetSamplerResource, 4, flags.pc,
|
||||
memory.sampler * 4u, sampler_handle, sampler_source, true, sample_adjust);
|
||||
}
|
||||
sampler = AddSampler(sampler_source, flags.pc);
|
||||
if (sampler == UINT32_MAX) {
|
||||
Fail(flags.pc, "sampler resource limit exceeded");
|
||||
|
||||
Reference in New Issue
Block a user