mirror of
https://github.com/boykopovar/AnyPS5.git
synced 2026-10-27 00:28:14 +00:00
Merge pull request #8026 from NFSS10/perf/agc-sampled-snapshot-buffer-reuse
perf(agc): reuse sampled snapshot byte buffers across cache entry replacement
This commit is contained in:
5 files changed
+139
-18
No files matched your search
@@ -37,9 +37,12 @@ std::uint64_t SampledTextureBudget(const VkPhysicalDeviceMemoryProperties& memor
|
||||
bool SampledBudgetReportDue(std::uint64_t reported, std::uint64_t budget, std::chrono::steady_clock::duration sinceReport);
|
||||
std::optional<std::uint32_t> LargestDeviceLocalHeap(const VkPhysicalDeviceMemoryProperties& memory);
|
||||
std::uint64_t StorageTextureCacheBudget(const Context& context);
|
||||
inline constexpr std::uint64_t SpareSnapshotPresents = 16;
|
||||
struct TextureCacheUse {
|
||||
std::size_t sampledEntries = 0;
|
||||
std::size_t spareEntries = 0;
|
||||
std::uint64_t sampledBytes = 0;
|
||||
std::uint64_t spareBytes = 0;
|
||||
std::uint64_t storageBytes = 0;
|
||||
};
|
||||
TextureCacheUse TextureCacheUsage();
|
||||
|
||||
@@ -665,7 +665,10 @@ bool FillDccClear(VkFormat format, DccKeys keys, bool alphaOnMsb, std::span<std:
|
||||
return true;
|
||||
}
|
||||
Layout layout{};
|
||||
if (!IsDccClear(keys) || keys == DccKeys::ClearRegister || !LayoutFor(format, layout)) return false;
|
||||
if (!IsDccClear(keys) || keys == DccKeys::ClearRegister || !LayoutFor(format, layout)) {
|
||||
std::fill(bytes.begin(), bytes.end(), std::byte{0});
|
||||
return false;
|
||||
}
|
||||
const bool color = keys == DccKeys::Clear1110 || keys == DccKeys::Clear1111;
|
||||
const bool alpha = keys == DccKeys::Clear0001 || keys == DccKeys::Clear1111;
|
||||
const int alphaChannel = layout.channels == 3 ? -1 : alphaOnMsb ? static_cast<int>(layout.channels) - 1 : 0;
|
||||
@@ -680,7 +683,9 @@ bool FillDccClear(VkFormat format, DccKeys keys, bool alphaOnMsb, std::span<std:
|
||||
if (((value >> i) & 1u) != 0) element[bit / 8u] |= static_cast<std::byte>(1u << (bit % 8u));
|
||||
}
|
||||
}
|
||||
for (std::size_t offset = 0; offset + element.size() <= bytes.size(); offset += element.size()) std::memcpy(bytes.data() + offset, element.data(), element.size());
|
||||
std::size_t offset = 0;
|
||||
for (; offset + element.size() <= bytes.size(); offset += element.size()) std::memcpy(bytes.data() + offset, element.data(), element.size());
|
||||
std::fill(bytes.begin() + offset, bytes.end(), std::byte{0});
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -133,6 +133,8 @@ struct TextureCache {
|
||||
std::list<CachedTexture> entries;
|
||||
std::unordered_map<TextureKey, std::list<CachedTexture>::iterator, TextureKeyHash> index;
|
||||
std::unordered_multimap<const StorageTexture*, std::list<CachedTexture>::iterator> views;
|
||||
std::vector<std::pair<std::vector<std::byte>, std::uint64_t>> spare;
|
||||
std::uint64_t spareBytes = 0;
|
||||
std::uint64_t bytes = 0;
|
||||
std::size_t passed = 0;
|
||||
std::chrono::steady_clock::time_point passedSince{};
|
||||
@@ -260,6 +262,39 @@ void touchTexture(TextureCache& cache, std::list<CachedTexture>::iterator it) {
|
||||
it->present = Recorder::Presents();
|
||||
}
|
||||
|
||||
constexpr std::uint64_t SpareSnapshotBytes = 256ull << 20;
|
||||
|
||||
void parkSnapshot(TextureCache& cache, std::vector<std::byte>& bytes) {
|
||||
const auto size = bytes.size();
|
||||
if (size == 0) return;
|
||||
const auto present = Recorder::Presents();
|
||||
while (!cache.spare.empty() && (present - cache.spare.front().second > SpareSnapshotPresents || cache.spareBytes + size > SpareSnapshotBytes)) {
|
||||
cache.spareBytes -= cache.spare.front().first.size();
|
||||
cache.spare.erase(cache.spare.begin());
|
||||
}
|
||||
if (cache.spareBytes + size <= SpareSnapshotBytes) {
|
||||
cache.spareBytes += size;
|
||||
cache.spare.emplace_back(std::move(bytes), present);
|
||||
}
|
||||
bytes = std::vector<std::byte>{};
|
||||
}
|
||||
|
||||
std::vector<std::byte> spareSnapshot(TextureCache& cache, std::size_t bytes) {
|
||||
const auto present = Recorder::Presents();
|
||||
while (!cache.spare.empty() && present - cache.spare.front().second > SpareSnapshotPresents) {
|
||||
cache.spareBytes -= cache.spare.front().first.size();
|
||||
cache.spare.erase(cache.spare.begin());
|
||||
}
|
||||
for (auto spare = cache.spare.begin(); spare != cache.spare.end(); ++spare) {
|
||||
if (spare->first.size() != bytes) continue;
|
||||
cache.spareBytes -= bytes;
|
||||
std::vector<std::byte> taken = std::move(spare->first);
|
||||
cache.spare.erase(spare);
|
||||
return taken;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
bool admitTexture(TextureCache& cache, const TextureKey& key, std::uint64_t incoming, std::uint64_t budget) {
|
||||
if (cache.bytes + incoming <= budget) return true;
|
||||
if (const auto now = std::chrono::steady_clock::now(); now - cache.passedSince >= std::chrono::seconds(1)) {
|
||||
@@ -859,6 +894,7 @@ std::shared_ptr<Texture> cachedTexture(const Context& context, std::span<const s
|
||||
std::lock_guard lock(cache.mutex);
|
||||
const CacheHold held(sampledCacheHeld);
|
||||
dropUncachedViews(cache);
|
||||
std::vector<std::byte> recycled;
|
||||
if (auto it = findTexture(cache, key); it != cache.entries.end()) {
|
||||
if (it->source != nullptr) {
|
||||
// The view follows the storage image, whatever the GPU wrote to it since; guest memory
|
||||
@@ -905,10 +941,14 @@ std::shared_ptr<Texture> cachedTexture(const Context& context, std::span<const s
|
||||
return it->texture;
|
||||
}
|
||||
}
|
||||
if (it->bytes.size() == bytes) recycled = std::move(it->bytes);
|
||||
else parkSnapshot(cache, it->bytes);
|
||||
eraseTexture(cache, it);
|
||||
++cache.counts.stale;
|
||||
}
|
||||
CachedTexture entry{key, address, std::vector<std::byte>(source != nullptr ? 0u : bytes), nullptr, *keys, snapshotGeneration};
|
||||
if (source != nullptr) parkSnapshot(cache, recycled);
|
||||
else if (recycled.empty()) recycled = spareSnapshot(cache, bytes);
|
||||
CachedTexture entry{key, address, source != nullptr ? std::vector<std::byte>{} : (recycled.empty() ? std::vector<std::byte>(bytes) : std::move(recycled)), nullptr, *keys, snapshotGeneration};
|
||||
entry.committed = std::move(committed);
|
||||
entry.surfaceBytes = bytes;
|
||||
const auto createTexture = [&](auto&&... args) {
|
||||
@@ -950,7 +990,7 @@ std::shared_ptr<Texture> cachedTexture(const Context& context, std::span<const s
|
||||
entry.texture = createTexture(*context.detiler, resource, components, entry.bytes, depthCompare);
|
||||
entry.accounted = entry.texture->AllocationBytes();
|
||||
constexpr std::size_t SnapshotCompareBytes = 256u << 10u;
|
||||
if (entry.bytes.size() > SnapshotCompareBytes && (entry.generation != 0 || entry.untracked[0] != 0)) std::vector<std::byte>().swap(entry.bytes);
|
||||
if (entry.bytes.size() > SnapshotCompareBytes && (entry.generation != 0 || entry.untracked[0] != 0)) parkSnapshot(cache, entry.bytes);
|
||||
counters.snapshots.fetch_add(1, std::memory_order_relaxed);
|
||||
}
|
||||
const auto budget = sampledBudget(context, cache);
|
||||
@@ -1266,6 +1306,8 @@ TextureCacheUse TextureCacheUsage() {
|
||||
dropUncachedViews(sampled);
|
||||
use.sampledEntries = sampled.entries.size();
|
||||
use.sampledBytes = sampled.bytes;
|
||||
use.spareEntries = sampled.spare.size();
|
||||
use.spareBytes = sampled.spareBytes;
|
||||
}
|
||||
use.storageBytes = storageCacheBytes();
|
||||
return use;
|
||||
@@ -1311,6 +1353,8 @@ void ClearCachedTextures(VkDevice device) {
|
||||
if (it->key.device == device) eraseTexture(sampled, it++);
|
||||
else ++it;
|
||||
}
|
||||
sampled.spare.clear();
|
||||
sampled.spareBytes = 0;
|
||||
}
|
||||
auto& storage = StorageTextures();
|
||||
std::vector<std::shared_ptr<StorageTexture>> cleared;
|
||||
|
||||
@@ -223,6 +223,22 @@ void packedFloatDccClearTests() {
|
||||
Require(TextureClearKeys(resource, 1024) == DccKeys::Clear1111, "packed float texture discarded its white fast-clear keys");
|
||||
}
|
||||
|
||||
void fillContractTests() {
|
||||
std::array<std::uint32_t, 4> texels;
|
||||
texels.fill(0xdeadbeefu);
|
||||
auto span = std::as_writable_bytes(std::span(texels));
|
||||
Require(!FillDccClear(VK_FORMAT_R8G8B8A8_UNORM, DccKeys::ClearRegister, false, span), "a register clear encoded a texel");
|
||||
for (const auto texel : texels) Require(texel == 0u, "a declined clear left its span untouched");
|
||||
texels.fill(0xdeadbeefu);
|
||||
Require(!FillDccClear(VK_FORMAT_R8G8B8A8_UNORM, DccKeys::Uncompressed, false, span), "an uncompressed code encoded a texel");
|
||||
for (const auto texel : texels) Require(texel == 0u, "a declined clear left its span untouched");
|
||||
std::array<std::byte, 10> partial;
|
||||
partial.fill(std::byte{0x5a});
|
||||
Require(FillDccClear(VK_FORMAT_B10G11R11_UFLOAT_PACK32, DccKeys::Clear1111, false, partial), "packed float fast clear was not encoded");
|
||||
const std::array<std::byte, 4> white{std::byte{0xc0}, std::byte{0x03}, std::byte{0x1e}, std::byte{0x78}};
|
||||
for (std::size_t index = 0; index < partial.size(); ++index) Require(partial[index] == (index < 8u ? white[index % 4u] : std::byte{0}), "a clear fill over a partial trailing element did not zero the tail");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void RunTextureFormatTests() {
|
||||
@@ -287,5 +303,6 @@ void RunTextureFormatTests() {
|
||||
convertedDccClearTests();
|
||||
singleDccKeysTests();
|
||||
packedFloatDccClearTests();
|
||||
fillContractTests();
|
||||
gcnBufferFormatTests();
|
||||
}
|
||||
@@ -283,38 +283,38 @@ private:
|
||||
|
||||
class WatchedSurface {
|
||||
public:
|
||||
explicit WatchedSurface(const Context& context) : context(context) {
|
||||
explicit WatchedSurface(const Context& context, std::size_t bytes = SurfaceBytes) : context(context), span(bytes) {
|
||||
#ifdef _WIN32
|
||||
memory = static_cast<std::uint8_t*>(GuestArena::GuestArenaAllocate_nid_postfix(SurfaceBytes, 65536));
|
||||
if (memory != nullptr) GuestArena::GuestArenaCommit_nid_postfix(memory, SurfaceBytes, PAGE_READWRITE, SurfaceBytes);
|
||||
memory = static_cast<std::uint8_t*>(GuestArena::GuestArenaAllocate_nid_postfix(bytes, 65536));
|
||||
if (memory != nullptr) GuestArena::GuestArenaCommit_nid_postfix(memory, bytes, PAGE_READWRITE, bytes);
|
||||
#else
|
||||
constexpr std::uintptr_t alignment = 65536;
|
||||
void* mapped = mmap(nullptr, SurfaceBytes + alignment, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
|
||||
void* mapped = mmap(nullptr, bytes + alignment, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
|
||||
if (mapped != MAP_FAILED) {
|
||||
const auto begin = reinterpret_cast<std::uintptr_t>(mapped);
|
||||
const auto aligned = (begin + alignment - 1) & ~(alignment - 1);
|
||||
if (aligned != begin) munmap(mapped, aligned - begin);
|
||||
if (aligned + SurfaceBytes != begin + SurfaceBytes + alignment) munmap(reinterpret_cast<void*>(aligned + SurfaceBytes), begin + alignment - aligned);
|
||||
if (aligned + bytes != begin + bytes + alignment) munmap(reinterpret_cast<void*>(aligned + bytes), begin + alignment - aligned);
|
||||
memory = reinterpret_cast<std::uint8_t*>(aligned);
|
||||
GuestWriteWatch::GuestWriteWatchRegister_nid_postfix(memory, SurfaceBytes);
|
||||
GuestWriteWatch::GuestWriteWatchRegister_nid_postfix(memory, bytes);
|
||||
}
|
||||
#endif
|
||||
Require(memory != nullptr, "cannot allocate the watched surface");
|
||||
Require(AgcDriver::GuestMemory::Watched(Address(), SurfaceBytes), "the watched surface is not write-watched");
|
||||
std::memset(memory, 0x5a, SurfaceBytes);
|
||||
GuestAllocations::Mutation().Add(memory, SurfaceBytes, true, true, true);
|
||||
AgcDriver::GuestMemory::CollectWritesUncached(Address(), SurfaceBytes);
|
||||
Require(AgcDriver::GuestMemory::Watched(Address(), bytes), "the watched surface is not write-watched");
|
||||
std::memset(memory, 0x5a, bytes);
|
||||
GuestAllocations::Mutation().Add(memory, bytes, true, true, true);
|
||||
AgcDriver::GuestMemory::CollectWritesUncached(Address(), bytes);
|
||||
}
|
||||
|
||||
~WatchedSurface() {
|
||||
ClearCachedTextures(context.device);
|
||||
GuestAllocations::Mutation().Remove(memory);
|
||||
#ifdef _WIN32
|
||||
GuestArena::GuestArenaReset_nid_postfix(memory, SurfaceBytes);
|
||||
GuestArena::GuestArenaRelease_nid_postfix(memory, SurfaceBytes);
|
||||
GuestArena::GuestArenaReset_nid_postfix(memory, span);
|
||||
GuestArena::GuestArenaRelease_nid_postfix(memory, span);
|
||||
#else
|
||||
munmap(memory, SurfaceBytes);
|
||||
GuestWriteWatch::GuestWriteWatchUnregister_nid_postfix(memory, SurfaceBytes);
|
||||
munmap(memory, span);
|
||||
GuestWriteWatch::GuestWriteWatchUnregister_nid_postfix(memory, span);
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -325,6 +325,7 @@ public:
|
||||
|
||||
private:
|
||||
const Context& context;
|
||||
std::size_t span = 0;
|
||||
std::uint8_t* memory = nullptr;
|
||||
};
|
||||
|
||||
@@ -674,6 +675,55 @@ void snapshotTests(const Context& context) {
|
||||
Require(TextureCacheUsage().sampledBytes == tiny->AllocationBytes(), "a snapshot whose " + std::to_string(tiny->AllocationBytes()) + "-byte image is smaller than its " + std::to_string(guestBytes) + " guest bytes counted " + std::to_string(TextureCacheUsage().sampledBytes) + " bytes, not its allocation");
|
||||
}
|
||||
|
||||
void recycleTests(const Context& context) {
|
||||
Block block(context);
|
||||
ClearCachedTextures(context.device);
|
||||
const auto address = block.Surface(0);
|
||||
std::memset(reinterpret_cast<void*>(address), 0xa1, SurfaceBytes);
|
||||
const auto first = Sampled(context, address);
|
||||
Require(Sampled(context, address) == first, "an unchanged surface did not serve its snapshot");
|
||||
std::memset(reinterpret_cast<void*>(address), 0xb2, SurfaceBytes);
|
||||
const auto second = Sampled(context, address);
|
||||
Require(second != first, "a changed surface did not replace its snapshot");
|
||||
std::memset(reinterpret_cast<void*>(address), 0xa1, SurfaceBytes);
|
||||
const auto third = Sampled(context, address);
|
||||
Require(third != second, "the replaced snapshot's size-equal buffer was not reread from guest memory");
|
||||
Require(Sampled(context, address) == third, "an unchanged surface did not serve its remade snapshot");
|
||||
constexpr std::uint32_t BigSide = Side * 2;
|
||||
constexpr std::size_t BigBytes = std::size_t{BigSide} * BigSide * 4u;
|
||||
const WatchedSurface watchedFirst(context, BigBytes);
|
||||
const WatchedSurface watchedSecond(context, BigBytes);
|
||||
const auto bigFirst = Sampled(context, watchedFirst.Address(), BigSide);
|
||||
Require(TextureCacheUsage().spareEntries == 1 && TextureCacheUsage().spareBytes == BigBytes, "the first big snapshot did not park its dropped " + std::to_string(BigBytes) + "-byte buffer in the spare pool");
|
||||
const auto bigSecond = Sampled(context, watchedSecond.Address(), BigSide);
|
||||
Require(TextureCacheUsage().spareEntries == 1 && TextureCacheUsage().spareBytes == BigBytes, "the second big snapshot did not take the parked spare of its size");
|
||||
}
|
||||
|
||||
void spareAgeTests(const Context& context) {
|
||||
Block block(context);
|
||||
ClearCachedTextures(context.device);
|
||||
constexpr std::uint32_t BigSide = Side * 2;
|
||||
constexpr std::size_t BigBytes = std::size_t{BigSide} * BigSide * 4u;
|
||||
{
|
||||
const WatchedSurface watched(context, BigBytes);
|
||||
const auto first = Sampled(context, watched.Address(), BigSide);
|
||||
Require(TextureCacheUsage().spareEntries == 1 && TextureCacheUsage().spareBytes == BigBytes, "the big snapshot did not park its dropped buffer in the spare pool");
|
||||
for (std::uint64_t present = 0; present <= SpareSnapshotPresents; ++present) Recorder::CountPresent();
|
||||
const auto small = Sampled(context, block.Surface(0));
|
||||
Require(TextureCacheUsage().spareEntries == 0 && TextureCacheUsage().spareBytes == 0, "a spare idle for " + std::to_string(SpareSnapshotPresents + 1u) + " presents was not released");
|
||||
}
|
||||
ClearCachedTextures(context.device);
|
||||
{
|
||||
const WatchedSurface watchedFirst(context, BigBytes);
|
||||
const WatchedSurface watchedSecond(context, BigBytes);
|
||||
const auto second = Sampled(context, watchedFirst.Address(), BigSide);
|
||||
Require(TextureCacheUsage().spareEntries == 1 && TextureCacheUsage().spareBytes == BigBytes, "the second big snapshot did not park its dropped buffer in the spare pool");
|
||||
for (std::uint64_t present = 0; present < SpareSnapshotPresents; ++present) Recorder::CountPresent();
|
||||
const auto third = Sampled(context, watchedSecond.Address(), BigSide);
|
||||
Require(TextureCacheUsage().spareEntries == 1 && TextureCacheUsage().spareBytes == BigBytes, "a spare taken within " + std::to_string(SpareSnapshotPresents) + " presents was released instead of reused");
|
||||
}
|
||||
}
|
||||
|
||||
void heldTests(const Context& context) {
|
||||
Block block(context);
|
||||
auto held = Sampled(context, block.Surface(0));
|
||||
@@ -1204,6 +1254,7 @@ int main(int argc, char** argv) {
|
||||
std::lock_guard gpu(AgcDriver::GuestMemory::GpuMutex());
|
||||
reportedBudgetTests(context);
|
||||
snapshotTests(context);
|
||||
recycleTests(context);
|
||||
heldTests(context);
|
||||
releasedTests(context);
|
||||
rescanTests(context);
|
||||
@@ -1215,6 +1266,7 @@ int main(int argc, char** argv) {
|
||||
secondUseTests(context);
|
||||
earlyMissTests(context);
|
||||
collidingKeyTests(context);
|
||||
spareAgeTests(context);
|
||||
ClearCachedTextures(context.device);
|
||||
}
|
||||
std::puts("sampled texture cache budget tests passed");
|
||||
|
||||
Reference in new issue
Block a user