graphics: keep image heap descriptors of unmapped textures null

This commit is contained in:
nmzik
2026-10-23 06:40:04 +02:00
parent 27dc7c281f
commit 38c884e8c1
4 changed files with 106 additions and 5 deletions
@@ -585,6 +585,10 @@ TextureBinding RenderExecutor::ResolveTexture(const ShaderRecompiler::IR::ImageR
const auto alignment = desc.info.DataAlignment();
EXIT_NOT_IMPLEMENTED(desc.info.data.size == 0 || alignment == 0 ||
(address & (static_cast<uint64_t>(alignment) - 1u)) != 0);
// Image heaps keep the descriptors of unmapped textures, which PS5 never samples.
if (resource.heap && !m_context.IsMapped(address, desc.info.data.size)) {
return {.layout = vk::ImageLayout::eGeneral};
}
if (storage) {
ValidateStorageTexture(resource, descriptor, desc.info.data.size);
}
@@ -131,6 +131,7 @@ bool ImageHeap::Acquire(const IR::ImageHeapRequest& request, IR::ImageHeapView&
created->base = request.base;
created->size = size;
created->words.resize(size / 4u);
created->dead.resize(size / 4u);
created->dirty_pages.assign((Common::AlignUp(request.base + size, TRACKER_PAGE_SIZE) -
Common::AlignDown(request.base, TRACKER_PAGE_SIZE)) /
TRACKER_PAGE_SIZE,
@@ -278,7 +279,10 @@ void ImageHeap::UpdateProbe(View& view, uint64_t probe) {
std::copy_n(words.begin() + offset,
std::min<size_t>(view.profile.r128 ? 4u : 8u, words.size() - offset),
value.dwords.begin());
const auto kind = IR::ImageHeapKind(view.profile, value);
// A buried word stays null in every view until its descriptor changes.
auto& dead = view.memory.dead[offset];
if (dead != 0u && dead != DescriptorHash {}(value)) dead = 0u;
const auto kind = dead != 0u ? 0u : IR::ImageHeapKind(view.profile, value);
Assign(view, offset, kind == 0u ? 0u : Intern(view, kind, value));
}
@@ -288,6 +292,32 @@ void ImageHeap::Assign(View& view, size_t slot, uint32_t entry) {
if (previous != entry) Widen(view.dirty_begin, view.dirty_end, slot, slot + 1u);
}
void ImageHeap::Bury() {
if (m_unmapped.empty()) return;
for (const auto& memory: m_memories) {
for (const auto& view: memory->views) {
bool buried = false;
for (const auto& [value, index]: view->elements) {
const auto& binding = m_elements[index].binding;
if (binding.image_id && std::ranges::any_of(m_unmapped, [&](GuestRange range) {
return ImageRangeOverlaps(binding.desc.info.data, range);
})) {
m_elements[index].stale = buried = true;
}
}
if (!buried) continue;
for (uint64_t probe = 0; probe < view->count; ++probe) {
const auto slot = (view->first + probe * view->step) / 4u;
const auto& element = m_elements[view->slots[slot] & ElementMask];
if (!element.stale) continue;
memory->dead[slot] = DescriptorHash {}(element.value);
Assign(*view, slot, 0u);
}
}
}
m_unmapped.clear();
}
uint32_t ImageHeap::Intern(View& view, uint32_t kind, const DescriptorValue& value) {
const auto arm = static_cast<uint32_t>(std::ranges::find(view.kind_keys, kind) -
view.kind_keys.begin());
@@ -398,9 +428,9 @@ void ImageHeap::Rebind(RenderExecutor& executor) {
}
for (const auto index: view->pending) {
if (m_elements[index].refs == 0u) continue;
auto binding = executor.ResolveTexture(
IR::ImageHeapResource(view->profile, m_elements[index].kind),
m_elements[index].value);
auto resource = IR::ImageHeapResource(view->profile, m_elements[index].kind);
resource.heap = true; // Resolves only mapped textures.
auto binding = executor.ResolveTexture(resource, m_elements[index].value);
if (binding.image_id) {
auto& image = textures.GetImage(binding.image_id);
image.heap_refs.fetch_add(1u, std::memory_order_relaxed);
@@ -483,6 +513,12 @@ void ImageHeap::Commit(CommandBuffer& buffer, vk::PipelineBindPoint bind_point,
}
void ImageHeap::UnmapMemory(uint64_t address, uint64_t size) {
// Descriptors of textures in the range stay null even once it is mapped again.
if (!m_unmapped.empty() && m_unmapped.back().End() == address) {
m_unmapped.back().size += size;
} else {
m_unmapped.push_back({address, size});
}
auto& scheduler = m_context.GetCommandScheduler();
std::scoped_lock lock {m_mutex};
std::erase_if(m_memories, [&](const std::unique_ptr<Memory>& memory) {
@@ -33,7 +33,10 @@ public:
KYTY_CLASS_NO_COPY(ImageHeap);
// Starts a draw or dispatch, before its programs acquire views.
void BeginDraw() { ++m_draw; }
void BeginDraw() {
++m_draw;
Bury();
}
bool Acquire(const ShaderRecompiler::IR::ImageHeapRequest& request,
ShaderRecompiler::IR::ImageHeapView& result) override;
// Image discovery: resolves new elements and rediscovers changed images.
@@ -89,6 +92,7 @@ private:
uint64_t size = 0;
std::vector<uint32_t> words;
std::vector<uint8_t> dirty_pages; // Unwatched, so unread since a write.
std::vector<uint64_t> dead; // Per word, a buried descriptor's hash.
uint64_t draw = 0; // Last draw that refreshed it.
std::vector<std::unique_ptr<View>> views;
};
@@ -101,6 +105,7 @@ private:
void UpdateProbes(View& view, uint64_t begin, uint64_t end);
void UpdateProbe(View& view, uint64_t probe);
void Assign(View& view, size_t slot, uint32_t entry);
void Bury();
uint32_t Intern(View& view, uint32_t kind, const DescriptorValue& value);
void Release(View& view, uint32_t entry);
void Unreference(Element& element);
@@ -120,6 +125,7 @@ private:
std::vector<std::pair<size_t, size_t>> m_runs;
std::vector<uint32_t> m_scratch;
std::vector<uint32_t> m_writes; // Elements whose descriptors changed.
std::vector<GuestRange> m_unmapped; // Since the last Bury.
vk::DescriptorSetLayout m_layout = nullptr;
vk::DescriptorPool m_pool = nullptr;
vk::DescriptorSet m_set = nullptr;