mirror of
https://github.com/KytyPS5/KytyPS5.git
synced 2026-10-29 08:41:20 +00:00
graphics: hold image heap descriptors whose address another extent pins
This commit is contained in:
1 parent
c5f7644751
commit
95bbc9c65d
6 files changed
+99
No files matched your search
@@ -1302,6 +1302,19 @@ ImageId TextureCache::FindImage(ImageDesc& desc, bool exact_format) {
|
|||||||
view_layer = overlap.layer;
|
view_layer = overlap.layer;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// PS5: image heaps hold a descriptor per dynamic-resolution extent of a render target,
|
||||||
|
// and only one extent at an address holds data, so heap lookups create no second one.
|
||||||
|
if (!result && desc.heap &&
|
||||||
|
std::any_of(candidates.begin(), candidates.end(), [&](ImageId id) {
|
||||||
|
const auto* image = m_slot_images.try_get(id);
|
||||||
|
return image != nullptr && image->registered &&
|
||||||
|
image->heap_refs.load(std::memory_order_relaxed) != 0u &&
|
||||||
|
image->info.data.address == desc.info.data.address &&
|
||||||
|
image->info.type == desc.info.type &&
|
||||||
|
image->info.extent != desc.info.extent;
|
||||||
|
})) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
} else if (block_alias) {
|
} else if (block_alias) {
|
||||||
for (const auto candidate: candidates) {
|
for (const auto candidate: candidates) {
|
||||||
const auto& owner = m_slot_images[candidate];
|
const auto& owner = m_slot_images[candidate];
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ public:
|
|||||||
ImageInfo info;
|
ImageInfo info;
|
||||||
ImageViewInfo view_info;
|
ImageViewInfo view_info;
|
||||||
BindingType type = BindingType::Texture;
|
BindingType type = BindingType::Texture;
|
||||||
|
bool heap = false; // PS5: resolved for a resident image heap.
|
||||||
};
|
};
|
||||||
|
|
||||||
TextureCache(GraphicContext& graphics, CommandScheduler& scheduler, PageManager& page_manager,
|
TextureCache(GraphicContext& graphics, CommandScheduler& scheduler, PageManager& page_manager,
|
||||||
|
|||||||
@@ -604,8 +604,13 @@ TextureBinding RenderExecutor::ResolveTexture(const ShaderRecompiler::IR::ImageR
|
|||||||
view_levels, desc.info.resources.layers);
|
view_levels, desc.info.resources.layers);
|
||||||
desc.view_info.base_level = view_base;
|
desc.view_info.base_level = view_base;
|
||||||
desc.type = storage ? TextureCache::BindingType::Storage : TextureCache::BindingType::Texture;
|
desc.type = storage ? TextureCache::BindingType::Storage : TextureCache::BindingType::Texture;
|
||||||
|
desc.heap = resource.heap;
|
||||||
|
|
||||||
auto id = texture_cache.FindImage(desc, shader_conversion);
|
auto id = texture_cache.FindImage(desc, shader_conversion);
|
||||||
|
if (!id) {
|
||||||
|
// Another extent at the address holds it; the heap retries once an image is created.
|
||||||
|
return {.desc = std::move(desc), .layout = vk::ImageLayout::eGeneral};
|
||||||
|
}
|
||||||
auto* image = &texture_cache.GetImage(id);
|
auto* image = &texture_cache.GetImage(id);
|
||||||
const bool stencil_association = static_cast<bool>(image->depth_id);
|
const bool stencil_association = static_cast<bool>(image->depth_id);
|
||||||
if (stencil_association) {
|
if (stencil_association) {
|
||||||
|
|||||||
@@ -394,6 +394,9 @@ uint32_t ImageHeap::Allocate() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void ImageHeap::Retire(View& view) {
|
void ImageHeap::Retire(View& view) {
|
||||||
|
if (view.released.empty()) return;
|
||||||
|
// A retired index is reused once its tick ends, so no held entry may keep it.
|
||||||
|
std::erase_if(view.held, [&](uint32_t index) { return m_elements[index].refs == 0u; });
|
||||||
const auto tick = m_context.GetCommandScheduler().CurrentTick();
|
const auto tick = m_context.GetCommandScheduler().CurrentTick();
|
||||||
for (const auto index: view.released) m_retired.emplace_back(tick, index);
|
for (const auto index: view.released) m_retired.emplace_back(tick, index);
|
||||||
view.released.clear();
|
view.released.clear();
|
||||||
@@ -440,6 +443,7 @@ void ImageHeap::Rebind(RenderExecutor& executor) {
|
|||||||
KYTY_PROFILER_FUNCTION();
|
KYTY_PROFILER_FUNCTION();
|
||||||
auto& textures = m_context.GetTextureCache();
|
auto& textures = m_context.GetTextureCache();
|
||||||
const auto epoch = Image::heap_epoch.load(std::memory_order_relaxed);
|
const auto epoch = Image::heap_epoch.load(std::memory_order_relaxed);
|
||||||
|
const auto serial = Image::serials.load(std::memory_order_relaxed);
|
||||||
for (auto* view: m_active) {
|
for (auto* view: m_active) {
|
||||||
bool stale = false;
|
bool stale = false;
|
||||||
const auto mark = [&](uint32_t index) { m_elements[index].stale = stale = true; };
|
const auto mark = [&](uint32_t index) { m_elements[index].stale = stale = true; };
|
||||||
@@ -468,6 +472,19 @@ void ImageHeap::Rebind(RenderExecutor& executor) {
|
|||||||
mark(index);
|
mark(index);
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
|
// Held elements retry once an image is created, such as a render target of their extent.
|
||||||
|
if (view->held_serial != serial) {
|
||||||
|
view->held_serial = serial;
|
||||||
|
std::erase_if(view->held, [&](uint32_t index) {
|
||||||
|
const auto& element = m_elements[index];
|
||||||
|
if (element.refs == 0u || element.stale) return true;
|
||||||
|
const auto binding = executor.ResolveTexture(
|
||||||
|
IR::ImageHeapResource(view->profile, element.kind), element.value);
|
||||||
|
if (binding.image_id) mark(index);
|
||||||
|
// An unmapped texture stays null.
|
||||||
|
return binding.image_id || binding.desc.info.data.Empty();
|
||||||
|
});
|
||||||
|
}
|
||||||
if (stale) {
|
if (stale) {
|
||||||
// A changed image needs a new element; pending draws still read the old one. The new
|
// A changed image needs a new element; pending draws still read the old one. The new
|
||||||
// element keeps the old value, since a view takes table changes only when acquired.
|
// element keeps the old value, since a view takes table changes only when acquired.
|
||||||
@@ -496,6 +513,8 @@ void ImageHeap::Rebind(RenderExecutor& executor) {
|
|||||||
view->dcc.push_back(index);
|
view->dcc.push_back(index);
|
||||||
}
|
}
|
||||||
view->away.push_back(index);
|
view->away.push_back(index);
|
||||||
|
} else if (!binding.desc.info.data.Empty()) {
|
||||||
|
view->held.push_back(index);
|
||||||
}
|
}
|
||||||
m_elements[index].binding = std::move(binding);
|
m_elements[index].binding = std::move(binding);
|
||||||
m_writes.push_back(index);
|
m_writes.push_back(index);
|
||||||
|
|||||||
@@ -81,6 +81,7 @@ private:
|
|||||||
std::vector<uint32_t> released; // No slot selects them after the next upload.
|
std::vector<uint32_t> released; // No slot selects them after the next upload.
|
||||||
std::vector<uint32_t> away; // Not yet moved to the read layout.
|
std::vector<uint32_t> away; // Not yet moved to the read layout.
|
||||||
std::vector<uint32_t> dcc; // Color metadata clears apply on each discovery.
|
std::vector<uint32_t> dcc; // Color metadata clears apply on each discovery.
|
||||||
|
std::vector<uint32_t> held; // Another extent holds their address.
|
||||||
std::unique_ptr<Buffer> buffer;
|
std::unique_ptr<Buffer> buffer;
|
||||||
size_t dirty_begin = 0; // Slots to upload.
|
size_t dirty_begin = 0; // Slots to upload.
|
||||||
size_t dirty_end = 0;
|
size_t dirty_end = 0;
|
||||||
@@ -88,6 +89,7 @@ private:
|
|||||||
size_t changed_end = 0;
|
size_t changed_end = 0;
|
||||||
uint64_t resolve_epoch = 0;
|
uint64_t resolve_epoch = 0;
|
||||||
uint64_t layout_epoch = 0;
|
uint64_t layout_epoch = 0;
|
||||||
|
uint64_t held_serial = 0; // Image::serials when held was last retried.
|
||||||
};
|
};
|
||||||
|
|
||||||
struct Memory {
|
struct Memory {
|
||||||
|
|||||||
@@ -5511,6 +5511,65 @@ public:
|
|||||||
ImageHeapTestAccess::ImageOf(heap, filtered.slots[240]),
|
ImageHeapTestAccess::ImageOf(heap, filtered.slots[240]),
|
||||||
"a reactivated view kept an element whose image slot holds a new image");
|
"a reactivated view kept an element whose image slot holds a new image");
|
||||||
|
|
||||||
|
// Dynamic-resolution tables hold a descriptor per extent of one render target, each with
|
||||||
|
// its own pitch. Only the first gets an image; the others wait until an image of their
|
||||||
|
// extent exists.
|
||||||
|
const auto extent = [&](uint32_t width) {
|
||||||
|
auto words = descriptor(50, Format::k8_8_8_8UNorm);
|
||||||
|
words[2] = (width - 1u) | (3u << 14u);
|
||||||
|
return words;
|
||||||
|
};
|
||||||
|
const auto resolve = [&](const auto &root, const auto &words) {
|
||||||
|
return executor.ResolveTexture(root, {.dwords = words, .dword_count = 8u}).image_id;
|
||||||
|
};
|
||||||
|
write(50, extent(64));
|
||||||
|
write(51, extent(128));
|
||||||
|
Require(name, "extents", acquire(sampled), "the view was declined");
|
||||||
|
draw();
|
||||||
|
Require(name, "held extent",
|
||||||
|
ImageHeapTestAccess::ImageOf(heap, filtered.slots[400]) &&
|
||||||
|
!ImageHeapTestAccess::ImageOf(heap, filtered.slots[408]),
|
||||||
|
"a second extent of one address got its own image");
|
||||||
|
// A non-heap lookup, as for a render target, creates the second extent.
|
||||||
|
const auto rendered = resolve(sampled, extent(128));
|
||||||
|
Require(name, "rendered extent", acquire(sampled), "the view was declined");
|
||||||
|
draw();
|
||||||
|
Require(name, "held descriptor",
|
||||||
|
rendered && ImageHeapTestAccess::ImageOf(heap, filtered.slots[408]) == rendered,
|
||||||
|
"a held descriptor missed the image made for its extent");
|
||||||
|
// A released held element's index is reused once its tick ends; it is no longer held.
|
||||||
|
write(52, extent(256));
|
||||||
|
Require(name, "third extent", acquire(sampled), "the view was declined");
|
||||||
|
draw();
|
||||||
|
const auto held = filtered.slots[416];
|
||||||
|
write(52, {});
|
||||||
|
Require(name, "released extent", acquire(sampled), "the view was declined");
|
||||||
|
draw();
|
||||||
|
write(53, descriptor(53, Format::k8_8_8_8UNorm));
|
||||||
|
Require(name, "reused element", acquire(sampled), "the view was declined");
|
||||||
|
draw();
|
||||||
|
Require(name, "retry", acquire(sampled), "the view was declined");
|
||||||
|
draw();
|
||||||
|
Require(name, "reused held element", held != 0u && filtered.slots[424] == held,
|
||||||
|
"a reused element was retried as the held element it replaced");
|
||||||
|
// A 2D view of a heap-pinned volume is another image type, not another extent.
|
||||||
|
auto plane_root = sampled;
|
||||||
|
plane_root.heap = true;
|
||||||
|
auto volume_root = plane_root;
|
||||||
|
volume_root.dimension = ShaderRecompiler::Decoder::ImageDimension::Dim3D;
|
||||||
|
auto volume_words = descriptor(54, Format::k8_8_8_8UNorm);
|
||||||
|
volume_words[3] = DstSel(4, 5, 6, 7) |
|
||||||
|
(static_cast<uint32_t>(Prospero::ImageType::kColor3D) << 28u);
|
||||||
|
volume_words[4] = 3u;
|
||||||
|
const auto volume = resolve(volume_root, volume_words);
|
||||||
|
Require(name, "volume", static_cast<bool>(volume), "a volume texture was not resolved");
|
||||||
|
auto &volume_refs = textures.GetImage(volume).heap_refs;
|
||||||
|
volume_refs.fetch_add(1u);
|
||||||
|
const auto plane = resolve(plane_root, descriptor(54, Format::k8_8_8_8UNorm));
|
||||||
|
volume_refs.fetch_sub(1u);
|
||||||
|
Require(name, "volume plane", static_cast<bool>(plane),
|
||||||
|
"a 2D view of a pinned volume was held as another extent");
|
||||||
|
|
||||||
// A table follows its descriptor's size and keeps its elements. Growing probes the new
|
// A table follows its descriptor's size and keeps its elements. Growing probes the new
|
||||||
// words, shrinking releases them, and a draw sees one size.
|
// words, shrinking releases them, and a draw sees one size.
|
||||||
write(table_size / 32u, descriptor(40, Format::k8_8_8_8UNorm));
|
write(table_size / 32u, descriptor(40, Format::k8_8_8_8UNorm));
|
||||||
|
|||||||
Reference in new issue
Block a user