graphics: keep bounded image heaps resident in a bindless descriptor set

This commit is contained in:
nmzik
2026-10-22 22:15:56 +02:00
parent 79837b423d
commit 27dc7c281f
31 changed files with 1377 additions and 75 deletions
@@ -161,6 +161,7 @@ void ValidateNativeProgram(const IR::Program& program, bool lds_storage) {
if (!kind.has_value()) {
Fail(program, "native shader plan has an invalid image class");
}
if (image.heap) continue;
if (image.mip_count == 0u ||
(image.mip_mode != IR::ImageMipMode::Dynamic && image.mip_count != 1u)) {
Fail(program, "native shader plan has an invalid image mip descriptor count");
@@ -120,6 +120,10 @@ uint32_t ImageType(EmitterState& state, const IR::ImageResource& image) {
} else {
EXIT("invalid image resource class");
}
if (image.dimension == ImageDimension::Dim1D || image.dimension == ImageDimension::Dim1DArray) {
state.builder.RequireCapability(sampled == 1 ? spv::CapabilitySampled1D
: spv::CapabilityImage1D);
}
const auto& info = ImageDimensionInfoFor(image.dimension);
if (sampled == 2 && info.multisampled != 0u) {
state.builder.RequireCapability(spv::CapabilityStorageImageMultisample);
@@ -147,14 +151,20 @@ void DecorateNonUniformDescriptor(EmitterState& state, uint32_t id, spv::Capabil
state.builder.RequireCapability(capability);
state.builder.AddAnnotation(spv::OpDecorate, id, spv::DecorationNonUniform);
}
ImageDefinition& ImageDescriptorDefinition(EmitterState& state, const IR::ImageResource& image,
IR::DescriptorBindingKind kind) {
return (image.heap ? state.heap_images : state.images)[IR::ImageBindingIndex(kind)];
}
} // 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);
const auto image = state.builder.AllocateId();
const auto kind = *IR::DescriptorBindingForImage(state.program.info.images[resource]);
state.builder.AddFunction(spv::OpLoad, state.images[IR::ImageBindingIndex(kind)].type,
const auto& resource_info = state.program.info.images[resource];
const auto kind = *IR::DescriptorBindingForImage(resource_info);
state.builder.AddFunction(spv::OpLoad, ImageDescriptorDefinition(state, resource_info, kind).type,
image, pointer);
if (array_index != 0u) {
DecorateNonUniformDescriptor(state, image,
@@ -187,13 +197,13 @@ uint32_t MakeSampledImage(EmitterState& state, uint32_t resource, uint32_t sampl
EXIT_IF(image_resource.resource_class != IR::ImageResourceClass::Sampled);
const auto image = LoadImageDescriptor(state, resource, mip, array_index);
const auto sampled_image = state.builder.AllocateId();
const auto kind = *IR::DescriptorBindingForImage(image_resource);
auto& sampled_type = state.images[IR::ImageBindingIndex(kind)].sampled_type;
if (sampled_type == 0) {
sampled_type = state.builder.Type(
spv::OpTypeSampledImage, state.images[IR::ImageBindingIndex(kind)].type);
auto& definition = ImageDescriptorDefinition(state, image_resource,
*IR::DescriptorBindingForImage(image_resource));
if (definition.sampled_type == 0) {
definition.sampled_type = state.builder.Type(spv::OpTypeSampledImage, definition.type);
}
state.builder.AddFunction(spv::OpSampledImage, sampled_type, sampled_image, image, sampler_id);
state.builder.AddFunction(spv::OpSampledImage, definition.sampled_type, sampled_image, image,
sampler_id);
if (array_index != 0u || sampler_dynamic) {
DecorateNonUniformDescriptor(state, sampled_image, spv::CapabilitySampledImageArrayNonUniformIndexing);
}
@@ -205,11 +215,11 @@ uint32_t ImageDescriptorPointer(EmitterState& state, uint32_t resource, uint32_t
const auto& image = state.program.info.images.at(resource);
EXIT_IF(mip >= image.mip_count);
const auto kind = IR::DescriptorBindingForImage(image);
EXIT_IF(!kind.has_value());
EXIT_IF(!kind.has_value() || (image.heap && array_index == 0u));
if (array_index == 0u) {
array_index = ConstantU32(state, image.descriptor_index + mip);
}
const auto& definition = state.images[IR::ImageBindingIndex(*kind)];
const auto& definition = ImageDescriptorDefinition(state, image, *kind);
return DescriptorElementPointer(state, definition.pointer_type, definition.variable,
array_index, *kind, resource,
"image descriptor array was not emitted");
@@ -700,6 +700,21 @@ auto EmitImageAccess(ValueEmitContext& ctx, const IR::Inst& inst, uint32_t resul
image.indirect_resources.size() < 2u) {
ctx.Fail(inst, "has no indirect image runtime mapping");
}
if (image.heap) {
const auto entry = EmitImageHeapEntry(state, key, image.indirect_mapping_offset);
const auto kinds = static_cast<uint32_t>(image.indirect_resources.size()) - 1u;
// One kind leaves every slot word a bare element.
if (kinds == 1u) return emit(image.indirect_resources[1], entry);
const auto element = Binary(state, spv::OpBitwiseAnd, TypeU32(state), entry,
ConstantU32(state, (1u << IR::ImageHeapKindShift) - 1u));
const auto EmitKind = [&](uint32_t kind) {
return emit(image.indirect_resources[kind + 1u], element);
};
return EmitIndexSwitch(state,
Binary(state, spv::OpShiftRightLogical, TypeU32(state), entry,
ConstantU32(state, IR::ImageHeapKindShift)),
kinds, result_type, EmitKind);
}
const auto selected = EmitIndirectResourceIndex(
state, key, image.indirect_mapping_offset, image.indirect_search_iterations, 0u);
const auto SameType = [&](uint32_t lhs, uint32_t rhs) {
@@ -879,14 +894,19 @@ void EmitImage(ValueEmitContext& ctx, const IR::Inst& inst) {
}
if (op == IR::ValueOpcode::ImageQueryLod) {
state.builder.RequireCapability(spv::CapabilityImageQuery);
const auto dimension = image.dimension;
const auto sampled = MakeSampledImage(state, mem.resource,
LoadSamplerDescriptor(state, mem.sampler));
const auto lod = state.builder.AllocateId();
state.builder.AddFunction(
spv::OpImageQueryLod, TypeF32Vector(state, 2), lod, sampled,
CoordF32(ctx, mem, *address, 0, ImageDimensionInfoFor(dimension).spatial_components,
image.cube));
const auto sampler = LoadSamplerDescriptor(state, mem.sampler);
const auto lod = EmitImageAccess(ctx, inst, TypeF32Vector(state, 2),
[&](uint32_t resource, uint32_t array_index) {
const auto& candidate = state.program.info.images[resource];
const auto query = state.builder.AllocateId();
state.builder.AddFunction(
spv::OpImageQueryLod, TypeF32Vector(state, 2), query,
MakeSampledImage(state, resource, sampler, 0u, array_index),
CoordF32(ctx, mem, *address, 0,
ImageDimensionInfoFor(candidate.dimension).spatial_components,
candidate.cube));
return query;
});
uint32_t values[4] = {ConstantU32(state, 0), ConstantU32(state, 0), ConstantU32(state, 0),
ConstantU32(state, 0)};
for (uint32_t index = 0; index < 2u; index++) {
@@ -130,6 +130,7 @@ struct EmitterState {
uint32_t lds_u64_variable = 0;
std::array<uint32_t, 2> scratch_variable {};
std::array<ImageDefinition, IR::ImageBindingCount> images {};
std::array<ImageDefinition, IR::ImageBindingCount> heap_images {};
uint32_t sampler_variable = 0;
uint32_t sampler_type = 0;
uint32_t sampler_pointer_type = 0;
@@ -431,6 +432,8 @@ struct MemoryResourceAccess {
uint32_t EmitIndirectResourceIndex(EmitterState& state, uint32_t key, uint32_t mapping_offset,
uint32_t search_iterations, uint32_t default_resource);
// Slot word of an image heap byte offset; offsets past the heap select the null element.
uint32_t EmitImageHeapEntry(EmitterState& state, uint32_t key, uint32_t mapping_offset);
MemoryResourceAccess PrepareMemoryResourceAccess(EmitterState& state, const IR::MemoryInfo& mem,
uint32_t array_index = 0u);
@@ -38,19 +38,39 @@ uint32_t EmitBinaryU32(EmitterState& state, spv::Op opcode, uint32_t lhs, uint32
return ret;
}
static uint32_t LoadFlattenedSrt(EmitterState& state, uint32_t index) {
const auto pointer = state.builder.AllocateId();
state.builder.AddFunction(spv::OpAccessChain, TypeStorageBufferElementPointer(state), pointer,
state.flattened_srt_variable, ConstantU32(state, 0), index);
const auto value = state.builder.AllocateId();
state.builder.AddFunction(spv::OpLoad, TypeU32(state), value, pointer);
return value;
}
uint32_t EmitImageHeapEntry(EmitterState& state, uint32_t key, uint32_t mapping_offset) {
const auto Mapping = [&](uint32_t word) {
return LoadFlattenedSrt(state, ConstantU32(state, mapping_offset + word));
};
// Keys past the table select its trailing null slot.
const auto index = EmitUMin32(state, Binary(state, spv::OpShiftRightLogical, TypeU32(state), key,
ConstantU32(state, 2u)),
Mapping(0u));
const auto offset = Unary(state, spv::OpUConvert, TypeU64(state),
Binary(state, spv::OpShiftLeftLogical, TypeU32(state), index,
ConstantU32(state, 2u)));
const auto pointer = Unary(state, spv::OpConvertUToPtr, TypePhysicalU32Pointer(state),
Binary(state, spv::OpIAdd, TypeU64(state),
PackU64(state, Mapping(1u), Mapping(2u)), offset));
const auto entry = state.builder.AllocateId();
state.builder.AddFunction(spv::OpLoad, TypeU32(state), entry, pointer,
spv::MemoryAccessAlignedMask, 4u);
return entry;
}
uint32_t EmitIndirectResourceIndex(EmitterState& state, uint32_t key, uint32_t mapping_offset,
uint32_t search_iterations, uint32_t default_resource) {
const auto LoadMapping = [&](uint32_t index) {
const auto pointer = state.builder.AllocateId();
state.builder.AddFunction(spv::OpAccessChain, TypeStorageBufferElementPointer(state),
pointer, state.flattened_srt_variable, ConstantU32(state, 0),
index);
const auto value = state.builder.AllocateId();
state.builder.AddFunction(spv::OpLoad, TypeU32(state), value, pointer);
return value;
};
const auto mapping = ConstantU32(state, mapping_offset);
const auto count = LoadMapping(mapping);
const auto count = LoadFlattenedSrt(state, mapping);
if (search_iterations == 0u) {
const auto index = Binary(state, spv::OpShiftRightLogical, TypeU32(state), key,
ConstantU32(state, 2u));
@@ -58,7 +78,7 @@ uint32_t EmitIndirectResourceIndex(EmitterState& state, uint32_t key, uint32_t m
const auto safe = Select(state, TypeU32(state), valid, index, ConstantU32(state, 0u));
const auto entry = Binary(state, spv::OpIAdd, TypeU32(state), safe,
ConstantU32(state, mapping_offset + 1u));
return Select(state, TypeU32(state), valid, LoadMapping(entry),
return Select(state, TypeU32(state), valid, LoadFlattenedSrt(state, entry),
ConstantU32(state, default_resource));
}
const auto entry_at = [&](uint32_t index) {
@@ -76,7 +96,7 @@ uint32_t EmitIndirectResourceIndex(EmitterState& state, uint32_t key, uint32_t m
ConstantU32(state, 1u));
const auto probe = Select(state, TypeU32(state), active, mid, ConstantU32(state, 0u));
const auto less = Binary(state, spv::OpULessThan, TypeBool(state),
LoadMapping(entry_at(probe)), key);
LoadFlattenedSrt(state, entry_at(probe)), key);
low = Select(state, TypeU32(state),
Binary(state, spv::OpLogicalAnd, TypeBool(state), active, less),
Binary(state, spv::OpIAdd, TypeU32(state), mid, ConstantU32(state, 1u)), low);
@@ -87,9 +107,10 @@ uint32_t EmitIndirectResourceIndex(EmitterState& state, uint32_t key, uint32_t m
const auto in_range = Binary(state, spv::OpULessThan, TypeBool(state), low, count);
const auto entry = entry_at(Select(state, TypeU32(state), in_range, low, ConstantU32(state, 0u)));
const auto match = Binary(state, spv::OpLogicalAnd, TypeBool(state), in_range,
Binary(state, spv::OpIEqual, TypeBool(state), LoadMapping(entry), key));
const auto resource = LoadMapping(Binary(state, spv::OpIAdd, TypeU32(state), entry,
ConstantU32(state, 1u)));
Binary(state, spv::OpIEqual, TypeBool(state),
LoadFlattenedSrt(state, entry), key));
const auto resource = LoadFlattenedSrt(
state, Binary(state, spv::OpIAdd, TypeU32(state), entry, ConstantU32(state, 1u)));
return Select(state, TypeU32(state), match, resource, ConstantU32(state, default_resource));
}
@@ -206,6 +206,17 @@ uint32_t F32ArrayType(EmitterState& state, uint32_t count) {
return state.builder.Type(spv::OpTypeArray, TypeF32(state), ConstantU32(state, count));
}
void DefineImageArray(EmitterState& state, ImageDefinition& definition, const IR::ImageResource& image,
uint32_t count, uint32_t set, uint32_t binding) {
definition.type = ImageType(state, image);
const auto array = state.builder.Type(spv::OpTypeArray, definition.type, ConstantU32(state, count));
definition.variable = state.builder.DefineGlobalVariable(
TypePointer(state, spv::StorageClassUniformConstant, array), spv::StorageClassUniformConstant);
state.builder.AddAnnotation(spv::OpDecorate, definition.variable, spv::DecorationDescriptorSet, set);
state.builder.AddAnnotation(spv::OpDecorate, definition.variable, spv::DecorationBinding, binding);
definition.pointer_type = TypePointer(state, spv::StorageClassUniformConstant, definition.type);
}
void DefineDescriptors(EmitterState& state) {
using Kind = IR::DescriptorBindingKind;
auto& info = state.program.info;
@@ -222,6 +233,14 @@ void DefineDescriptors(EmitterState& state) {
(image.mip_mode != IR::ImageMipMode::Dynamic && image.mip_count != 1u)) {
EXIT("shader image has an invalid descriptor type or mip count");
}
if (image.heap) {
// Heap kinds alias one array; the root itself is never accessed.
auto& definition = state.heap_images[IR::ImageBindingIndex(*kind)];
if (image.indirect_resources.empty() && definition.variable == 0) {
DefineImageArray(state, definition, image, IR::ImageHeapCapacity, IR::ImageHeapSet, 0u);
}
continue;
}
auto& count = counts[static_cast<size_t>(*kind)];
image.descriptor_index = count;
if (count == 0) image_types[IR::ImageBindingIndex(*kind)] = &image;
@@ -331,21 +350,11 @@ void DefineDescriptors(EmitterState& state) {
default: {
EXIT_IF(IR::ImageBindingResourceClass(kind) ==
IR::ImageResourceClass::None);
const auto& image = *image_types[IR::ImageBindingIndex(kind)];
const auto name = "image_" + std::to_string(static_cast<uint32_t>(kind));
auto& definition = state.images[IR::ImageBindingIndex(kind)];
definition.type = ImageType(state, image);
definition.variable = Define(ArrayType(definition.type), name.c_str(),
spv::StorageClassUniformConstant);
definition.pointer_type = TypePointer(
state, spv::StorageClassUniformConstant, definition.type);
if (image.dimension == ImageDimension::Dim1D ||
image.dimension == ImageDimension::Dim1DArray) {
state.builder.RequireCapability(image.resource_class ==
IR::ImageResourceClass::Sampled
? spv::CapabilitySampled1D
: spv::CapabilityImage1D);
}
DefineImageArray(state, definition, *image_types[IR::ImageBindingIndex(kind)],
counts[index], 0u, IR::NativeBinding(state.program.stage, kind));
state.builder.AddName(definition.variable,
("image_" + std::to_string(static_cast<uint32_t>(kind))).c_str());
break;
}
}
@@ -752,7 +761,7 @@ void DefineModule(EmitterState& state) {
if (state.program.info.buffer_u16) {
state.builder.RequireCapability(spv::CapabilityStorageBuffer16BitAccess);
}
if (state.program.info.uses_dma) {
if (state.program.info.uses_dma || state.program.info.uses_image_heap) {
state.builder.RequireCapability(spv::CapabilityInt64);
state.builder.RequireCapability(spv::CapabilityPhysicalStorageBufferAddresses);
state.builder.RequireExtension("SPV_KHR_physical_storage_buffer");
@@ -820,7 +829,7 @@ void DefineModule(EmitterState& state) {
state.builder.RequireExtension("SPV_KHR_fragment_shader_barycentric");
}
state.builder.RequireExtension("SPV_KHR_float_controls");
state.builder.AddMemoryModel(state.program.info.uses_dma
state.builder.AddMemoryModel(state.program.info.uses_dma || state.program.info.uses_image_heap
? spv::AddressingModelPhysicalStorageBuffer64
: spv::AddressingModelLogical,
spv::MemoryModelGLSL450);
@@ -169,6 +169,7 @@ struct ImageResource {
bool depth_compare = false;
bool cube = false;
bool r128 = false;
bool heap = false; // Resident image heap root or kind.
uint32_t indirect_root = NoIndirectImage;
uint32_t indirect_mapping_offset = 0;
uint32_t indirect_search_iterations = 0;
@@ -344,6 +345,11 @@ inline constexpr uint32_t FirstImageBinding = 1u;
inline constexpr uint32_t FirstComparisonImageBinding = 22u;
inline constexpr uint32_t FirstStorageImageBinding = 29u;
inline constexpr uint32_t ImageBindingCount = 50u;
// Resident image heaps share one sampled-image array at binding 0 of this set. A heap slot
// word is kind << ImageHeapKindShift | element; element 0 is the null descriptor.
inline constexpr uint32_t ImageHeapSet = 1u;
inline constexpr uint32_t ImageHeapCapacity = 65536u;
inline constexpr uint32_t ImageHeapKindShift = 20u;
enum class DescriptorBindingKind : uint32_t {
Buffers = 0u,
@@ -529,6 +535,7 @@ struct ShaderInfo {
bool uses_lds = false;
bool uses_gds = false;
bool uses_flattened_srt = false;
bool uses_image_heap = false;
bool uses_dispatch_threads = false;
bool bvh = false;
bool subgroup_ballot = false;
@@ -681,6 +681,32 @@ bool MaterializeIndirectDescriptor(const ResourcePlan& p
const auto count = table_size >= first + 4u
? std::min((table_size - first - 4u) / step + 1u, domain)
: 0u;
descriptors[resource_index] = {.dword_count = dword_count};
if constexpr (std::is_same_v<Specialization, ResourceSpecialization::Image>) {
const auto& image = program.info.images[resource_index];
// A resident heap maps slot words instead of materializing its entries.
ImageHeapView view;
if (image_heap && runtime.image_heap != nullptr &&
image.resource_class == ImageResourceClass::Sampled &&
image.mip_mode != ImageMipMode::Dynamic &&
runtime.image_heap->Acquire({table_base, table_size, step, first, count, &image},
view)) {
snapshot.specialization_reads.emplace_back(table_base, table_size);
if (view.kinds.empty()) return true;
auto root = root_resource;
root.indirect_root = resource_index;
root.indirect_mapping_offset = static_cast<uint32_t>(snapshot.flattened_srt.size());
root.heap = true;
specializations[resource_index] = root;
snapshot.flattened_srt.insert(snapshot.flattened_srt.end(),
{view.slot_count, static_cast<uint32_t>(view.slots), static_cast<uint32_t>(view.slots >> 32u)});
for (const auto& kind: view.kinds) {
descriptors.push_back(kind);
specializations.push_back(root);
}
return true;
}
}
if (child_count == 0u || (!image_heap && count * child_count > MaxIndirectDescriptorProbes))
return failed("candidate count", fmt::format("rows={} limit={}", count, MaxIndirectDescriptorProbes));
// Affine SMEM offsets wrap in U32 before adding the aligned immediate.
@@ -692,7 +718,6 @@ bool MaterializeIndirectDescriptor(const ResourcePlan& p
return read_failed("image heap read", 0u, 0u, table_base, 0u, heap_words.size() * sizeof(uint32_t));
}
const auto mapping_offset = snapshot.flattened_srt.size();
descriptors[resource_index] = {.dword_count = dword_count};
snapshot.flattened_srt.resize(mapping_offset + 2u, 0u);
for (uint32_t word = 0; word < count; ++word) {
const auto table_offset = affine ? first + word * step : uint64_t {word} * 4u;
@@ -1010,10 +1035,12 @@ static bool BuildResourceSpecialization(const ResourcePlan& program, ResourceSna
const auto key_count = root.indirect_mapping_offset < snapshot.flattened_srt.size()
? snapshot.flattened_srt[root.indirect_mapping_offset]
: 0u;
// A heap maps its slot count and slot address; tables map each key.
size_t mapping_words = 3u;
if (!root.heap)
mapping_words = 1u + size_t {key_count} * (root.indirect_search_iterations == 0u ? 1u : 2u);
if (key_count == 0u ||
static_cast<size_t>(root.indirect_mapping_offset) + 1u +
static_cast<size_t>(key_count) * (root.indirect_search_iterations == 0u ? 1u : 2u) >
snapshot.flattened_srt.size()) {
root.indirect_mapping_offset + mapping_words > snapshot.flattened_srt.size()) {
return SpecializationFail("indirect image specialization has an invalid key mapping");
}
uint32_t exemplar = ImageResource::NoIndirectImage;
@@ -1749,6 +1776,41 @@ bool MaterializeResources(const ResourcePlan& program, const SrtRuntime& runtime
return BuildResourceSpecialization(program, snapshot, specialization);
}
uint32_t ImageHeapKind(const ImageResource& root, DescriptorValue& value) {
// Mirrors BuildResourceSpecialization for sampled roots: entries it would reject, and
// entries that need shader-side conversion, read as null instead of failing the draw.
const auto format = static_cast<Prospero::BufferFormat>((value.dwords[1] >> 20u) & 0x1ffu);
const auto tile = static_cast<Prospero::TileMode>((value.dwords[3] >> 20u) & 0x1fu);
const auto numeric_class =
Prospero::SampledTextureNumericClass(Prospero::RemapTextureFormat(format));
if (NullImageDescriptor(value) || !ValidImageDescriptor(value, root.r128) ||
DescriptorDimension(value, root.dimension) != root.dimension ||
Prospero::IsFmaskTextureFormat(format) || format == Prospero::BufferFormat::k11_11_10UNorm ||
ImageConversionFormat(format) != Prospero::BufferFormat::kInvalid ||
(root.unfiltered_access_only && format == Prospero::BufferFormat::k16UInt &&
tile == Prospero::TileMode::kDepth) ||
numeric_class == Prospero::TextureNumericClass::Unsupported ||
(root.depth_compare && numeric_class != Prospero::TextureNumericClass::Float)) {
value.dwords.fill(0u);
return 0u;
}
return 1u + static_cast<uint32_t>(numeric_class) * 2u + (DescriptorIsCube(value) ? 1u : 0u);
}
ImageResource ImageHeapResource(const ImageResource& root, uint32_t kind) {
ImageResource resource;
resource.resource_class = ImageResourceClass::Sampled;
resource.dimension = root.dimension;
resource.unfiltered_access_only = root.unfiltered_access_only;
resource.depth_compare = root.depth_compare;
resource.r128 = root.r128;
if (kind != 0u) {
resource.numeric_class = static_cast<Prospero::TextureNumericClass>((kind - 1u) / 2u);
resource.cube = ((kind - 1u) & 1u) != 0u;
}
return resource;
}
void ApplyResourceSpecialization(Program& program, const ResourceSpecialization& specialization) {
EXIT_IF(!program.resource_tracking_complete || program.shader_info_complete);
EXIT_IF(program.info.buffers.size() > specialization.buffers.size() ||
@@ -1799,6 +1861,7 @@ void ApplyResourceSpecialization(Program& program, const ResourceSpecialization&
image.indirect_mapping_offset = source.indirect_mapping_offset;
image.indirect_search_iterations = source.indirect_search_iterations;
image.cube = source.cube;
image.heap = source.heap;
image.indirect_resources.clear();
}
for (uint32_t index = 0; index < images.size(); index++) {
@@ -31,6 +31,7 @@ struct ResourceSpecialization {
uint32_t indirect_search_iterations = 0;
bool cube = false;
bool fmask = false;
bool heap = false;
bool operator==(const Image&) const = default;
};
@@ -59,6 +60,13 @@ bool MaterializeResources(const ResourcePlan& program, const SrtRuntime& runtime
// Applies an already-derived specialization to native IR before layout and emission.
void ApplyResourceSpecialization(Program& program, const ResourceSpecialization& specialization);
// Kind of an image heap entry as root sees it, or 0 (and a zeroed value) when root reads it as
// null. Entries of one kind specialize identically.
uint32_t ImageHeapKind(const ImageResource& root, DescriptorValue& value);
// The sampled resource that root resolves entries of kind as; kind 0 gives the identity of
// root's view of a heap.
ImageResource ImageHeapResource(const ImageResource& root, uint32_t kind);
} // namespace Libs::Graphics::ShaderRecompiler::IR
#endif /* EMULATOR_INCLUDE_EMULATOR_GRAPHICS_SHADER_RECOMPILER_RESOURCEMATERIALIZATION_H_ */
@@ -594,6 +594,7 @@ void CollectShaderInfo(Program& program, ShaderStageInputInfo input_info) {
};
info.uses_flattened_srt |= std::ranges::any_of(info.buffers, uses_mapping) ||
std::ranges::any_of(info.images, uses_mapping);
info.uses_image_heap = std::ranges::any_of(info.images, &ImageResource::heap);
switch (program.stage) {
case ShaderType::Vertex:
case ShaderType::Local: CollectVertexInputs(input_info.vertex, info, inputs.components); break;
@@ -12,6 +12,33 @@ class Value;
using SrtMemoryReader = bool (*)(void* userdata, uint64_t address, std::span<uint32_t> values);
// Image descriptors at base + first + k * step, k < count, within size bytes that a sampled
// root selects by GPU-computed byte offset. The host keeps them resident instead of
// materializing them.
struct ImageHeapRequest {
uint64_t base = 0;
uint64_t size = 0;
uint64_t step = 0;
uint64_t first = 0;
uint64_t count = 0;
const ImageResource* root = nullptr;
};
struct ImageHeapView {
uint64_t slots = 0; // Device address of one slot per heap dword.
uint32_t slot_count = 0; // Keys past it select a trailing null slot.
std::span<const DescriptorValue> kinds; // One representative descriptor per kind.
};
class ImageHeapSource {
public:
// False when the host does not keep the table resident.
virtual bool Acquire(const ImageHeapRequest& request, ImageHeapView& view) = 0;
protected:
~ImageHeapSource() = default;
};
struct SrtRuntime {
std::span<const uint32_t> user_data;
uint64_t shader_base = 0;
@@ -20,6 +47,7 @@ struct SrtRuntime {
SrtMemoryReader read_specialization_memory = nullptr;
std::span<const uint32_t> workgroup_counts;
uint64_t indirect_args_address = 0;
ImageHeapSource* image_heap = nullptr;
};
class SrtReadCapture {