perf(agc): size the device staging pool from the device-local heap
The buffer pool's device tier (detiler scratch buffers and the staging shadows of written guest buffers) retained at most 512 MiB by default. A title whose working set of these buffers is larger allocates and frees device memory continually: PPSA14632 (LEGO Horizon Adventures) had 70761 misses and 69828 evictions per 10 s with 485 of 512 MiB retained, each a vkAllocateMemory or vkFreeMemory. The default budget is now an eighth of the largest device-local heap, and at least the old 512 MiB, so a 16 GiB card retains up to 2 GiB and an 8 GiB card 1 GiB. APS5_STAGING_POOL_MIB still sets it. Only the constructor knows the device, so only the budget comes from the heap. Whether the tier is in use at all (APS5_STAGING_POOL_MIB unset or not 0) stays a static check, DeviceTierEnabled, which Capacity, Usage and tierFor share, so the size classes and the tier stay consistent. On PPSA14632 with 2 GiB the device tier has 1312 misses and 99 evictions per 10 s (1628 MiB retained), and presents rise from about 34 to about 39 per 10 s. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
08729e07fd
commit
3bb4494ea7
|
|
@ -43,14 +43,10 @@ struct BufferAllocation {
|
|||
// the 256-byte copied-region buffers to fit, and those then missed on every build (81869 misses
|
||||
// and 80850 evictions per run, each a Vulkan create or destroy under the pool mutex, 3.5 s per
|
||||
// run). APS5_BUFFER_POOL_SHARED=1 keeps one tier as before.
|
||||
//
|
||||
// Device-local allocations (the detiler's scratch buffers and the staging shadows of written guest
|
||||
// buffers, see GuestBufferMemory) are retained in a third tier with a budget of their own, video
|
||||
// memory instead of pinned host memory: APS5_STAGING_POOL_MIB (default 512); 0 keeps them in the
|
||||
// two host tiers as before.
|
||||
class BufferPool {
|
||||
public:
|
||||
explicit BufferPool(const Context& context);
|
||||
static VkDeviceSize DeviceBudget(const VkPhysicalDeviceMemoryProperties& memory);
|
||||
~BufferPool();
|
||||
BufferPool(const BufferPool&) = delete;
|
||||
BufferPool& operator=(const BufferPool&) = delete;
|
||||
|
|
@ -91,8 +87,7 @@ private:
|
|||
// 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);
|
||||
// The device tier's budget (APS5_STAGING_POOL_MIB), read once.
|
||||
static VkDeviceSize DeviceBudget();
|
||||
static bool DeviceTierEnabled();
|
||||
static bool DeviceTiered(VkMemoryPropertyFlags properties);
|
||||
static std::size_t NextCapacity(std::size_t capacity);
|
||||
void destroy(const BufferAllocation& allocation) noexcept;
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ constexpr VkBufferUsageFlags deviceUsage = VK_BUFFER_USAGE_STORAGE_BUFFER_BIT |
|
|||
BufferPool::BufferPool(const Context& context) : device(context.device), unmap(context.Function<PFN_vkUnmapMemory>("vkUnmapMemory")), destroyBuffer(context.Function<PFN_vkDestroyBuffer>("vkDestroyBuffer")), freeMemory(context.Function<PFN_vkFreeMemory>("vkFreeMemory")), maxSlots(std::max<std::size_t>(1, std::min<std::size_t>(MaxSlots(), context.limits.maxMemoryAllocationCount / 6u))) {
|
||||
smallTier.budget = smallBudget;
|
||||
largeTier.budget = budget;
|
||||
deviceTier.budget = DeviceBudget();
|
||||
deviceTier.budget = DeviceTierEnabled() ? DeviceBudget(context.memory) : 0;
|
||||
}
|
||||
|
||||
BufferPool::~BufferPool() {
|
||||
|
|
@ -40,12 +40,22 @@ BufferPool::~BufferPool() {
|
|||
}
|
||||
}
|
||||
|
||||
VkDeviceSize BufferPool::DeviceBudget() {
|
||||
static const VkDeviceSize deviceBudget = [] {
|
||||
bool BufferPool::DeviceTierEnabled() {
|
||||
static const bool enabled = [] {
|
||||
const char* value = std::getenv("APS5_STAGING_POOL_MIB");
|
||||
return (value != nullptr ? std::strtoull(value, nullptr, 10) : 512ull) << 20u;
|
||||
return value == nullptr || std::strtoull(value, nullptr, 10) != 0;
|
||||
}();
|
||||
return deviceBudget;
|
||||
return enabled;
|
||||
}
|
||||
|
||||
VkDeviceSize BufferPool::DeviceBudget(const VkPhysicalDeviceMemoryProperties& memory) {
|
||||
static const char* value = std::getenv("APS5_STAGING_POOL_MIB");
|
||||
if (value != nullptr) return static_cast<VkDeviceSize>(std::strtoull(value, nullptr, 10)) << 20u;
|
||||
VkDeviceSize largest = 0;
|
||||
for (std::uint32_t heap = 0; heap < memory.memoryHeapCount && heap < VK_MAX_MEMORY_HEAPS; ++heap) {
|
||||
if ((memory.memoryHeaps[heap].flags & VK_MEMORY_HEAP_DEVICE_LOCAL_BIT) != 0) largest = std::max(largest, memory.memoryHeaps[heap].size);
|
||||
}
|
||||
return std::max<VkDeviceSize>(VkDeviceSize{512} << 20u, largest / 8u);
|
||||
}
|
||||
|
||||
void BufferPool::destroy(const BufferAllocation& allocation) noexcept {
|
||||
|
|
@ -56,7 +66,7 @@ void BufferPool::destroy(const BufferAllocation& allocation) noexcept {
|
|||
}
|
||||
|
||||
bool BufferPool::DeviceTiered(VkMemoryPropertyFlags properties) {
|
||||
return (properties & VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT) != 0 && (properties & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT) == 0 && DeviceBudget() != 0;
|
||||
return (properties & VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT) != 0 && (properties & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT) == 0 && DeviceTierEnabled();
|
||||
}
|
||||
|
||||
std::size_t BufferPool::Capacity(std::size_t bytes, VkMemoryPropertyFlags properties) {
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
#include "prx/libSceAgcDriver/Graphics/include/TextureDetiler.hpp"
|
||||
#include "prx/libSceAgcDriver/Graphics/include/VertexInput.hpp"
|
||||
#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp"
|
||||
#include "prx/libSceAgcDriver/Graphics/include/BufferPool.hpp"
|
||||
#include "prx/libSceAgcDriver/Graphics/include/ShaderInputState.hpp"
|
||||
#include "prx/libSceAgcDriver/Graphics/include/DccMetadata.hpp"
|
||||
#include "prx/libSceAgcDriver/Graphics/include/DepthSurface.hpp"
|
||||
|
|
@ -3549,6 +3550,19 @@ void meshIndexBufferTests() {
|
|||
expectFailure([] { static_cast<void>(AgcDriver::Graphics::MeshIndexBufferDescriptor(AgcDriver::Pm4::DrawParameters{0, 3, 2, 1, 0, true})); }, "invalid mesh index buffer range");
|
||||
}
|
||||
|
||||
void stagingPoolBudgetTests() {
|
||||
if (std::getenv("APS5_STAGING_POOL_MIB") != nullptr) return;
|
||||
VkPhysicalDeviceMemoryProperties memory{};
|
||||
memory.memoryHeapCount = 2;
|
||||
memory.memoryHeaps[0] = {VkDeviceSize{32} << 30u, 0};
|
||||
memory.memoryHeaps[1] = {VkDeviceSize{16} << 30u, VK_MEMORY_HEAP_DEVICE_LOCAL_BIT};
|
||||
Require(AgcDriver::Graphics::BufferPool::DeviceBudget(memory) == VkDeviceSize{2} << 30u, "the staging pool budget is not an eighth of the device-local heap");
|
||||
memory.memoryHeaps[1].size = VkDeviceSize{2} << 30u;
|
||||
Require(AgcDriver::Graphics::BufferPool::DeviceBudget(memory) == VkDeviceSize{512} << 20u, "the staging pool budget fell below 512 MiB");
|
||||
memory.memoryHeapCount = 1;
|
||||
Require(AgcDriver::Graphics::BufferPool::DeviceBudget(memory) == VkDeviceSize{512} << 20u, "a host heap sized the staging pool budget");
|
||||
}
|
||||
|
||||
void meshArgumentTests() {
|
||||
using AgcDriver::Graphics::MeshArguments;
|
||||
using AgcDriver::Graphics::ResolveMeshArguments;
|
||||
|
|
@ -3747,6 +3761,7 @@ int main() {
|
|||
sampledBudgetReportTests();
|
||||
meshArgumentTests();
|
||||
meshIndexBufferTests();
|
||||
stagingPoolBudgetTests();
|
||||
validationTests();
|
||||
vertexCopyTests();
|
||||
highestDrawIndexTests();
|
||||
|
|
|
|||
Loading…
Reference in New Issue