mirror of
https://github.com/KytyPS5/KytyPS5.git
synced 2026-10-28 13:56:59 +01:00
graphics: keep bounded image heaps resident in a bindless descriptor set
This commit is contained in:
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user