mirror of
https://github.com/KytyPS5/KytyPS5.git
synced 2026-10-20 02:47:02 +02:00
graphics: support dynamic indirect image tables
This commit is contained in:
@@ -113,7 +113,13 @@ bool ValidateNativeProgram(const IR::Program& program, std::string* error) {
|
||||
if (!program.info.addresses.empty()) {
|
||||
Expect(Kind::AddressMemory, Dense(program.info.addresses.size()));
|
||||
}
|
||||
if (program.values != nullptr && !program.values->srt_reads.empty()) {
|
||||
const bool uses_flattened_runtime =
|
||||
program.values != nullptr &&
|
||||
(!program.values->srt_reads.empty() ||
|
||||
std::ranges::any_of(program.info.images, [](const IR::ImageResource& image) {
|
||||
return image.indirect_mapping_capacity != 0u;
|
||||
}));
|
||||
if (uses_flattened_runtime) {
|
||||
Expect(Kind::FlattenedSrt);
|
||||
}
|
||||
if (program.bindings.ShaderDataDwords() != 0 && program.bindings.push_constant_size == 0) {
|
||||
|
||||
@@ -445,6 +445,13 @@ uint32_t MakeSampledImage(EmitterState& state, const IR::MemoryInfo& mem, uint32
|
||||
return sampled_image;
|
||||
}
|
||||
|
||||
uint32_t MakeSampledImage(EmitterState& state, const IR::MemoryInfo& mem, uint32_t use_pc,
|
||||
ImageViewKind view, uint32_t image_resource) {
|
||||
auto selected = mem;
|
||||
selected.resource = image_resource;
|
||||
return MakeSampledImage(state, selected, use_pc, view);
|
||||
}
|
||||
|
||||
uint32_t StorageImageDescriptorPointer(EmitterState& state, uint32_t resource, bool uint_image,
|
||||
uint32_t use_pc, ImageViewKind view) {
|
||||
(void)use_pc;
|
||||
|
||||
@@ -720,6 +720,8 @@ uint32_t LoadSamplerDescriptor(EmitterState& state, uint32_t sampler, uint32_t u
|
||||
|
||||
uint32_t MakeSampledImage(EmitterState& state, const IR::MemoryInfo& mem, uint32_t use_pc,
|
||||
ImageViewKind view);
|
||||
uint32_t MakeSampledImage(EmitterState& state, const IR::MemoryInfo& mem, uint32_t use_pc,
|
||||
ImageViewKind view, uint32_t image_resource);
|
||||
|
||||
ImageViewKind StorageImageViewKind(const EmitterState& state, const IR::MemoryInfo& mem,
|
||||
bool uint_image, uint32_t use_pc);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include "graphics/shader/recompiler/backend/spirv/spirvEmitterInternal.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <bit>
|
||||
|
||||
namespace Libs::Graphics::ShaderRecompiler::Spirv::Emitter {
|
||||
namespace {
|
||||
@@ -423,11 +424,11 @@ bool EmitValueImage(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
const auto layout = Layout(mem, view);
|
||||
const bool integer = mem.kind == IR::ResourceKind::ImageUint;
|
||||
const bool dref = HasFlag(mem, Decoder::ImageSampleFlagCompare);
|
||||
const auto sampled = MakeSampledImage(state, mem, pc, view);
|
||||
const auto coord =
|
||||
CoordF32(ctx, mem, *address, layout.coord, ImageViewCoordinateComponents(view));
|
||||
const auto sample = state.builder.AllocateId();
|
||||
if (op == IR::ValueOpcode::ImageGatherRaw) {
|
||||
const auto sampled = MakeSampledImage(state, mem, pc, view);
|
||||
const auto sample = state.builder.AllocateId();
|
||||
std::vector<uint32_t> words =
|
||||
dref ? std::vector<uint32_t> {OpImageDrefGather,
|
||||
state.vec4_float_type,
|
||||
@@ -461,12 +462,10 @@ bool EmitValueImage(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
: (dref ? OpImageSampleDrefImplicitLod : OpImageSampleImplicitLod);
|
||||
const auto result_type =
|
||||
dref ? state.float_type : (integer ? state.vec4_uint_type : state.vec4_float_type);
|
||||
std::vector<uint32_t> words {opcode, result_type, sample, sampled, coord};
|
||||
if (dref) {
|
||||
words.push_back(layout.dref != NoImageComponent
|
||||
? AddressF32(ctx, mem, *address, layout.dref)
|
||||
: ZeroF32(state));
|
||||
}
|
||||
const auto dref_value =
|
||||
dref ? (layout.dref != NoImageComponent ? AddressF32(ctx, mem, *address, layout.dref)
|
||||
: ZeroF32(state))
|
||||
: 0u;
|
||||
uint32_t operand_mask = 0;
|
||||
std::vector<uint32_t> operands;
|
||||
if (HasFlag(mem, Decoder::ImageSampleFlagDerivative)) {
|
||||
@@ -485,12 +484,114 @@ bool EmitValueImage(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
operand_mask |= ImageOperandsBiasMask;
|
||||
operands.push_back(AddressF32(ctx, mem, *address, layout.bias));
|
||||
}
|
||||
if (operand_mask != 0u) {
|
||||
words.push_back(operand_mask);
|
||||
words.insert(words.end(), operands.begin(), operands.end());
|
||||
const auto EmitSample = [&](uint32_t resource) {
|
||||
const auto sampled = MakeSampledImage(state, mem, pc, view, resource);
|
||||
const auto sample = state.builder.AllocateId();
|
||||
std::vector<uint32_t> words {opcode, result_type, sample, sampled, coord};
|
||||
if (dref) {
|
||||
words.push_back(dref_value);
|
||||
}
|
||||
if (operand_mask != 0u) {
|
||||
words.push_back(operand_mask);
|
||||
words.insert(words.end(), operands.begin(), operands.end());
|
||||
}
|
||||
state.builder.AddFunction(words);
|
||||
return sample;
|
||||
};
|
||||
const auto& image = state.program.info.images[mem.resource];
|
||||
if (image.indirect_root != mem.resource) {
|
||||
ctx.Define(inst, ResultVector(ctx, EmitSample(mem.resource), integer, dref));
|
||||
return true;
|
||||
}
|
||||
state.builder.AddFunction(words);
|
||||
ctx.Define(inst, ResultVector(ctx, sample, integer, dref));
|
||||
const auto* handle = image_arg.ResolveInstruction();
|
||||
const auto* source = image.source < ctx.program.descriptor_sources.size()
|
||||
? &ctx.program.descriptor_sources[image.source]
|
||||
: nullptr;
|
||||
if (handle == nullptr || source == nullptr || !source->indirect_image.has_value() ||
|
||||
source->indirect_image->key_arg >= handle->NumArgs()) {
|
||||
ctx.Fail(inst, "has invalid indirect image key provenance");
|
||||
return true;
|
||||
}
|
||||
const auto key = ctx.Def(handle->Arg(source->indirect_image->key_arg));
|
||||
if (state.flattened_srt_variable == 0 || image.indirect_mapping_capacity == 0u ||
|
||||
image.indirect_resources.size() < 2u) {
|
||||
ctx.Fail(inst, "has no indirect image runtime mapping");
|
||||
return true;
|
||||
}
|
||||
const auto LoadMapping = [&](uint32_t index) {
|
||||
const auto pointer = state.builder.AllocateId();
|
||||
state.builder.AddFunction({OpAccessChain, state.ptr_storage_buffer_uint, pointer,
|
||||
state.flattened_srt_variable, ConstantU32(state, 0), index});
|
||||
const auto value = state.builder.AllocateId();
|
||||
state.builder.AddFunction({OpLoad, state.uint_type, value, pointer});
|
||||
return value;
|
||||
};
|
||||
const auto mapping = ConstantU32(state, image.indirect_mapping_offset);
|
||||
auto low = ConstantU32(state, 0u);
|
||||
auto high = LoadMapping(mapping);
|
||||
auto selected = ConstantU32(state, 0u);
|
||||
for (uint32_t iteration = 0; iteration < std::bit_width(image.indirect_mapping_capacity);
|
||||
iteration++) {
|
||||
const auto active = Binary(state, OpULessThan, state.bool_type, low, high);
|
||||
const auto mid =
|
||||
Binary(state, OpShiftRightLogical, state.uint_type,
|
||||
Binary(state, OpIAdd, state.uint_type, low, high), ConstantU32(state, 1u));
|
||||
const auto probe = state.builder.AllocateId();
|
||||
state.builder.AddFunction(
|
||||
{OpSelect, state.uint_type, probe, active, mid, ConstantU32(state, 0u)});
|
||||
const auto entry = Binary(state, OpIAdd, state.uint_type, mapping,
|
||||
Binary(state, OpIAdd, state.uint_type,
|
||||
Binary(state, OpShiftLeftLogical, state.uint_type,
|
||||
probe, ConstantU32(state, 1u)),
|
||||
ConstantU32(state, 1u)));
|
||||
const auto mapped_key = LoadMapping(entry);
|
||||
const auto candidate =
|
||||
LoadMapping(Binary(state, OpIAdd, state.uint_type, entry, ConstantU32(state, 1u)));
|
||||
const auto equal = Binary(state, OpIEqual, state.bool_type, mapped_key, key);
|
||||
const auto match = Binary(state, OpLogicalAnd, state.bool_type, active, equal);
|
||||
const auto next_selected = state.builder.AllocateId();
|
||||
state.builder.AddFunction(
|
||||
{OpSelect, state.uint_type, next_selected, match, candidate, selected});
|
||||
selected = next_selected;
|
||||
const auto less = Binary(state, OpULessThan, state.bool_type, mapped_key, key);
|
||||
const auto take_upper = Binary(state, OpLogicalAnd, state.bool_type, active, less);
|
||||
const auto take_lower = Binary(state, OpLogicalAnd, state.bool_type, active,
|
||||
Unary(state, OpLogicalNot, state.bool_type, less));
|
||||
const auto next_low = state.builder.AllocateId();
|
||||
state.builder.AddFunction(
|
||||
{OpSelect, state.uint_type, next_low, take_upper,
|
||||
Binary(state, OpIAdd, state.uint_type, mid, ConstantU32(state, 1u)), low});
|
||||
low = next_low;
|
||||
const auto next_high = state.builder.AllocateId();
|
||||
state.builder.AddFunction(
|
||||
{OpSelect, state.uint_type, next_high, take_lower, mid, high});
|
||||
high = next_high;
|
||||
}
|
||||
const auto default_label = state.builder.AllocateId();
|
||||
const auto merge_label = state.builder.AllocateId();
|
||||
std::vector<uint32_t> labels(image.indirect_resources.size() - 1u);
|
||||
std::vector<uint32_t> switch_words {OpSwitch, selected, default_label};
|
||||
for (uint32_t candidate = 1; candidate < image.indirect_resources.size(); candidate++) {
|
||||
labels[candidate - 1u] = state.builder.AllocateId();
|
||||
switch_words.push_back(candidate);
|
||||
switch_words.push_back(labels[candidate - 1u]);
|
||||
}
|
||||
state.builder.AddFunction({OpSelectionMerge, merge_label, SelectionControlNone});
|
||||
state.builder.AddFunction(switch_words);
|
||||
std::vector<uint32_t> phi_words {OpPhi, result_type, state.builder.AllocateId()};
|
||||
state.builder.AddFunction({OpLabel, default_label});
|
||||
phi_words.push_back(EmitSample(image.indirect_resources[0]));
|
||||
phi_words.push_back(default_label);
|
||||
state.builder.AddFunction({OpBranch, merge_label});
|
||||
for (uint32_t candidate = 1; candidate < image.indirect_resources.size(); candidate++) {
|
||||
state.builder.AddFunction({OpLabel, labels[candidate - 1u]});
|
||||
phi_words.push_back(EmitSample(image.indirect_resources[candidate]));
|
||||
phi_words.push_back(labels[candidate - 1u]);
|
||||
state.builder.AddFunction({OpBranch, merge_label});
|
||||
}
|
||||
state.builder.AddFunction({OpLabel, merge_label});
|
||||
state.builder.AddFunction(phi_words);
|
||||
ctx.Define(inst, ResultVector(ctx, phi_words[2], integer, dref));
|
||||
return true;
|
||||
}
|
||||
const auto atomic_opcode = ImageAtomicOpcode(op);
|
||||
|
||||
@@ -190,18 +190,24 @@ enum class ImageMipMode { None, DynamicStorage };
|
||||
constexpr uint32_t StorageImageIdentitySwizzle = 0x00000facu;
|
||||
|
||||
struct ImageResource {
|
||||
uint32_t source = 0;
|
||||
uint32_t first_use_pc = 0;
|
||||
ResourceKind kind = ResourceKind::None;
|
||||
Decoder::ImageDimension dimension = Decoder::ImageDimension::Unknown;
|
||||
ImageMipMode mip_mode = ImageMipMode::None;
|
||||
uint32_t mip_count = 1;
|
||||
uint32_t storage_swizzle = StorageImageIdentitySwizzle;
|
||||
bool read = false;
|
||||
bool written = false;
|
||||
bool atomic = false;
|
||||
bool depth_compare = false;
|
||||
bool cube = false;
|
||||
static constexpr uint32_t NoIndirectImage = UINT32_MAX;
|
||||
|
||||
uint32_t source = 0;
|
||||
uint32_t first_use_pc = 0;
|
||||
ResourceKind kind = ResourceKind::None;
|
||||
Decoder::ImageDimension dimension = Decoder::ImageDimension::Unknown;
|
||||
ImageMipMode mip_mode = ImageMipMode::None;
|
||||
uint32_t mip_count = 1;
|
||||
uint32_t storage_swizzle = StorageImageIdentitySwizzle;
|
||||
bool read = false;
|
||||
bool written = false;
|
||||
bool atomic = false;
|
||||
bool depth_compare = false;
|
||||
bool cube = false;
|
||||
uint32_t indirect_root = NoIndirectImage;
|
||||
uint32_t indirect_mapping_offset = 0;
|
||||
uint32_t indirect_mapping_capacity = 0;
|
||||
std::vector<uint32_t> indirect_resources;
|
||||
|
||||
bool operator==(const ImageResource& other) const = default;
|
||||
};
|
||||
|
||||
@@ -25,6 +25,7 @@ struct DescriptorSource {
|
||||
uint32_t heap_source = 0;
|
||||
uint32_t selector_stride = 0;
|
||||
uint32_t selector_offset = 0;
|
||||
uint32_t key_arg = 0;
|
||||
|
||||
bool operator==(const IndirectImage& other) const = default;
|
||||
};
|
||||
|
||||
@@ -230,7 +230,12 @@ bool AllocateBindings(Program& program, const BindingLayoutOptions& options, std
|
||||
}
|
||||
AddBinding(next, DescriptorBindingKind::AddressMemory, std::move(resources));
|
||||
}
|
||||
if (!program.values->srt_reads.empty()) {
|
||||
const bool uses_flattened_runtime =
|
||||
!program.values->srt_reads.empty() ||
|
||||
std::ranges::any_of(program.info.images, [](const ImageResource& image) {
|
||||
return image.indirect_mapping_capacity != 0u;
|
||||
});
|
||||
if (uses_flattened_runtime) {
|
||||
AddBinding(next, DescriptorBindingKind::FlattenedSrt);
|
||||
}
|
||||
|
||||
|
||||
@@ -176,7 +176,8 @@ bool ReadScalarBufferWord(const ShaderBufferResource& descriptor, uint32_t dynam
|
||||
bool MaterializeIndirectImage(const DescriptorSource::IndirectImage& indirect,
|
||||
const DescriptorValue& material_value,
|
||||
const DescriptorValue& heap_value, const SrtRuntime& runtime,
|
||||
DescriptorValue& result, std::string* error, uint32_t use_pc) {
|
||||
ResourceSnapshot::IndirectImage& result, std::string* error,
|
||||
uint32_t use_pc) {
|
||||
ShaderBufferResource material;
|
||||
ShaderBufferResource heap;
|
||||
if (!DecodeBufferDescriptor(material_value, material) ||
|
||||
@@ -228,9 +229,10 @@ bool MaterializeIndirectImage(const DescriptorSource::IndirectImage& indirect,
|
||||
}
|
||||
}
|
||||
|
||||
DescriptorValue invariant;
|
||||
bool have_invariant = false;
|
||||
for (const auto key: keys) {
|
||||
ResourceSnapshot::IndirectImage next;
|
||||
next.capacity = static_cast<uint32_t>(probe_count + 1u);
|
||||
next.keys = std::move(keys);
|
||||
for (const auto key: next.keys) {
|
||||
DescriptorValue candidate;
|
||||
candidate.dword_count = 8u;
|
||||
const auto heap_offset = key << 5u;
|
||||
@@ -240,18 +242,18 @@ bool MaterializeIndirectImage(const DescriptorSource::IndirectImage& indirect,
|
||||
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;
|
||||
if (NullImageDescriptor(candidate) || !ValidImageDescriptor(candidate)) {
|
||||
candidate.dwords.fill(0);
|
||||
}
|
||||
const auto found = std::ranges::find(next.descriptors, candidate);
|
||||
if (found == next.descriptors.end()) {
|
||||
next.descriptors.push_back(candidate);
|
||||
next.candidates.push_back(static_cast<uint32_t>(next.descriptors.size() - 1u));
|
||||
} else {
|
||||
next.candidates.push_back(static_cast<uint32_t>(found - next.descriptors.begin()));
|
||||
}
|
||||
}
|
||||
result = invariant;
|
||||
result = std::move(next);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -280,6 +282,18 @@ bool IsImageOp(ValueOpcode op) {
|
||||
}
|
||||
}
|
||||
|
||||
size_t FlattenedRuntimeDwords(const Program& program) {
|
||||
size_t size = program.values != nullptr ? program.values->srt_reads.size() : 0u;
|
||||
for (uint32_t resource = 0; resource < program.info.images.size(); resource++) {
|
||||
const auto& image = program.info.images[resource];
|
||||
if (image.indirect_root == resource) {
|
||||
size = std::max(size, static_cast<size_t>(image.indirect_mapping_offset) + 1u +
|
||||
static_cast<size_t>(image.indirect_mapping_capacity) * 2u);
|
||||
}
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool ValidateResourceSnapshot(const Program& program, const ResourceSnapshot& snapshot,
|
||||
@@ -300,7 +314,7 @@ bool ValidateResourceSnapshot(const Program& program, const ResourceSnapshot& sn
|
||||
return false;
|
||||
}
|
||||
if (program.values == nullptr ||
|
||||
snapshot.flattened_srt.size() != program.values->srt_reads.size()) {
|
||||
snapshot.flattened_srt.size() != FlattenedRuntimeDwords(program)) {
|
||||
if (error != nullptr) {
|
||||
*error = "flattened SRT snapshot does not match the shader plan";
|
||||
}
|
||||
@@ -346,6 +360,36 @@ bool ValidateResourceSnapshot(const Program& program, const ResourceSnapshot& sn
|
||||
return false;
|
||||
}
|
||||
}
|
||||
std::unordered_set<uint32_t> indirect_resources;
|
||||
for (const auto& table: snapshot.indirect_images) {
|
||||
const auto* source = table.resource < program.info.images.size()
|
||||
? Source(program, program.info.images[table.resource].source)
|
||||
: nullptr;
|
||||
if (source == nullptr || !source->indirect_image.has_value() ||
|
||||
program.info.images[table.resource].indirect_root != ImageResource::NoIndirectImage ||
|
||||
table.keys.empty() || table.keys.size() != table.candidates.size() ||
|
||||
table.capacity < table.keys.size() || table.descriptors.empty() ||
|
||||
!indirect_resources.insert(table.resource).second) {
|
||||
if (error != nullptr) {
|
||||
*error = "indirect image snapshot does not match the shader plan";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
for (const auto candidate: table.candidates) {
|
||||
if (candidate >= table.descriptors.size()) {
|
||||
if (error != nullptr) {
|
||||
*error = "indirect image snapshot has an invalid candidate";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (snapshot.images[table.resource] != table.descriptors[table.candidates[0]]) {
|
||||
if (error != nullptr) {
|
||||
*error = "indirect image snapshot has an inconsistent root descriptor";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return CheckWidth(snapshot.buffers, 4, "buffer") && CheckWidth(snapshot.images, 8, "image") &&
|
||||
CheckWidth(snapshot.samplers, 4, "sampler");
|
||||
}
|
||||
@@ -355,6 +399,12 @@ bool ValidateResourceSpecialization(const Program& program, const ResourceSnapsh
|
||||
if (!ValidateResourceSnapshot(program, snapshot, error)) {
|
||||
return false;
|
||||
}
|
||||
if (!snapshot.indirect_images.empty()) {
|
||||
if (error != nullptr) {
|
||||
*error = "indirect image snapshot was not specialized";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
for (uint32_t i = 0; i < program.info.buffers.size(); i++) {
|
||||
const auto& buffer = program.info.buffers[i];
|
||||
ShaderBufferResource descriptor;
|
||||
@@ -384,6 +434,23 @@ bool ValidateResourceSpecialization(const Program& program, const ResourceSnapsh
|
||||
return false;
|
||||
}
|
||||
if (NullImageDescriptor(descriptor)) {
|
||||
if (image.indirect_root != ImageResource::NoIndirectImage) {
|
||||
const auto root = image.indirect_root;
|
||||
if (root >= program.info.images.size() ||
|
||||
program.info.images[root].kind != image.kind ||
|
||||
program.info.images[root].dimension != image.dimension ||
|
||||
program.info.images[root].mip_mode != image.mip_mode ||
|
||||
program.info.images[root].mip_count != image.mip_count ||
|
||||
program.info.images[root].storage_swizzle != image.storage_swizzle ||
|
||||
program.info.images[root].cube != image.cube) {
|
||||
if (error != nullptr) {
|
||||
*error = fmt::format(
|
||||
"null indirect image descriptor {} changed binding class", i);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
bool canonical_kind =
|
||||
image.kind == ResourceKind::Image || image.kind == ResourceKind::StorageImage;
|
||||
if (image.atomic) {
|
||||
@@ -522,11 +589,18 @@ bool MaterializeResources(const Program& program, const SrtRuntime& runtime,
|
||||
descriptor.dwords.fill(0);
|
||||
}
|
||||
}
|
||||
next.images.reserve(program.info.images.size());
|
||||
for (const auto& image: program.info.images) {
|
||||
const auto* source = Source(program, image.source);
|
||||
DescriptorValue descriptor;
|
||||
next.flattened_srt = std::move(flattened_srt);
|
||||
next.flattened_srt.resize(FlattenedRuntimeDwords(program));
|
||||
next.images.resize(program.info.images.size());
|
||||
std::vector<uint8_t> image_written(program.info.images.size());
|
||||
for (uint32_t image_index = 0; image_index < program.info.images.size(); image_index++) {
|
||||
const auto& image = program.info.images[image_index];
|
||||
const auto* source = Source(program, image.source);
|
||||
if (source != nullptr && source->indirect_image.has_value()) {
|
||||
if (image.indirect_root != ImageResource::NoIndirectImage &&
|
||||
image.indirect_root != image_index) {
|
||||
continue;
|
||||
}
|
||||
const std::array requests {
|
||||
DescriptorSourceRequest {source->indirect_image->material_source,
|
||||
image.first_use_pc},
|
||||
@@ -537,19 +611,69 @@ bool MaterializeResources(const Program& program, const SrtRuntime& runtime,
|
||||
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)) {
|
||||
const auto& material = tables[0];
|
||||
const auto& heap = tables[1];
|
||||
ResourceSnapshot::IndirectImage table;
|
||||
if (!MaterializeIndirectImage(*source->indirect_image, material, heap, runtime, table,
|
||||
error, image.first_use_pc)) {
|
||||
return false;
|
||||
}
|
||||
if (image.indirect_root == ImageResource::NoIndirectImage) {
|
||||
next.images[image_index] = table.descriptors[table.candidates[0]];
|
||||
image_written[image_index] = 1u;
|
||||
if (table.descriptors.size() > 1u) {
|
||||
table.resource = image_index;
|
||||
next.indirect_images.push_back(std::move(table));
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (table.keys.size() > image.indirect_mapping_capacity ||
|
||||
table.descriptors.size() > image.indirect_resources.size()) {
|
||||
if (error != nullptr) {
|
||||
*error = fmt::format(
|
||||
"indirect image table at pc 0x{:08x} changed candidate topology",
|
||||
image.first_use_pc);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
for (uint32_t candidate = 0; candidate < image.indirect_resources.size(); candidate++) {
|
||||
const auto resource = image.indirect_resources[candidate];
|
||||
if (resource >= program.info.images.size() ||
|
||||
program.info.images[resource].indirect_root != image_index) {
|
||||
if (error != nullptr) {
|
||||
*error = "indirect image specialization has an invalid candidate resource";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
next.images[resource] =
|
||||
table.descriptors[candidate < table.descriptors.size() ? candidate : 0u];
|
||||
image_written[resource] = 1u;
|
||||
}
|
||||
std::vector<uint32_t> order(table.keys.size());
|
||||
std::iota(order.begin(), order.end(), 0u);
|
||||
std::ranges::sort(order, {}, [&](uint32_t index) { return table.keys[index]; });
|
||||
const auto mapping = image.indirect_mapping_offset;
|
||||
next.flattened_srt[mapping] = static_cast<uint32_t>(table.keys.size());
|
||||
for (uint32_t entry = 0; entry < order.size(); entry++) {
|
||||
const auto source = order[entry];
|
||||
const auto offset = mapping + 1u + entry * 2u;
|
||||
next.flattened_srt[offset] = table.keys[source];
|
||||
next.flattened_srt[offset + 1] = table.candidates[source];
|
||||
}
|
||||
} else {
|
||||
descriptor = *cursor++;
|
||||
auto descriptor = *cursor++;
|
||||
if (!ValidImageDescriptor(descriptor)) {
|
||||
descriptor.dwords.fill(0);
|
||||
}
|
||||
next.images[image_index] = descriptor;
|
||||
image_written[image_index] = 1u;
|
||||
}
|
||||
if (!ValidImageDescriptor(descriptor)) {
|
||||
descriptor.dwords.fill(0);
|
||||
}
|
||||
if (std::ranges::find(image_written, uint8_t {0}) != image_written.end()) {
|
||||
if (error != nullptr) {
|
||||
*error = "indirect image specialization left an unmaterialized candidate";
|
||||
}
|
||||
next.images.push_back(descriptor);
|
||||
return false;
|
||||
}
|
||||
next.samplers.assign(cursor, cursor + program.info.samplers.size());
|
||||
cursor += program.info.samplers.size();
|
||||
@@ -584,7 +708,6 @@ bool MaterializeResources(const Program& program, const SrtRuntime& runtime,
|
||||
next.addresses.push_back({*runtime.flat_memory_base, *runtime.flat_memory_base});
|
||||
}
|
||||
}
|
||||
next.flattened_srt = std::move(flattened_srt);
|
||||
next.user_data.assign(runtime.user_data.begin(), runtime.user_data.end());
|
||||
if (!ValidateResourceSnapshot(program, next, error)) {
|
||||
return false;
|
||||
@@ -593,7 +716,7 @@ bool MaterializeResources(const Program& program, const SrtRuntime& runtime,
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SpecializeResources(Program& program, const ResourceSnapshot& snapshot, std::string* error) {
|
||||
bool SpecializeResources(Program& program, ResourceSnapshot& snapshot, std::string* error) {
|
||||
if (!program.resource_tracking_complete || program.shader_info_complete ||
|
||||
program.binding_layout_complete) {
|
||||
if (error != nullptr) {
|
||||
@@ -606,10 +729,50 @@ bool SpecializeResources(Program& program, const ResourceSnapshot& snapshot, std
|
||||
return false;
|
||||
}
|
||||
|
||||
auto next = program.info;
|
||||
auto next = program.info;
|
||||
auto next_snapshot = snapshot;
|
||||
for (const auto& table: snapshot.indirect_images) {
|
||||
if (table.resource >= next.images.size() || table.descriptors.size() < 2u ||
|
||||
next.images.size() + table.descriptors.size() - 1u > ShaderInfo::MaxImages) {
|
||||
if (error != nullptr) {
|
||||
*error = "indirect image candidates exceed the dense image resource limit";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
const auto root_image = next.images[table.resource];
|
||||
std::vector<uint32_t> resources(table.descriptors.size());
|
||||
resources[0] = table.resource;
|
||||
for (uint32_t candidate = 1; candidate < table.descriptors.size(); candidate++) {
|
||||
resources[candidate] = static_cast<uint32_t>(next.images.size());
|
||||
auto image = root_image;
|
||||
image.indirect_root = table.resource;
|
||||
next.images.push_back(std::move(image));
|
||||
next_snapshot.images.push_back(table.descriptors[candidate]);
|
||||
}
|
||||
auto& root = next.images[table.resource];
|
||||
root.indirect_root = table.resource;
|
||||
root.indirect_mapping_offset = static_cast<uint32_t>(next_snapshot.flattened_srt.size());
|
||||
root.indirect_mapping_capacity = table.capacity;
|
||||
root.indirect_resources = std::move(resources);
|
||||
next_snapshot.flattened_srt.resize(next_snapshot.flattened_srt.size() + 1u +
|
||||
static_cast<size_t>(table.capacity) * 2u);
|
||||
std::vector<uint32_t> order(table.keys.size());
|
||||
std::iota(order.begin(), order.end(), 0u);
|
||||
std::ranges::sort(order, {}, [&](uint32_t index) { return table.keys[index]; });
|
||||
next_snapshot.flattened_srt[root.indirect_mapping_offset] =
|
||||
static_cast<uint32_t>(table.keys.size());
|
||||
for (uint32_t entry = 0; entry < order.size(); entry++) {
|
||||
const auto source = order[entry];
|
||||
const auto offset = root.indirect_mapping_offset + 1u + entry * 2u;
|
||||
next_snapshot.flattened_srt[offset] = table.keys[source];
|
||||
next_snapshot.flattened_srt[offset + 1] = table.candidates[source];
|
||||
}
|
||||
next_snapshot.images[table.resource] = table.descriptors[0];
|
||||
}
|
||||
next_snapshot.indirect_images.clear();
|
||||
for (uint32_t i = 0; i < next.buffers.size(); i++) {
|
||||
ShaderBufferResource descriptor;
|
||||
if (!DecodeBufferDescriptor(snapshot.buffers[i], descriptor)) {
|
||||
if (!DecodeBufferDescriptor(next_snapshot.buffers[i], descriptor)) {
|
||||
if (error != nullptr) {
|
||||
*error = fmt::format("buffer descriptor {} has invalid width", i);
|
||||
}
|
||||
@@ -621,10 +784,10 @@ bool SpecializeResources(Program& program, const ResourceSnapshot& snapshot, std
|
||||
}
|
||||
for (uint32_t i = 0; i < next.addresses.size(); i++) {
|
||||
next.addresses[i].specialized_base =
|
||||
AddressSpecialization(next.addresses[i], snapshot.addresses[i]);
|
||||
AddressSpecialization(next.addresses[i], next_snapshot.addresses[i]);
|
||||
}
|
||||
for (uint32_t i = 0; i < next.images.size(); i++) {
|
||||
const auto& descriptor = snapshot.images[i];
|
||||
const auto& descriptor = next_snapshot.images[i];
|
||||
auto& image = next.images[i];
|
||||
image.mip_count = StorageMipCount(image, descriptor);
|
||||
if (image.mip_count == 0u) {
|
||||
@@ -681,6 +844,68 @@ bool SpecializeResources(Program& program, const ResourceSnapshot& snapshot, std
|
||||
}
|
||||
}
|
||||
}
|
||||
for (uint32_t root_index = 0; root_index < next.images.size(); root_index++) {
|
||||
auto& root = next.images[root_index];
|
||||
if (root.indirect_root != root_index) {
|
||||
continue;
|
||||
}
|
||||
if (root.indirect_mapping_capacity == 0u || root.indirect_resources.size() < 2u ||
|
||||
static_cast<size_t>(root.indirect_mapping_offset) + 1u +
|
||||
static_cast<size_t>(root.indirect_mapping_capacity) * 2u >
|
||||
next_snapshot.flattened_srt.size()) {
|
||||
if (error != nullptr) {
|
||||
*error = "indirect image specialization has an invalid key mapping";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
uint32_t exemplar = ImageResource::NoIndirectImage;
|
||||
for (const auto resource: root.indirect_resources) {
|
||||
if (resource >= next.images.size() ||
|
||||
next.images[resource].indirect_root != root_index) {
|
||||
if (error != nullptr) {
|
||||
*error = "indirect image specialization has an invalid candidate";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if (!NullImageDescriptor(next_snapshot.images[resource])) {
|
||||
exemplar = resource;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (exemplar == ImageResource::NoIndirectImage) {
|
||||
if (error != nullptr) {
|
||||
*error = "indirect image specialization has no typed candidate";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
const auto& image_class = next.images[exemplar];
|
||||
for (uint32_t candidate = 0; candidate < next.images.size(); candidate++) {
|
||||
auto& image = next.images[candidate];
|
||||
if (image.indirect_root != root_index) {
|
||||
continue;
|
||||
}
|
||||
if (NullImageDescriptor(next_snapshot.images[candidate])) {
|
||||
image.kind = image_class.kind;
|
||||
image.dimension = image_class.dimension;
|
||||
image.mip_count = image_class.mip_count;
|
||||
image.storage_swizzle = image_class.storage_swizzle;
|
||||
image.cube = image_class.cube;
|
||||
}
|
||||
if (image.kind != image_class.kind || image.dimension != image_class.dimension ||
|
||||
image.mip_mode != image_class.mip_mode ||
|
||||
image.mip_count != image_class.mip_count ||
|
||||
image.storage_swizzle != image_class.storage_swizzle ||
|
||||
image.depth_compare != image_class.depth_compare ||
|
||||
image.cube != image_class.cube) {
|
||||
if (error != nullptr) {
|
||||
*error = fmt::format(
|
||||
"indirect image table at pc 0x{:08x} has incompatible candidates",
|
||||
root.first_use_pc);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (program.values == nullptr) {
|
||||
if (error != nullptr) {
|
||||
*error = "typed SSA is not ready";
|
||||
@@ -709,7 +934,16 @@ bool SpecializeResources(Program& program, const ResourceSnapshot& snapshot, std
|
||||
}
|
||||
return false;
|
||||
}
|
||||
const auto& image = next.images[memory.resource];
|
||||
const auto& image = next.images[memory.resource];
|
||||
if (image.indirect_root == memory.resource &&
|
||||
inst.GetOpcode() != ValueOpcode::ImageSampleRaw) {
|
||||
if (error != nullptr) {
|
||||
*error = fmt::format(
|
||||
"indirect image table at pc 0x{:08x} is used by unsupported {}",
|
||||
inst.Flags<MemoryFlags>().pc, ValueOpcodeName(inst.GetOpcode()));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
memory.kind = image.kind;
|
||||
memory.image_dimension = image.dimension;
|
||||
memory.image_cube = image.cube;
|
||||
@@ -717,6 +951,7 @@ bool SpecializeResources(Program& program, const ResourceSnapshot& snapshot, std
|
||||
}
|
||||
program.info = std::move(next);
|
||||
program.values->memory_info = std::move(memory_info);
|
||||
snapshot = std::move(next_snapshot);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -14,6 +14,13 @@ struct ResourceSnapshot {
|
||||
|
||||
bool operator==(const Address& other) const = default;
|
||||
};
|
||||
struct IndirectImage {
|
||||
uint32_t resource = 0;
|
||||
uint32_t capacity = 0;
|
||||
std::vector<uint32_t> keys;
|
||||
std::vector<uint32_t> candidates;
|
||||
std::vector<DescriptorValue> descriptors;
|
||||
};
|
||||
|
||||
std::vector<DescriptorValue> buffers;
|
||||
std::vector<DescriptorValue> images;
|
||||
@@ -21,6 +28,7 @@ struct ResourceSnapshot {
|
||||
std::vector<Address> addresses;
|
||||
std::vector<uint32_t> flattened_srt;
|
||||
std::vector<uint32_t> user_data;
|
||||
std::vector<IndirectImage> indirect_images;
|
||||
};
|
||||
|
||||
bool ValidateResourceSnapshot(const Program& program, const ResourceSnapshot& snapshot,
|
||||
@@ -35,7 +43,7 @@ bool MaterializeResources(const Program& program, const SrtRuntime& runtime,
|
||||
|
||||
// Applies runtime descriptor shape/format facts to a copied dense topology before layout and
|
||||
// emission. On failure the program is unchanged.
|
||||
bool SpecializeResources(Program& program, const ResourceSnapshot& snapshot, std::string* error);
|
||||
bool SpecializeResources(Program& program, ResourceSnapshot& snapshot, std::string* error);
|
||||
|
||||
} // namespace Libs::Graphics::ShaderRecompiler::IR
|
||||
|
||||
|
||||
@@ -169,8 +169,12 @@ public:
|
||||
}
|
||||
}
|
||||
for (const auto& plan: m_indirect_images) {
|
||||
for (uint32_t dword = 0; dword < plan.roots.size(); dword++) {
|
||||
plan.handle->SetArg(dword, plan.roots[dword]);
|
||||
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_values.memory_info[index].planning_only = true;
|
||||
@@ -204,9 +208,10 @@ private:
|
||||
struct IndirectImagePlan {
|
||||
Inst* handle = nullptr;
|
||||
uint32_t source = 0;
|
||||
Value key;
|
||||
std::array<Value, 8> roots {};
|
||||
std::array<uint32_t, 9> memory {};
|
||||
std::array<const Inst*, 9> reads {};
|
||||
std::array<uint32_t, 8> memory {};
|
||||
std::array<const Inst*, 8> reads {};
|
||||
};
|
||||
|
||||
bool Fail(uint32_t pc, std::string* error, const std::string& reason) const {
|
||||
@@ -474,8 +479,8 @@ private:
|
||||
} 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];
|
||||
plan.memory[dword] = memory_index;
|
||||
plan.reads[dword] = heap_reads[dword];
|
||||
}
|
||||
|
||||
const auto* shift = heap_offset.TryInstruction();
|
||||
@@ -538,13 +543,12 @@ private:
|
||||
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};
|
||||
material_source_index, heap_source_index, selector_stride, selector_offset, 0u};
|
||||
|
||||
plan.handle = &handle;
|
||||
plan.source = InternSource(image_source);
|
||||
plan.roots = image_source.dwords;
|
||||
plan.memory[0] = material_memory_index;
|
||||
plan.reads[0] = material_read;
|
||||
plan.handle = &handle;
|
||||
plan.source = InternSource(image_source);
|
||||
plan.key = Value(material_read);
|
||||
plan.roots = image_source.dwords;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -1029,9 +1029,10 @@ static std::string ShaderDescribeSpecialization(const ShaderRecompiler::IR::Prog
|
||||
for (uint32_t i = 0; i < program.info.images.size(); i++) {
|
||||
const auto& image = program.info.images[i];
|
||||
ret += fmt::format(
|
||||
" i{}[kind={} dim={} mip={}/{} swizzle=0x{:03x}]", i, static_cast<uint32_t>(image.kind),
|
||||
static_cast<uint32_t>(image.dimension), static_cast<uint32_t>(image.mip_mode),
|
||||
image.mip_count, image.storage_swizzle);
|
||||
" i{}[kind={} dim={} mip={}/{} swizzle=0x{:03x} indirect={}/{}+{}]", i,
|
||||
static_cast<uint32_t>(image.kind), static_cast<uint32_t>(image.dimension),
|
||||
static_cast<uint32_t>(image.mip_mode), image.mip_count, image.storage_swizzle,
|
||||
image.indirect_root, image.indirect_mapping_offset, image.indirect_mapping_capacity);
|
||||
}
|
||||
for (uint32_t i = 0; i < program.info.addresses.size(); i++) {
|
||||
ret += fmt::format(" a{}[base=0x{:x}]", i, program.info.addresses[i].specialized_base);
|
||||
@@ -1068,9 +1069,15 @@ static void ShaderAppendNativeSpecialization(std::vector<uint32_t>&
|
||||
for (const auto& image: program.info.images) {
|
||||
ids.push_back(static_cast<uint32_t>(image.kind));
|
||||
ids.push_back(static_cast<uint32_t>(image.dimension));
|
||||
ids.push_back(static_cast<uint32_t>(image.cube));
|
||||
ids.push_back(static_cast<uint32_t>(image.mip_mode));
|
||||
ids.push_back(image.mip_count);
|
||||
ids.push_back(image.storage_swizzle);
|
||||
ids.push_back(image.indirect_root);
|
||||
ids.push_back(image.indirect_mapping_offset);
|
||||
ids.push_back(image.indirect_mapping_capacity);
|
||||
ids.push_back(static_cast<uint32_t>(image.indirect_resources.size()));
|
||||
ids.insert(ids.end(), image.indirect_resources.begin(), image.indirect_resources.end());
|
||||
}
|
||||
ids.push_back(static_cast<uint32_t>(program.info.addresses.size()));
|
||||
for (const auto& address: program.info.addresses) {
|
||||
|
||||
+109
-10
@@ -247,15 +247,24 @@ void TestInvariantIndirectImageMaterialization() {
|
||||
ValidateValueProgram(*fixture->program.values, true, &validation_error),
|
||||
"post-tracking dead-code elimination invalidated descriptor provenance");
|
||||
|
||||
Check(fixture->program.info.buffers.empty() &&
|
||||
Check(fixture->program.info.buffers.size() == 1 &&
|
||||
fixture->program.info.images.size() == 1 &&
|
||||
fixture->program.values->dynamic_reads.empty(),
|
||||
"indirect image planning reads leaked into shader resources");
|
||||
fixture->program.values->dynamic_reads.size() == 1,
|
||||
"indirect image key was not retained as a scalar-buffer read");
|
||||
const auto source = fixture->program.info.images[0].source;
|
||||
Check(source < fixture->program.values->descriptor_sources.size() &&
|
||||
fixture->program.values->descriptor_sources[source]
|
||||
.indirect_image.has_value(),
|
||||
"indirect image source was not retained for runtime proof");
|
||||
const auto image_handle = std::ranges::find_if(
|
||||
*fixture->block, [](const Inst &inst) {
|
||||
return inst.GetOpcode() == ValueOpcode::GetImageResource;
|
||||
});
|
||||
Check(image_handle != fixture->block->end() &&
|
||||
image_handle->Arg(0).ResolveInstruction() != nullptr &&
|
||||
image_handle->Arg(0).ResolveInstruction()->GetOpcode() ==
|
||||
ValueOpcode::ReadConstBuffer,
|
||||
"indirect image handle discarded the live material key");
|
||||
|
||||
std::array<uint32_t, 9> user_data{0x1000u, 224u << 16u, 2u, 0u, 0x2000u,
|
||||
16u << 16u, 4u, 0u, 7u};
|
||||
@@ -284,24 +293,114 @@ void TestInvariantIndirectImageMaterialization() {
|
||||
.read_specialization_memory = ReadLinearTestMemory};
|
||||
ResourceSnapshot snapshot;
|
||||
std::string error;
|
||||
const auto same_snapshot = [](const ResourceSnapshot &lhs,
|
||||
const ResourceSnapshot &rhs) {
|
||||
return lhs.buffers == rhs.buffers && lhs.images == rhs.images &&
|
||||
lhs.samplers == rhs.samplers && lhs.addresses == rhs.addresses &&
|
||||
lhs.flattened_srt == rhs.flattened_srt &&
|
||||
lhs.user_data == rhs.user_data &&
|
||||
lhs.indirect_images.empty() == rhs.indirect_images.empty();
|
||||
};
|
||||
Check(MaterializeResources(fixture->program, runtime, snapshot, &error) &&
|
||||
snapshot.images.size() == 1 &&
|
||||
std::equal(image_descriptor.begin(), image_descriptor.end(),
|
||||
snapshot.images[0].dwords.begin()),
|
||||
"invariant indirect image table did not materialize");
|
||||
|
||||
const auto prior_image = snapshot.images[0];
|
||||
const auto prior_snapshot = snapshot;
|
||||
memory.fail_address = 0x1004u;
|
||||
Check(!MaterializeResources(fixture->program, runtime, snapshot, &error) &&
|
||||
error.find("scalar read") != std::string::npos &&
|
||||
snapshot.images[0] == prior_image,
|
||||
same_snapshot(snapshot, prior_snapshot),
|
||||
"rejected planning memory read mutated the snapshot");
|
||||
memory.fail_address = UINT64_MAX;
|
||||
|
||||
memory.words[(0x1000u - memory.base + 36u) / 4u] = 1u;
|
||||
Check(!MaterializeResources(fixture->program, runtime, snapshot, &error) &&
|
||||
error.find("not invariant") != std::string::npos &&
|
||||
snapshot.images[0] == prior_image,
|
||||
"divergent indirect image table was accepted or mutated the snapshot");
|
||||
for (uint32_t dword = 0; dword < image_descriptor.size(); dword++) {
|
||||
memory.words[(0x2000u - memory.base) / 4u + dword] = 0u;
|
||||
memory.words[(0x2020u - memory.base) / 4u + dword] = 0u;
|
||||
}
|
||||
memory.words[(0x2000u - memory.base) / 4u + 1u] = image_descriptor[1];
|
||||
memory.words[(0x2000u - memory.base) / 4u + 3u] = image_descriptor[3];
|
||||
memory.words[(0x2020u - memory.base) / 4u + 1u] = image_descriptor[1];
|
||||
memory.words[(0x2020u - memory.base) / 4u + 3u] =
|
||||
image_descriptor[3] ^ (1u << 28u);
|
||||
ResourceSnapshot null_snapshot;
|
||||
Check(MaterializeResources(fixture->program, runtime, null_snapshot, &error) &&
|
||||
null_snapshot.indirect_images.empty() &&
|
||||
std::ranges::all_of(null_snapshot.images[0].dwords,
|
||||
[](uint32_t dword) { return dword == 0u; }),
|
||||
"stale typed null image descriptors were not canonicalized");
|
||||
|
||||
for (uint32_t dword = 0; dword < image_descriptor.size(); dword++) {
|
||||
memory.words[(0x2000u - memory.base) / 4u + dword] =
|
||||
image_descriptor[dword];
|
||||
memory.words[(0x2020u - memory.base) / 4u + dword] =
|
||||
image_descriptor[dword];
|
||||
}
|
||||
memory.words[(0x2020u - memory.base) / 4u] ^= 1u;
|
||||
memory.words[(0x1000u - memory.base + 36u) / 4u] = 1u;
|
||||
ResourceSnapshot dynamic_snapshot;
|
||||
Check(MaterializeResources(fixture->program, runtime, dynamic_snapshot,
|
||||
&error) &&
|
||||
dynamic_snapshot.images.size() == 1 &&
|
||||
dynamic_snapshot.indirect_images.size() == 1 &&
|
||||
dynamic_snapshot.indirect_images[0].descriptors.size() == 2 &&
|
||||
SpecializeResources(fixture->program, dynamic_snapshot, &error) &&
|
||||
fixture->program.info.images.size() == 2 &&
|
||||
fixture->program.info.images[0].indirect_root == 0 &&
|
||||
fixture->program.info.images[0].indirect_mapping_capacity != 0 &&
|
||||
fixture->program.info.images[0].indirect_resources.size() == 2 &&
|
||||
dynamic_snapshot.images.size() == 2 &&
|
||||
dynamic_snapshot.indirect_images.empty(),
|
||||
"dynamic indirect image table was not specialized transactionally");
|
||||
|
||||
for (uint32_t dword = 0; dword < image_descriptor.size(); dword++) {
|
||||
memory.words[(0x2000u - memory.base) / 4u + dword] =
|
||||
image_descriptor[dword];
|
||||
memory.words[(0x2020u - memory.base) / 4u + dword] =
|
||||
image_descriptor[dword];
|
||||
}
|
||||
memory.words[(0x2000u - memory.base) / 4u] += 0x100u;
|
||||
memory.words[(0x2020u - memory.base) / 4u] += 0x101u;
|
||||
ResourceSnapshot rebound_snapshot;
|
||||
Check(MaterializeResources(fixture->program, runtime, rebound_snapshot,
|
||||
&error) &&
|
||||
ValidateResourceSpecialization(fixture->program, rebound_snapshot,
|
||||
&error),
|
||||
"stable indirect key mapping did not accept changed image addresses");
|
||||
memory.words[(0x2020u - memory.base) / 4u] =
|
||||
memory.words[(0x2000u - memory.base) / 4u];
|
||||
Check(MaterializeResources(fixture->program, runtime, rebound_snapshot,
|
||||
&error) &&
|
||||
ValidateResourceSpecialization(fixture->program, rebound_snapshot,
|
||||
&error),
|
||||
"runtime indirect key mapping did not accept collapsed candidates");
|
||||
const auto collapsed_snapshot = rebound_snapshot;
|
||||
ResourceSnapshot capacity_snapshot;
|
||||
for (const uint32_t records : {1u, 3u}) {
|
||||
user_data[2] = records;
|
||||
Check(MaterializeResources(fixture->program, runtime, capacity_snapshot,
|
||||
&error) &&
|
||||
ValidateResourceSpecialization(fixture->program,
|
||||
capacity_snapshot, &error),
|
||||
"runtime indirect key mapping rejected a fitting material-table size");
|
||||
}
|
||||
user_data[2] = 2u;
|
||||
memory.words[(0x2020u - memory.base) / 4u] =
|
||||
memory.words[(0x2000u - memory.base) / 4u] + 1u;
|
||||
memory.words[(0x2040u - memory.base) / 4u] =
|
||||
memory.words[(0x2000u - memory.base) / 4u] + 2u;
|
||||
for (uint32_t dword = 1; dword < image_descriptor.size(); dword++) {
|
||||
memory.words[(0x2040u - memory.base) / 4u + dword] =
|
||||
image_descriptor[dword];
|
||||
}
|
||||
memory.words[(0x1000u - memory.base + 68u) / 4u] = 2u;
|
||||
Check(!MaterializeResources(fixture->program, runtime, rebound_snapshot,
|
||||
&error) &&
|
||||
error.find("candidate topology") != std::string::npos &&
|
||||
same_snapshot(rebound_snapshot, collapsed_snapshot),
|
||||
"larger indirect candidate topology reused or mutated a cached snapshot");
|
||||
|
||||
auto memory_backed = MakeIndirectImageFixture(false, 0u, true);
|
||||
memory_backed->PlanAndTrack();
|
||||
@@ -319,7 +418,7 @@ void TestInvariantIndirectImageMaterialization() {
|
||||
Check(!MaterializeResources(memory_backed->program, memory_backed_runtime,
|
||||
snapshot, &error) &&
|
||||
error.find("constant read failed") != std::string::npos &&
|
||||
snapshot.images[0] == prior_image,
|
||||
same_snapshot(snapshot, prior_snapshot),
|
||||
"rejected indirect table descriptor read mutated the snapshot");
|
||||
memory.fail_address = UINT64_MAX;
|
||||
|
||||
|
||||
@@ -16737,6 +16737,126 @@ TestCase ImageStoreVariants() {
|
||||
return test;
|
||||
}
|
||||
|
||||
void CheckIndirectImageKeySwitch() {
|
||||
constexpr const char *name = "IndirectImageKeySwitch";
|
||||
constexpr uint32_t mapping_capacity = 1793u;
|
||||
using namespace ShaderRecompiler::IR;
|
||||
|
||||
Program program{};
|
||||
program.stage = ShaderType::Compute;
|
||||
program.wave_size = 32;
|
||||
program.srt_plan_complete = true;
|
||||
program.resource_tracking_complete = true;
|
||||
program.shader_info_complete = true;
|
||||
program.values = std::make_shared<ValueProgram>();
|
||||
program.values->block_storage.push_back(std::make_unique<Block>());
|
||||
auto *block = program.values->block_storage.back().get();
|
||||
program.values->blocks.push_back(block);
|
||||
program.values->block_info.push_back({.id = 0});
|
||||
|
||||
auto &key = block->AppendNewInst(ValueOpcode::LaneId);
|
||||
auto &image = block->AppendNewInst(
|
||||
ValueOpcode::GetImageResource,
|
||||
{Value(&key), Value(0u), Value(0u), Value(0u), Value(0u), Value(0u),
|
||||
Value(0u), Value(0u)});
|
||||
image.SetFlags<uint32_t>(0u);
|
||||
auto &sampler = block->AppendNewInst(
|
||||
ValueOpcode::GetSamplerResource,
|
||||
{Value(0u), Value(0u), Value(0u), Value(0u)});
|
||||
sampler.SetFlags<uint32_t>(0u);
|
||||
auto &address = block->AppendNewInst(
|
||||
ValueOpcode::MakeImageAddress,
|
||||
{Value(0u), Value(0u), Value(0u), Value(0u), Value(0u), Value(0u),
|
||||
Value(0u), Value(0u), Value(0u), Value(0u), Value(0u), Value(0u),
|
||||
Value(0u)});
|
||||
MemoryInfo memory{};
|
||||
memory.kind = ResourceKind::Image;
|
||||
memory.resource = 0;
|
||||
memory.sampler = 0;
|
||||
memory.dmask = 0xf;
|
||||
memory.image_sample_flags = ShaderRecompiler::Decoder::ImageSampleFlagLod;
|
||||
memory.image_dimension = ShaderRecompiler::Decoder::ImageDimension::Dim2D;
|
||||
memory.image_address_components = 3;
|
||||
program.values->memory_info.push_back(memory);
|
||||
const MemoryFlags memory_flags{0u, 0x10f0u};
|
||||
uint64_t memory_flag_bits = 0;
|
||||
std::memcpy(&memory_flag_bits, &memory_flags, sizeof(memory_flags));
|
||||
auto &sample = block->AppendNewInst(
|
||||
ValueOpcode::ImageSampleRaw,
|
||||
{Value(&image), Value(&sampler), Value(&address)},
|
||||
memory_flag_bits);
|
||||
auto &sample_x = block->AppendNewInst(ValueOpcode::CompositeExtractU32x4,
|
||||
{Value(&sample), Value(0u)});
|
||||
block->AppendNewInst(ValueOpcode::ReferenceU32, {Value(&sample_x)});
|
||||
|
||||
program.values->descriptor_sources.resize(2);
|
||||
program.values->descriptor_sources[0].dword_count = 8;
|
||||
program.values->descriptor_sources[0].indirect_image =
|
||||
DescriptorSource::IndirectImage{0u, 0u, 224u, 12u, 0u};
|
||||
program.values->descriptor_sources[1].dword_count = 4;
|
||||
|
||||
ImageResource root{};
|
||||
root.source = 0;
|
||||
root.first_use_pc = 0x10f0u;
|
||||
root.kind = ResourceKind::Image;
|
||||
root.dimension = ShaderRecompiler::Decoder::ImageDimension::Dim2D;
|
||||
root.read = true;
|
||||
root.indirect_root = 0;
|
||||
root.indirect_mapping_offset = 0;
|
||||
root.indirect_mapping_capacity = mapping_capacity;
|
||||
root.indirect_resources = {0u, 1u};
|
||||
auto candidate = root;
|
||||
candidate.indirect_mapping_capacity = 0;
|
||||
candidate.indirect_resources.clear();
|
||||
program.info.images = {root, candidate};
|
||||
program.info.samplers.push_back({1u, 0x10f0u});
|
||||
program.info.sampled_pairs.push_back({0u, 0u, 0x10f0u});
|
||||
|
||||
std::string error;
|
||||
Require(name, "binding layout",
|
||||
AllocateBindings(program, {}, &error), error.c_str());
|
||||
ResourceSnapshot snapshot{};
|
||||
DescriptorValue descriptor{};
|
||||
descriptor.dword_count = 8;
|
||||
descriptor.dwords[0] = 0x20u;
|
||||
descriptor.dwords[1] =
|
||||
static_cast<uint32_t>(Prospero::BufferFormat::k32_32_32_32Float)
|
||||
<< 20u;
|
||||
descriptor.dwords[2] = 3u | (3u << 14u);
|
||||
descriptor.dwords[3] =
|
||||
DstSel(4, 5, 6, 7) |
|
||||
(static_cast<uint32_t>(Prospero::ImageType::kColor2D) << 28u);
|
||||
snapshot.images = {descriptor, descriptor};
|
||||
snapshot.images[1].dwords[0] = 0x40u;
|
||||
snapshot.flattened_srt.resize(1u + mapping_capacity * 2u);
|
||||
snapshot.flattened_srt[0] = 2u;
|
||||
snapshot.flattened_srt[1] = 0u;
|
||||
snapshot.flattened_srt[2] = 0u;
|
||||
snapshot.flattened_srt[3] = 1u;
|
||||
snapshot.flattened_srt[4] = 1u;
|
||||
DescriptorValue sampler_descriptor{};
|
||||
sampler_descriptor.dword_count = 4;
|
||||
snapshot.samplers.push_back(sampler_descriptor);
|
||||
|
||||
ShaderComputeInputInfo compute{};
|
||||
std::vector<u32> spirv;
|
||||
Require(name, "SPIR-V emit",
|
||||
ShaderRecompiler::Spirv::EmitProgram(program, snapshot, nullptr,
|
||||
nullptr, &compute, spirv, &error),
|
||||
error.c_str());
|
||||
ValidateSpirv(name, spirv);
|
||||
spvtools::SpirvTools tools(SPV_ENV_VULKAN_1_2);
|
||||
std::string text;
|
||||
Require(name, "SPIR-V disassembly", tools.Disassemble(spirv, &text),
|
||||
"failed to disassemble indirect image shader");
|
||||
Require(name, "key switch",
|
||||
text.find("OpSwitch") != std::string::npos &&
|
||||
text.find("OpPhi") != std::string::npos &&
|
||||
CountText(text, "OpImageSampleExplicitLod") == 2 &&
|
||||
CountText(text, "OpIEqual") == 11,
|
||||
"dynamic image key did not use a compact two-sample switch");
|
||||
}
|
||||
|
||||
TestCase ImageStoreMipSelectsPpsa01340Descriptor() {
|
||||
using O = ShaderOpcode;
|
||||
|
||||
@@ -19533,6 +19653,11 @@ void CheckStorageImageSwizzleSpecializationId() {
|
||||
ShaderRecompiler::IR::ImageMipMode::DynamicStorage;
|
||||
auto mip3_program = mip1_program;
|
||||
mip3_program.info.images[0].mip_count = 3;
|
||||
auto array_program = identity_program;
|
||||
array_program.info.images[0].dimension =
|
||||
ShaderRecompiler::Decoder::ImageDimension::Dim2DArray;
|
||||
auto cube_program = array_program;
|
||||
cube_program.info.images[0].cube = true;
|
||||
|
||||
const auto resources =
|
||||
std::make_shared<const ShaderRecompiler::IR::ResourceSnapshot>();
|
||||
@@ -19549,16 +19674,25 @@ void CheckStorageImageSwizzleSpecializationId() {
|
||||
auto mip3_info = identity_info;
|
||||
mip3_info.stage.program =
|
||||
std::make_shared<const ShaderRecompiler::IR::Program>(mip3_program);
|
||||
auto array_info = identity_info;
|
||||
array_info.stage.program =
|
||||
std::make_shared<const ShaderRecompiler::IR::Program>(array_program);
|
||||
auto cube_info = identity_info;
|
||||
cube_info.stage.program =
|
||||
std::make_shared<const ShaderRecompiler::IR::Program>(cube_program);
|
||||
|
||||
const auto identity_id = ShaderGetIdCS(regs, identity_info, true);
|
||||
const auto rgb1_id = ShaderGetIdCS(regs, rgb1_info, true);
|
||||
const auto mip1_id = ShaderGetIdCS(regs, mip1_info, true);
|
||||
const auto mip3_id = ShaderGetIdCS(regs, mip3_info, true);
|
||||
const auto array_id = ShaderGetIdCS(regs, array_info, true);
|
||||
const auto cube_id = ShaderGetIdCS(regs, cube_info, true);
|
||||
Require("StorageImageSwizzleSpecializationId", "pipeline cache key",
|
||||
identity_id != rgb1_id &&
|
||||
identity_id.ids.size() == rgb1_id.ids.size() &&
|
||||
mip1_id != mip3_id && mip1_id.ids.size() == mip3_id.ids.size(),
|
||||
"storage swizzle or mip-count variants share a pipeline ID");
|
||||
mip1_id != mip3_id && mip1_id.ids.size() == mip3_id.ids.size() &&
|
||||
array_id != cube_id && array_id.ids.size() == cube_id.ids.size(),
|
||||
"storage swizzle, mip-count, or cube variants share a pipeline ID");
|
||||
std::printf("[host] %-32s ok\n", "StorageImageSwizzlePipelineId");
|
||||
}
|
||||
|
||||
@@ -20854,6 +20988,11 @@ int main(int argc, char **argv) {
|
||||
RunCase(&vulkan, ImageStoreBgraUsesInverseSwizzle());
|
||||
return 0;
|
||||
}
|
||||
if (argc == 2 && std::strcmp(argv[1], "--indirect-image-only") == 0) {
|
||||
CheckStorageImageSwizzleSpecializationId();
|
||||
CheckIndirectImageKeySwitch();
|
||||
return 0;
|
||||
}
|
||||
if (argc == 2 && std::strcmp(argv[1], "--storage-mip-host-only") == 0) {
|
||||
VulkanHarness vulkan;
|
||||
vulkan.CheckRenderExecutorStencilBindingDiscovery();
|
||||
@@ -20924,6 +21063,7 @@ int main(int argc, char **argv) {
|
||||
CheckEmbeddedFetchVertexOffset();
|
||||
CheckEmbeddedFetchLaneSpill();
|
||||
CheckRectListShaders();
|
||||
CheckIndirectImageKeySwitch();
|
||||
CheckPs5GameExampleImageClearRuntimeShape();
|
||||
vulkan.CheckSchedulerTimeline();
|
||||
vulkan.CheckGpuMappedRangeLifecycle();
|
||||
|
||||
Reference in New Issue
Block a user