mirror of
https://github.com/boykopovar/AnyPS5.git
synced 2026-10-18 23:07:07 -05:00
Merge pull request #7071 from SleglJan/fix/storage-budget-pressure
fix(agc): let the storage image cache budget follow the heap's use like the sampled one
This commit is contained in:
6 files changed
+75
-16
No files matched your search
@@ -30,9 +30,10 @@ void FlushCachedTextures(VkDevice device);
|
||||
void ClearCachedTextures(VkDevice device);
|
||||
std::uint64_t ReleaseTextureCaches(std::uint64_t bytes);
|
||||
std::uint64_t TextureCacheBudget(const VkPhysicalDeviceMemoryProperties& memory);
|
||||
std::uint64_t StorageTextureBudget(const VkPhysicalDeviceMemoryProperties& memory, const VkPhysicalDeviceMemoryBudgetPropertiesEXT* reported);
|
||||
std::uint64_t StorageTextureBudget(const VkPhysicalDeviceMemoryProperties& memory, const VkPhysicalDeviceMemoryBudgetPropertiesEXT* reported, std::uint64_t sampledBytes, std::uint64_t storageBytes);
|
||||
std::uint64_t SampledTextureBudget(const VkPhysicalDeviceMemoryProperties& memory, const VkPhysicalDeviceMemoryBudgetPropertiesEXT* reported, std::uint64_t textureBytes);
|
||||
bool SampledBudgetReportDue(std::uint64_t reported, std::uint64_t budget, std::chrono::steady_clock::duration sinceReport);
|
||||
std::uint64_t StorageTextureCacheBudget(const Context& context);
|
||||
struct TextureCacheUse {
|
||||
std::size_t sampledEntries = 0;
|
||||
std::uint64_t sampledBytes = 0;
|
||||
|
||||
@@ -146,6 +146,7 @@ struct TextureCache {
|
||||
std::chrono::steady_clock::time_point budgetRead{};
|
||||
std::uint64_t budget = 0;
|
||||
std::uint64_t reportedBudget = 0;
|
||||
std::uint64_t reportedStorage = 0;
|
||||
std::chrono::steady_clock::time_point reportedAt{};
|
||||
std::uint64_t erasures = 0;
|
||||
};
|
||||
@@ -537,7 +538,7 @@ std::uint64_t storageBudget(const Context& context) {
|
||||
context.memoryProperties2(context.physical, &properties);
|
||||
cached.device = context.physical;
|
||||
cached.read = now;
|
||||
cached.budget = StorageTextureBudget(context.memory, &reported);
|
||||
cached.budget = StorageTextureBudget(context.memory, &reported, SampledTextureMemory(), storageCacheBytes());
|
||||
return cached.budget;
|
||||
}
|
||||
|
||||
@@ -554,11 +555,17 @@ std::uint64_t sampledBudget(const Context& context, TextureCache& cache) {
|
||||
const auto storage = storageCacheBytes();
|
||||
const auto budget = SampledTextureBudget(context.memory, &reported, sampled + storage);
|
||||
const bool fresh = cache.budgetDevice != context.physical;
|
||||
if (const auto heap = largestDeviceLocalHeap(context.memory); heap && (fresh || SampledBudgetReportDue(cache.reportedBudget, budget, now - cache.reportedAt))) {
|
||||
const auto storageLimit = storageBudget(context);
|
||||
if (const auto heap = largestDeviceLocalHeap(context.memory); heap && (fresh || SampledBudgetReportDue(cache.reportedBudget, budget, now - cache.reportedAt) || SampledBudgetReportDue(cache.reportedStorage, storageLimit, now - cache.reportedAt))) {
|
||||
char was[48] = "";
|
||||
if (!fresh) std::snprintf(was, sizeof(was), " (was %llu MiB)", static_cast<unsigned long long>(cache.reportedBudget >> 20u));
|
||||
std::fprintf(stderr, "[gpu] sampled texture cache budget %llu MiB%s: device-local heap %u has a VK_EXT_memory_budget budget of %llu MiB and uses %llu MiB, %llu MiB of it sampled textures (%llu MiB in the cache) and %llu MiB cached storage images; storage image cache budget %llu MiB\n", static_cast<unsigned long long>(budget >> 20u), was, *heap, static_cast<unsigned long long>(reported.heapBudget[*heap] >> 20u), static_cast<unsigned long long>(reported.heapUsage[*heap] >> 20u), static_cast<unsigned long long>(sampled >> 20u), static_cast<unsigned long long>(cache.bytes >> 20u), static_cast<unsigned long long>(storage >> 20u), static_cast<unsigned long long>(storageBudget(context) >> 20u));
|
||||
char wasStorage[48] = "";
|
||||
if (!fresh) {
|
||||
std::snprintf(was, sizeof(was), " (was %llu MiB)", static_cast<unsigned long long>(cache.reportedBudget >> 20u));
|
||||
std::snprintf(wasStorage, sizeof(wasStorage), " (was %llu MiB)", static_cast<unsigned long long>(cache.reportedStorage >> 20u));
|
||||
}
|
||||
std::fprintf(stderr, "[gpu] sampled texture cache budget %llu MiB%s: device-local heap %u has a VK_EXT_memory_budget budget of %llu MiB and uses %llu MiB, %llu MiB of it sampled textures (%llu MiB in the cache) and %llu MiB cached storage images; storage image cache budget %llu MiB%s\n", static_cast<unsigned long long>(budget >> 20u), was, *heap, static_cast<unsigned long long>(reported.heapBudget[*heap] >> 20u), static_cast<unsigned long long>(reported.heapUsage[*heap] >> 20u), static_cast<unsigned long long>(sampled >> 20u), static_cast<unsigned long long>(cache.bytes >> 20u), static_cast<unsigned long long>(storage >> 20u), static_cast<unsigned long long>(storageLimit >> 20u), wasStorage);
|
||||
cache.reportedBudget = budget;
|
||||
cache.reportedStorage = storageLimit;
|
||||
cache.reportedAt = now;
|
||||
}
|
||||
cache.budgetDevice = context.physical;
|
||||
@@ -1033,10 +1040,14 @@ std::uint64_t TextureCacheBudget(const VkPhysicalDeviceMemoryProperties& memory)
|
||||
return std::max<std::uint64_t>(2048ull << 20u, heap ? memory.memoryHeaps[*heap].size / 4u : 0u);
|
||||
}
|
||||
|
||||
std::uint64_t StorageTextureBudget(const VkPhysicalDeviceMemoryProperties& memory, const VkPhysicalDeviceMemoryBudgetPropertiesEXT* reported) {
|
||||
std::uint64_t StorageTextureBudget(const VkPhysicalDeviceMemoryProperties& memory, const VkPhysicalDeviceMemoryBudgetPropertiesEXT* reported, std::uint64_t sampledBytes, std::uint64_t storageBytes) {
|
||||
const auto heap = largestDeviceLocalHeap(memory);
|
||||
if (reported == nullptr || !heap || reported->heapBudget[*heap] == 0) return TextureCacheBudget(memory);
|
||||
return std::max<std::uint64_t>(512ull << 20u, std::min<std::uint64_t>(TextureCacheBudget(memory), reported->heapBudget[*heap] / 4u));
|
||||
const auto budget = reported->heapBudget[*heap];
|
||||
const auto usage = reported->heapUsage[*heap];
|
||||
const auto reserved = usage - std::min<std::uint64_t>(usage, sampledBytes + storageBytes) + std::max<std::uint64_t>(2048ull << 20u, sampledBytes) + budget / 8u;
|
||||
const auto cap = std::min<std::uint64_t>(TextureCacheBudget(memory), budget / 4u);
|
||||
return std::max<std::uint64_t>(512ull << 20u, std::min<std::uint64_t>(cap, budget > reserved ? budget - reserved : 0u));
|
||||
}
|
||||
|
||||
std::uint64_t SampledTextureBudget(const VkPhysicalDeviceMemoryProperties& memory, const VkPhysicalDeviceMemoryBudgetPropertiesEXT* reported, std::uint64_t textureBytes) {
|
||||
@@ -1053,6 +1064,10 @@ bool SampledBudgetReportDue(std::uint64_t reported, std::uint64_t budget, std::c
|
||||
return change > reported / 10u && sinceReport >= std::chrono::seconds(10);
|
||||
}
|
||||
|
||||
std::uint64_t StorageTextureCacheBudget(const Context& context) {
|
||||
return storageBudget(context);
|
||||
}
|
||||
|
||||
TextureCacheUse TextureCacheUsage() {
|
||||
TextureCacheUse use;
|
||||
{
|
||||
|
||||
@@ -5107,21 +5107,33 @@ void storageTextureBudgetTests() {
|
||||
memory.memoryHeaps[1] = {32 * GiB, 0};
|
||||
memory.memoryHeaps[2] = {16 * GiB, VK_MEMORY_HEAP_DEVICE_LOCAL_BIT};
|
||||
VkPhysicalDeviceMemoryBudgetPropertiesEXT reported{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MEMORY_BUDGET_PROPERTIES_EXT};
|
||||
Require(StorageTextureBudget(memory, nullptr) == 4 * GiB, "without VK_EXT_memory_budget the storage image cache does not keep a quarter of the 16 GiB heap");
|
||||
Require(StorageTextureBudget(memory, nullptr, 0, 0) == 4 * GiB, "without VK_EXT_memory_budget the storage image cache does not keep a quarter of the 16 GiB heap");
|
||||
reported.heapBudget[0] = 100 * GiB;
|
||||
reported.heapBudget[1] = 100 * GiB;
|
||||
Require(StorageTextureBudget(memory, &reported) == 4 * GiB, "a zero budget for the largest device-local heap did not fall back to a quarter of the heap");
|
||||
Require(StorageTextureBudget(memory, &reported, 0, 0) == 4 * GiB, "a zero budget for the largest device-local heap did not fall back to a quarter of the heap");
|
||||
reported.heapBudget[2] = 10 * GiB;
|
||||
Require(StorageTextureBudget(memory, &reported) == 2560 * MiB, "a 10 GiB budget on the 16 GiB heap does not limit the storage image cache to a quarter of the budget");
|
||||
Require(StorageTextureBudget(memory, &reported, 0, 0) == 2560 * MiB, "a 10 GiB budget on the 16 GiB heap does not limit the storage image cache to a quarter of the budget");
|
||||
reported.heapUsage[2] = 5 * GiB;
|
||||
Require(StorageTextureBudget(memory, &reported, 1 * GiB, 2 * GiB) == 2560 * MiB, "2 GiB used outside the texture caches on a 10 GiB budget lowered the storage image cache below its quarter");
|
||||
reported.heapUsage[2] = 8 * GiB;
|
||||
Require(StorageTextureBudget(memory, &reported, 1 * GiB, 2 * GiB) == 1792 * MiB, "a 10 GiB budget with 5 GiB used outside the texture caches does not leave 1792 MiB after the sampled textures' 2 GiB floor and 1280 MiB of headroom");
|
||||
reported.heapUsage[2] = 9 * GiB;
|
||||
Require(StorageTextureBudget(memory, &reported, 3 * GiB, 2 * GiB) == 1792 * MiB, "3 GiB of sampled textures were not reserved past their 2 GiB floor");
|
||||
reported.heapUsage[2] = 1 * GiB;
|
||||
Require(StorageTextureBudget(memory, &reported, 1 * GiB, 2 * GiB) == 2560 * MiB, "usage below the cached bytes is not read as no other device memory in use");
|
||||
Require(StorageTextureBudget(memory, &reported, 7 * GiB, 1 * GiB) == 1792 * MiB, "7 GiB of sampled textures with the usage below the cached bytes do not leave 1792 MiB after their reservation and 1280 MiB of headroom");
|
||||
reported.heapUsage[2] = 9 * GiB + 512 * MiB;
|
||||
Require(StorageTextureBudget(memory, &reported, 2 * GiB, 512 * MiB) == 512 * MiB, "a heap whose other users leave no room does not keep the 512 MiB floor");
|
||||
reported.heapUsage[2] = 0;
|
||||
reported.heapBudget[2] = 6 * GiB;
|
||||
Require(StorageTextureBudget(memory, &reported) == 1536 * MiB, "a 6 GiB budget on the 16 GiB heap does not limit the storage image cache to a quarter of the budget");
|
||||
Require(StorageTextureBudget(memory, &reported, 0, 0) == 1536 * MiB, "a 6 GiB budget on the 16 GiB heap does not limit the storage image cache to a quarter of the budget");
|
||||
reported.heapBudget[2] = 1 * GiB;
|
||||
Require(StorageTextureBudget(memory, &reported) == 512 * MiB, "a 1 GiB budget does not keep the 512 MiB floor");
|
||||
Require(StorageTextureBudget(memory, &reported, 0, 0) == 512 * MiB, "a 1 GiB budget does not keep the 512 MiB floor");
|
||||
reported.heapBudget[2] = 40 * GiB;
|
||||
Require(StorageTextureBudget(memory, &reported) == 4 * GiB, "a budget above the heap size raised the storage image cache past a quarter of the heap");
|
||||
Require(StorageTextureBudget(memory, &reported, 0, 0) == 4 * GiB, "a budget above the heap size raised the storage image cache past a quarter of the heap");
|
||||
memory.memoryHeaps[0].flags = 0;
|
||||
memory.memoryHeaps[2].flags = 0;
|
||||
Require(StorageTextureBudget(memory, &reported) == 2 * GiB, "a device without a device-local heap does not keep 2 GiB of storage images");
|
||||
Require(StorageTextureBudget(memory, &reported, 0, 0) == 2 * GiB, "a device without a device-local heap does not keep 2 GiB of storage images");
|
||||
}
|
||||
|
||||
void sampledBudgetReportTests() {
|
||||
|
||||
@@ -624,6 +624,21 @@ std::uint64_t ReadBudget(const Context& context) {
|
||||
return SampledTextureCacheBudget(context);
|
||||
}
|
||||
|
||||
constexpr std::uint64_t PressedUsage = ReportedBudget - 11ull * (1ull << 30u);
|
||||
|
||||
void VKAPI_PTR ReportPressure(VkPhysicalDevice, VkPhysicalDeviceMemoryProperties2* properties) {
|
||||
auto* reported = static_cast<VkPhysicalDeviceMemoryBudgetPropertiesEXT*>(properties->pNext);
|
||||
for (std::uint32_t heap = 0; heap < VK_MAX_MEMORY_HEAPS; ++heap) {
|
||||
reported->heapBudget[heap] = ReportedBudget;
|
||||
reported->heapUsage[heap] = PressedUsage;
|
||||
}
|
||||
}
|
||||
|
||||
std::uint64_t ReadStorageBudget(const Context& context) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1100));
|
||||
return StorageTextureCacheBudget(context);
|
||||
}
|
||||
|
||||
void memoryTests(Context context) {
|
||||
context.memoryProperties2 = &ReportBudget;
|
||||
const auto before = SampledTextureMemory();
|
||||
@@ -641,6 +656,20 @@ void memoryTests(Context context) {
|
||||
Require(SampledTextureMemory() == before, "a released texture still counts as sampled texture memory");
|
||||
}
|
||||
|
||||
void storageMemoryTests(Context context) {
|
||||
constexpr std::uint64_t GiB = 1ull << 30u;
|
||||
context.memoryProperties2 = &ReportBudget;
|
||||
Block block(context);
|
||||
auto held = Sampled(context, block.Surface(0));
|
||||
const auto quarter = std::min<std::uint64_t>(StorageTextureBudget(context.memory, nullptr, 0, 0), ReportedBudget / 4u);
|
||||
Require(ReadStorageBudget(context) == quarter, "52 GiB free of a 64 GiB budget does not leave the storage image cache its quarter");
|
||||
context.memoryProperties2 = &ReportPressure;
|
||||
const auto pressed = ReadStorageBudget(context);
|
||||
const auto expected = GiB + SampledTextureMemory() + TextureCacheUsage().storageBytes;
|
||||
Require(pressed == expected, "53 GiB in use of a 64 GiB budget leaves the storage image cache " + std::to_string(pressed >> 20u) + " MiB, not the " + std::to_string(expected >> 20u) + " MiB left after the use outside the texture caches, the sampled textures' 2 GiB floor and 8 GiB of headroom");
|
||||
std::printf("reported heap budget %llu MiB with %llu MiB in use: storage image cache budget %llu MiB\n", static_cast<unsigned long long>(ReportedBudget >> 20u), static_cast<unsigned long long>(PressedUsage >> 20u), static_cast<unsigned long long>(pressed >> 20u));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
@@ -673,6 +702,7 @@ int main(int argc, char** argv) {
|
||||
if (memory) {
|
||||
std::lock_guard gpu(AgcDriver::GuestMemory::GpuMutex());
|
||||
memoryTests(context);
|
||||
storageMemoryTests(context);
|
||||
ClearCachedTextures(context.device);
|
||||
std::puts("sampled texture memory budget tests passed");
|
||||
return 0;
|
||||
|
||||
@@ -45,6 +45,7 @@
|
||||
|
||||
### Functional
|
||||
|
||||
- [Texture cache budgets](../../../core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp) (libSceAgcDriver) - the storage image cache's budget is what the reported heap budget leaves after the heap's use other than the cached storage images, the unused part of the sampled textures' 2 GiB floor and an eighth of the budget, capped at a quarter of the budget and at `TextureCacheBudget` (a quarter of the heap's size, at least 2 GiB), never below 512 MiB; the sampled cache's budget reserves `TextureCacheBudget` for the storage cache. So the sampled budget falls first while the sampled textures alive stay within their floor, and both fall together above it; each cache trims only at its next insert, a storage image is allocated before its insert reads the budget, and both budgets read the heap at most once a second, so a burst between two reads can still exceed the reported budget. The 2 GiB floor and the eighth are the sampled formula's values reused and the 512 MiB floor and the caps the earlier ones; none was measured beyond one title (PPSA11386) on one 10 GB GPU, and a title whose storage working set exceeds the shrunk budget re-creates storage images at each use.
|
||||
- [Native multisampled DCC fast clears](../../../core/libs/prx/libSceAgcDriver/Graphics/src/MultisampleTarget.cpp) import uniform 0000/0001/1110/1111 keys into a single-layer, level-zero SW_64KB_R_X color image with 2, 4 or 8 fragments. Color and DCC/CMASK ranges must stay write-watched and unchanged; expanded or mixed initial DCC, register CMASK clears, compressed payloads, color transforms, lossy modes, metadata changes, guest publication and writes over a live owner throw. Driver-mediated reads of the color and metadata stay guarded. The metadata extent follows [AMD Addrlib](https://github.com/GPUOpen-Drivers/pal/blob/c5e800072a32f68b6ccc4422936d96167c6e0728/src/core/imported/addrlib/src/gfx10/gfx10addrlib.cpp#L347-L454) with the [PS5 Mesa model](https://github.com/mihawk-99/PS5_Mesa/blob/0b2d6d1a61d9bbf89cf8beb88a696144f67c61f8/src/amd/vulkan/winsys/ps5/radv_ps5_winsys.c): GFX10.1, 16 pipes, 256-byte interleave, two maximum compressed fragments. Console geometry, concurrent guest mutation and transitions outside this subset are unverified.
|
||||
|
||||
- [Initial stencil state](../../../core/libs/prx/libSceAgcDriver/Execution/include/QueueState.hpp) uses zero for `DB_STENCIL_CONTROL`, `DB_STENCILREFMASK` and `DB_STENCILREFMASK_BF` (context `0x10b`-`0x10d`), including after `CLEAR_STATE`. The public V0, V4, V5 and V7-V11 [register-default tables](../../../core/libs/prx/libSceAgc/Command/include/RegisterDefaultsData.hpp) supply these values. Linux's [GFX10 clear-state table](https://github.com/torvalds/linux/blob/master/drivers/gpu/drm/amd/amdgpu/clearstate_gfx10.h) agrees on the control register but uses `0x01000000` for the reference masks; the console reset values have not been measured. Planet Coaster (PPSA01736 v01.017) enables stencil tests without writing the control register.
|
||||
|
||||
+2
-2
@@ -199,9 +199,9 @@ Besides the recompiled shaders, the shader cache directory holds `pipelines.jour
|
||||
|
||||
### Texture cache budget
|
||||
|
||||
Sampled textures and storage images stay in device memory in two caches that evict their least recently used entries when they grow past their budgets. The storage image budget is a quarter of the `VK_EXT_memory_budget` budget of the largest device-local heap, capped at a quarter of the heap's size or 2 GiB, whichever is larger, and never below 512 MiB. The sampled texture budget is what the reported budget leaves after the heap's use other than cached textures, that cap and an eighth of the budget, and never below 2 GiB. Both are read again every second; `[gpu] sampled texture cache budget` lines report them at start-up and when they change. `APS5_TEXTURE_CACHE_MIB=<n>` fixes both budgets at `n` MiB, and the lines are then not printed.
|
||||
Sampled textures and storage images stay in device memory in two caches that evict their least recently used entries when they grow past their budgets. The storage image budget is what the `VK_EXT_memory_budget` budget of the largest device-local heap leaves after the heap's use other than the cached storage images themselves, the part of the sampled textures' 2 GiB floor not yet in use, and an eighth of the budget; it is capped at a quarter of that budget, at most a quarter of the heap's size or 2 GiB, whichever is larger, and never below 512 MiB. The sampled texture budget is what the reported budget leaves after the heap's use other than cached textures, a quarter of the heap's size (at least 2 GiB) and an eighth of the budget, and never below 2 GiB. While the sampled textures alive stay within their floor the sampled budget falls first, to that floor, then the storage budget; above it both fall together. Each cache trims at its next insert. Both budgets are read again every second; `[gpu] sampled texture cache budget` lines report them at start-up and when either moves by more than a tenth, at most every 10 s. `APS5_TEXTURE_CACHE_MIB=<n>` fixes both budgets at `n` MiB, and the lines are then not printed.
|
||||
|
||||
A fixed budget above the automatic one can exceed what the card holds next to the rest of the frame. Allocations then go to host memory (`[gpu] ... placed in host memory` lines), where the GPU reads them over PCIe. PPSA16064 on an RTX 4060 8 GB, whose automatic storage image budget is 1797 MiB, placed 868 and 878 MiB in host memory with `APS5_TEXTURE_CACHE_MIB=3072` and walked through its first level at 4.46 and 4.83 FPS; with the variable unset it placed none and ran the same walk at 6.48 and 6.07 FPS. Leave it unset unless a measurement on the card shows otherwise.
|
||||
A fixed budget above the automatic one can exceed what the card holds next to the rest of the frame. Allocations then go to host memory (`[gpu] ... placed in host memory` lines), where the GPU reads them over PCIe. PPSA16064 on an RTX 4060 8 GB, whose automatic storage image budget was 1797 MiB under the earlier rule, placed 868 and 878 MiB in host memory with `APS5_TEXTURE_CACHE_MIB=3072` and walked through its first level at 4.46 and 4.83 FPS; with the variable unset it placed none and ran the same walk at 6.48 and 6.07 FPS. Leave it unset unless a measurement on the card shows otherwise.
|
||||
|
||||
### Storing GPU results at each flip
|
||||
|
||||
|
||||
Reference in new issue
Block a user