mirror of
https://github.com/KytyPS5/KytyPS5.git
synced 2026-10-20 10:56:58 +02:00
perf: reduce texture registration and lookup overhead
This commit is contained in:
@@ -3,13 +3,15 @@
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace {
|
||||
|
||||
using Owners = std::vector<uint32_t>;
|
||||
using Table = Libs::Graphics::MultiLevelPageTable<Owners>;
|
||||
using OwnerIndex = Libs::Graphics::MultiRangePageOwnerIndex<uint32_t>;
|
||||
using PageOwners = Libs::Graphics::InlinePageOwnerList<uint32_t, 16>;
|
||||
using OwnerTable = Libs::Graphics::MultiLevelPageTable<PageOwners, 20, 40, 10>;
|
||||
|
||||
void Check(bool value, const char* text) {
|
||||
if (!value) {
|
||||
@@ -77,76 +79,93 @@ void TestAddressSpaceBoundaries() {
|
||||
"final page supports allocating and nonallocating access");
|
||||
}
|
||||
|
||||
void TestMultiRangeRegistrationDeduplicatesPages() {
|
||||
OwnerIndex index;
|
||||
// Depth and stencil-like planes overlap tracking pages and share one 1 MiB bucket.
|
||||
Check(index.Register(7, {{0x101000, 0x2800}, {0x102000, 0x3000}}),
|
||||
"multi-range owner registers");
|
||||
Check(index.CoarseMembershipCount(1) == 1,
|
||||
"one owner is inserted once in a shared 1 MiB bucket");
|
||||
Check(index.TrackingMembershipCount(0x102) == 1,
|
||||
"overlapping planes insert one 4 KiB membership");
|
||||
Check(!index.Register(7, {{0x101000, 0x1000}}), "duplicate owner registration hard-fails");
|
||||
void TestInlineOwnerStorageAndOverflow() {
|
||||
PageOwners owners;
|
||||
for (uint32_t owner = 1; owner <= 18; ++owner) {
|
||||
owners.push_back(owner);
|
||||
}
|
||||
Check(owners.size() == 18 && owners.front() == 1,
|
||||
"inline owner storage grows past its 16-owner capacity");
|
||||
uint32_t expected = 1;
|
||||
for (const uint32_t owner: owners) {
|
||||
Check(owner == expected++, "overflow preserves registration order");
|
||||
}
|
||||
Check(owners.Erase(5) && owners.size() == 17 && !owners.Contains(5),
|
||||
"overflow erase removes only the requested owner");
|
||||
Check(owners.Erase(18) && owners.size() == 16,
|
||||
"overflow storage shrinks back to inline capacity");
|
||||
const std::vector<uint32_t> remaining {1, 2, 3, 4, 6, 7, 8, 9,
|
||||
10, 11, 12, 13, 14, 15, 16, 17};
|
||||
size_t remaining_index = 0;
|
||||
for (const uint32_t owner: owners) {
|
||||
Check(owner == remaining[remaining_index++],
|
||||
"overflow-to-inline shrink preserves every owner");
|
||||
}
|
||||
Check(!owners.Erase(99), "missing inline owner is reported without mutation");
|
||||
|
||||
const auto owners = index.Query(0x100000, 0x10000);
|
||||
Check(owners.size() == 1 && owners.front() == 7, "multi-page query returns an owner once");
|
||||
PageOwners moved = std::move(owners);
|
||||
Check(owners.empty() && moved.size() == remaining.size() && moved.front() == 1,
|
||||
"moving a full inline owner list leaves the source empty");
|
||||
PageOwners partial;
|
||||
partial.push_back(41);
|
||||
partial.push_back(42);
|
||||
PageOwners partial_moved = std::move(partial);
|
||||
Check(partial.empty() && partial_moved.size() == 2 && partial_moved[1] == 42,
|
||||
"moving a partially populated list copies only live owners");
|
||||
PageOwners assigned;
|
||||
assigned.push_back(99);
|
||||
assigned = std::move(partial_moved);
|
||||
Check(partial_moved.empty() && assigned.size() == 2 && assigned.front() == 41,
|
||||
"move assignment replaces an inline list without reading inactive slots");
|
||||
PageOwners empty;
|
||||
PageOwners empty_moved = std::move(empty);
|
||||
Check(empty.empty() && empty_moved.empty(), "moving an empty owner list is safe");
|
||||
}
|
||||
|
||||
void TestSharedPageUnregisterLifecycle() {
|
||||
OwnerIndex index;
|
||||
const std::vector<OwnerIndex::ByteRange> ranges {{0x202000, 0x2000}};
|
||||
Check(index.Register(11, ranges) && index.Register(22, ranges),
|
||||
"two owners register on identical pages");
|
||||
Check(index.CoarseMembershipCount(2) == 2 && index.TrackingMembershipCount(0x202) == 2,
|
||||
"coarse and tracking pages retain both owners");
|
||||
|
||||
std::vector<OwnerIndex::ByteRange> releases;
|
||||
Check(index.Unregister(11, releases), "first owner unregisters");
|
||||
Check(releases.empty(), "shared tracking pages are not released with one owner remaining");
|
||||
Check(index.Query(0x202000, 1).size() == 1 && index.Query(0x202000, 1).front() == 22,
|
||||
"unregistering one owner preserves the other");
|
||||
Check(!index.Unregister(11, releases), "missing membership hard-fails without mutation");
|
||||
|
||||
Check(index.Unregister(22, releases), "final owner unregisters");
|
||||
Check(releases.size() == 1 && releases.front().address == 0x202000 &&
|
||||
releases.front().size == 0x2000,
|
||||
"adjacent final-owner tracking pages return one contiguous release");
|
||||
void TestOneMiBRegistrationGranularity() {
|
||||
static_assert(OwnerTable::kPageBits == 20);
|
||||
OwnerTable table;
|
||||
OwnerTable::PageRange pages {};
|
||||
constexpr uint64_t range_size = 64ull * 1024 * 1024;
|
||||
Check(OwnerTable::TryGetPageRange(0, range_size, pages), "large owner range is valid");
|
||||
Check(pages.first == 0 && pages.last_exclusive == 64,
|
||||
"64 MiB registration touches exactly 64 one-MiB entries");
|
||||
for (size_t page = pages.first; page < pages.last_exclusive; ++page) {
|
||||
table[page].push_back(7);
|
||||
}
|
||||
Check(table.AllocatedBucketCount() == 1,
|
||||
"large registration uses one sparse second-level bucket");
|
||||
for (size_t page = pages.first; page < pages.last_exclusive; ++page) {
|
||||
auto* owners = table.Find(page);
|
||||
Check(owners != nullptr && owners->size() == 1 && owners->front() == 7,
|
||||
"each touched one-MiB entry retains the owner once");
|
||||
Check(owners->Erase(7), "large owner unregisters by coarse page");
|
||||
}
|
||||
}
|
||||
|
||||
void TestStrictByteFilteringAndPredicate() {
|
||||
OwnerIndex index;
|
||||
Check(index.Register(31, {{0x300100, 0x100}}), "first byte-disjoint owner registers");
|
||||
Check(index.Register(32, {{0x300800, 0x100}}), "second byte-disjoint owner registers");
|
||||
Check(index.Register(33, {{0x30f000, 0x100}}), "coarse-only owner registers");
|
||||
Check(index.TrackingMembershipCount(0x300) == 2,
|
||||
"byte-disjoint owners share one tracking page");
|
||||
Check(index.CoarseMembershipCount(3) == 3,
|
||||
"all owners share one coarse candidate bucket");
|
||||
Check(index.Query(0x300400, 0x40).empty(), "page hit without byte overlap is filtered out");
|
||||
const auto page_candidates = index.QueryCandidates(0x300400, 0x40);
|
||||
Check(page_candidates.size() == 2,
|
||||
"fault candidate query retains touched-page owners and rejects coarse-only owners");
|
||||
const auto first = index.Query(0x300180, 0x10);
|
||||
Check(first.size() == 1 && first.front() == 31,
|
||||
"strict byte overlap selects only the matching owner");
|
||||
const auto predicate_filtered =
|
||||
index.Query(0x300000, 0x1000, [](uint32_t owner) { return owner == 32; });
|
||||
Check(predicate_filtered.size() == 1 && predicate_filtered.front() == 32,
|
||||
"supplied predicate filters query owners");
|
||||
void TestSharedCoarsePageLifecycle() {
|
||||
OwnerTable table;
|
||||
auto& owners = table[2];
|
||||
owners.push_back(11);
|
||||
owners.push_back(22);
|
||||
Check(owners.size() == 2, "two images share one coarse page");
|
||||
Check(owners.Erase(11) && owners.size() == 1 && owners.front() == 22,
|
||||
"unregistering one image preserves its coarse-page neighbor");
|
||||
Check(!owners.Erase(11), "double unregister is rejected without mutation");
|
||||
Check(owners.Erase(22) && owners.empty(), "final coarse-page owner unregisters");
|
||||
}
|
||||
|
||||
void TestOwnerIndexAddressSpaceBoundary() {
|
||||
OwnerIndex index;
|
||||
constexpr uint64_t last_byte = OwnerIndex::CoarseTable::kAddressSpaceSize - 1;
|
||||
Check(index.Register(41, {{last_byte, 1}}), "final guest byte registers");
|
||||
const auto exact = index.Query(last_byte, 1);
|
||||
Check(exact.size() == 1 && exact.front() == 41,
|
||||
"strict query finds an exact overlap at the final guest byte");
|
||||
Check(index.Query(last_byte - 1, 1).empty(),
|
||||
"strict query preserves half-open overlap boundaries");
|
||||
const auto page_candidates = index.QueryCandidates(last_byte - 1, 1);
|
||||
Check(page_candidates.size() == 1 && page_candidates.front() == 41,
|
||||
"page candidate query retains a byte-disjoint owner on the final tracking page");
|
||||
void TestOneMiBBoundaries() {
|
||||
OwnerTable::PageRange range {};
|
||||
Check(OwnerTable::TryGetPageRange(0x0fffff, 2, range),
|
||||
"range crossing a one-MiB boundary is valid");
|
||||
Check(range.first == 0 && range.last_exclusive == 2,
|
||||
"cross-boundary registration touches both coarse pages");
|
||||
Check(OwnerTable::TryGetPageRange(OwnerTable::kAddressSpaceSize - 1, 1, range),
|
||||
"final guest byte maps to a coarse owner page");
|
||||
Check(range.first == OwnerTable::kPageCount - 1 &&
|
||||
range.last_exclusive == OwnerTable::kPageCount,
|
||||
"final guest byte uses the final one-MiB page");
|
||||
}
|
||||
|
||||
} // namespace
|
||||
@@ -156,10 +175,10 @@ int main() {
|
||||
TestCrossBucketRange();
|
||||
TestQueriesDoNotAllocate();
|
||||
TestAddressSpaceBoundaries();
|
||||
TestMultiRangeRegistrationDeduplicatesPages();
|
||||
TestSharedPageUnregisterLifecycle();
|
||||
TestStrictByteFilteringAndPredicate();
|
||||
TestOwnerIndexAddressSpaceBoundary();
|
||||
TestInlineOwnerStorageAndOverflow();
|
||||
TestOneMiBRegistrationGranularity();
|
||||
TestSharedCoarsePageLifecycle();
|
||||
TestOneMiBBoundaries();
|
||||
std::printf("ImagePageTableTests: all cases passed\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user