Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,6 @@ struct SlabSlot {
// builds no longer sweeps the large ones out. Free slots are kept per size, usage and memory
// properties, so a lookup does not scan the other slots. Reuse is safe because a Buffer is only released once
// the GPU work using it completed (kept until the batch fence, or after a CommandBatch wait).
// APS5_NO_BUFFER_CLASSES=1 matches exact sizes only, as before; APS5_BUFFER_POOL_SLOTS=<n> sets the
// slot count (the bound applies to each tier below, and is held to a sixth of the device's
// maxMemoryAllocationCount so the three tiers keep half of it free).
//
// The size classes and the exact-size allocations are retained in two tiers with a budget each:
// the multi-MiB texture scratch buffers that come back from a batch (GPU-direct uploads and
Expand Down Expand Up @@ -123,8 +120,6 @@ class BufferPool {
std::uint64_t misses = 0;
std::uint64_t evictions = 0;
};
// The tier a buffer of `capacity` and `properties` is retained in (the large one for everything
// when shared; the device tier for device-local memory while it has a budget).
Tier& tierFor(std::size_t capacity, VkMemoryPropertyFlags properties);
static bool DeviceTierEnabled();
static VkDeviceSize deviceBudget(const Context& context);
Expand Down Expand Up @@ -157,6 +152,7 @@ class BufferPool {
Tier smallTier;
Tier largeTier;
Tier deviceTier;
Tier smallDeviceTier;
std::chrono::steady_clock::time_point heapRead{};
bool heapOver = false;
VkDeviceSize imageBudget = 0;
Expand Down
16 changes: 9 additions & 7 deletions core/libs/prx/libSceAgcDriver/Graphics/src/BufferPool.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -38,18 +38,19 @@ constexpr VkBufferUsageFlags deviceUsage = VK_BUFFER_USAGE_STORAGE_BUFFER_BIT |

}

BufferPool::BufferPool(const Context& context) : maxSlots(std::max<std::size_t>(1, std::min<std::size_t>(MaxSlots(), context.limits.maxMemoryAllocationCount / 6u))), cacheReclaim(context.cacheReclaim), device(context.device), unmap(context.Function<PFN_vkUnmapMemory>("vkUnmapMemory")), destroyBuffer(context.Function<PFN_vkDestroyBuffer>("vkDestroyBuffer")), freeMemory(context.Function<PFN_vkFreeMemory>("vkFreeMemory")), allocateMemory(context.Function<PFN_vkAllocateMemory>("vkAllocateMemory")), mapMemory(context.Function<PFN_vkMapMemory>("vkMapMemory")), physical(context.physical), memoryProperties(context.memory), memoryProperties2(context.memoryProperties2) {
BufferPool::BufferPool(const Context& context) : maxSlots(std::max<std::size_t>(1, std::min<std::size_t>(MaxSlots(), context.limits.maxMemoryAllocationCount / 8u))), cacheReclaim(context.cacheReclaim), device(context.device), unmap(context.Function<PFN_vkUnmapMemory>("vkUnmapMemory")), destroyBuffer(context.Function<PFN_vkDestroyBuffer>("vkDestroyBuffer")), freeMemory(context.Function<PFN_vkFreeMemory>("vkFreeMemory")), allocateMemory(context.Function<PFN_vkAllocateMemory>("vkAllocateMemory")), mapMemory(context.Function<PFN_vkMapMemory>("vkMapMemory")), physical(context.physical), memoryProperties(context.memory), memoryProperties2(context.memoryProperties2) {
smallTier.budget = smallBudget;
largeTier.budget = budget;
deviceTier.budget = deviceBudget(context);
smallDeviceTier.budget = std::max<VkDeviceSize>(smallBudget, deviceTier.budget / 8u);
memoryProperties = context.memory;
physical = context.physical;
memoryProperties2 = context.memoryProperties2;
imageBudget = ImageBudget(deviceTier.budget);
}

BufferPool::~BufferPool() {
for (auto* tier : {&smallTier, &largeTier, &deviceTier}) {
for (auto* tier : {&smallTier, &largeTier, &deviceTier, &smallDeviceTier}) {
for (const auto& [key, slots] : tier->free) {
for (const auto& slot : slots) destroy(slot.allocation);
}
Expand Down Expand Up @@ -384,7 +385,7 @@ VkBufferUsageFlags BufferPool::Usage(VkBufferUsageFlags usage, VkMemoryPropertyF
}

BufferPool::Tier& BufferPool::tierFor(std::size_t capacity, VkMemoryPropertyFlags properties) {
if (DeviceTiered(properties) && (sharedTiers() || capacity >= classLimit)) return deviceTier;
if (DeviceTiered(properties)) return sharedTiers() || capacity >= classLimit ? deviceTier : smallDeviceTier;
return !sharedTiers() && capacity < classLimit ? smallTier : largeTier;
}

Expand All @@ -398,11 +399,11 @@ std::optional<BufferAllocation> BufferPool::Take(std::size_t bytes, VkBufferUsag
const auto now = std::chrono::steady_clock::now();
if (now - lastReport > std::chrono::seconds(10)) {
lastReport = now;
AgcDriver::ProfilePrint_nid_no_patch("[bufferpool] small: %llu hits, %llu misses, %llu evictions, %zu retained (%.0f MiB); large: %llu hits, %llu misses, %llu evictions, %zu retained (%.0f MiB)%s; device: %llu hits, %llu misses, %llu evictions, %zu retained (%.0f MiB of %llu); images: %llu hits, %llu misses, %llu evictions, %zu retained (%.0f MiB of %llu)\n", static_cast<unsigned long long>(smallTier.hits), static_cast<unsigned long long>(smallTier.misses), static_cast<unsigned long long>(smallTier.evictions), smallTier.slots, smallTier.retainedBytes / 1048576.0, static_cast<unsigned long long>(largeTier.hits), static_cast<unsigned long long>(largeTier.misses), static_cast<unsigned long long>(largeTier.evictions), largeTier.slots, largeTier.retainedBytes / 1048576.0, sharedTiers() ? " (shared)" : "", static_cast<unsigned long long>(deviceTier.hits), static_cast<unsigned long long>(deviceTier.misses), static_cast<unsigned long long>(deviceTier.evictions), deviceTier.slots, deviceTier.retainedBytes / 1048576.0, static_cast<unsigned long long>(deviceTier.budget >> 20u), static_cast<unsigned long long>(imageHits), static_cast<unsigned long long>(imageMisses), static_cast<unsigned long long>(imageEvictions), imageSlots, imageBytes / 1048576.0, static_cast<unsigned long long>(imageBudget >> 20u));
AgcDriver::ProfilePrint_nid_no_patch("[bufferpool] small: %llu hits, %llu misses, %llu evictions, %zu retained (%.0f MiB); large: %llu hits, %llu misses, %llu evictions, %zu retained (%.0f MiB)%s; device: %llu hits, %llu misses, %llu evictions, %zu retained (%.0f MiB of %llu); small device: %llu hits, %llu misses, %llu evictions, %zu retained (%.0f MiB of %llu); images: %llu hits, %llu misses, %llu evictions, %zu retained (%.0f MiB of %llu)\n", static_cast<unsigned long long>(smallTier.hits), static_cast<unsigned long long>(smallTier.misses), static_cast<unsigned long long>(smallTier.evictions), smallTier.slots, smallTier.retainedBytes / 1048576.0, static_cast<unsigned long long>(largeTier.hits), static_cast<unsigned long long>(largeTier.misses), static_cast<unsigned long long>(largeTier.evictions), largeTier.slots, largeTier.retainedBytes / 1048576.0, sharedTiers() ? " (shared)" : "", static_cast<unsigned long long>(deviceTier.hits), static_cast<unsigned long long>(deviceTier.misses), static_cast<unsigned long long>(deviceTier.evictions), deviceTier.slots, deviceTier.retainedBytes / 1048576.0, static_cast<unsigned long long>(deviceTier.budget >> 20u), static_cast<unsigned long long>(smallDeviceTier.hits), static_cast<unsigned long long>(smallDeviceTier.misses), static_cast<unsigned long long>(smallDeviceTier.evictions), smallDeviceTier.slots, smallDeviceTier.retainedBytes / 1048576.0, static_cast<unsigned long long>(smallDeviceTier.budget >> 20u), static_cast<unsigned long long>(imageHits), static_cast<unsigned long long>(imageMisses), static_cast<unsigned long long>(imageEvictions), imageSlots, imageBytes / 1048576.0, static_cast<unsigned long long>(imageBudget >> 20u));
}
}
auto& tier = tierFor(capacity, properties);
const auto largest = &tier == &deviceTier && !exactSizes() && bytes <= std::numeric_limits<std::size_t>::max() / 2 ? std::max(capacity, bytes * 2) : capacity;
const auto largest = (&tier == &deviceTier || &tier == &smallDeviceTier) && !exactSizes() && bytes <= std::numeric_limits<std::size_t>::max() / 2 ? std::max(capacity, bytes * 2) : capacity;
auto found = tier.free.end();
for (auto size = capacity; size <= largest && found == tier.free.end(); size = NextCapacity(size)) found = tier.free.find({size, usage, properties});
if (found == tier.free.end() || found->second.empty()) {
Expand Down Expand Up @@ -469,9 +470,10 @@ VkDeviceSize BufferPool::trim() {
Tier retiredSmall;
Tier retiredLarge;
Tier retiredDevice;
Tier retiredSmallDevice;
{
std::lock_guard lock(mutex);
for (const auto& [tier, retired] : {std::pair{&smallTier, &retiredSmall}, std::pair{&largeTier, &retiredLarge}, std::pair{&deviceTier, &retiredDevice}}) {
for (const auto& [tier, retired] : {std::pair{&smallTier, &retiredSmall}, std::pair{&largeTier, &retiredLarge}, std::pair{&deviceTier, &retiredDevice}, std::pair{&smallDeviceTier, &retiredSmallDevice}}) {
tier->free.swap(retired->free);
tier->evictions += tier->slots;
tier->retainedBytes = 0;
Expand All @@ -483,7 +485,7 @@ VkDeviceSize BufferPool::trim() {
imageOrder.clear();
}
VkDeviceSize bytes = 0;
for (const auto* tier : {&retiredSmall, &retiredLarge, &retiredDevice}) {
for (const auto* tier : {&retiredSmall, &retiredLarge, &retiredDevice, &retiredSmallDevice}) {
for (const auto& [key, slots] : tier->free) {
for (const auto& slot : slots) bytes += destroy(slot.allocation);
}
Expand Down
21 changes: 21 additions & 0 deletions core/libs/prx/libSceAgcDriver/tests/BufferPool.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -324,6 +324,26 @@ void SmallDeviceRetention() {
Expect(mock.liveBytes == 0 && mock.liveBuffers.empty(), "device pool tier retention leaked Vulkan objects");
}

void SmallDeviceOwnTier() {
mock = MockDevice{};
{
auto context = mockContext();
constexpr auto usage = VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT;
VkDeviceMemory small = VK_NULL_HANDLE;
{
DeviceBuffer buffer(context, 256 * 1024, usage);
small = MemoryOf(buffer.Handle());
}
std::vector<std::unique_ptr<Buffer>> host;
for (int i = 0; i < 160; ++i) host.push_back(std::make_unique<Buffer>(context, 512 * 1024, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, HostVisible));
host.clear();
const auto made = mock.allocations;
DeviceBuffer again(context, 256 * 1024, usage);
Expect(MemoryOf(again.Handle()) == small && mock.allocations == made, "small host buffer churn evicted a retained small device buffer");
}
Expect(mock.liveBytes == 0 && mock.liveBuffers.empty(), "the small device tier leaked Vulkan objects");
}

void LargerClass() {
mock = MockDevice{};
auto context = mockContext();
Expand Down Expand Up @@ -1021,6 +1041,7 @@ int main(int argc, char** argv) {
LargerClass();
KeptUntilFence();
SmallDeviceRetention();
SmallDeviceOwnTier();
Budget();
AddressAndHostUnchanged();
SmallDeviceClasses();
Expand Down
Loading