Merge pull request #10837 from Raghav314/perf/agc-depth-plane-copies

perf(agc): keep depth plane copies resident instead of remaking them on each use
This commit is contained in:
Dean authored and GitHub committed 2026-10-27 08:18:34 +13:00
commit 263eee0a77
4 files changed
+119 -18

No files matched your search

@@ -65,7 +65,7 @@ public:
// compute pass writing it and the next pass sampling it share one image and copy nothing.
// CanCopyFrom says whether the two descriptors address the same surface compatibly.
Texture(const Context& context, const std::shared_ptr<StorageTexture>& source, const GuestTextureResource& descriptor, VkComponentMapping components);
Texture(const Context& context, VkImage depthImage, VkFormat depthFormat, VkImageAspectFlags aspect, VkComponentMapping components, VkImageViewType viewType = VK_IMAGE_VIEW_TYPE_2D, std::uint32_t baseLayer = 0, std::shared_ptr<OwnedImage> owner = {}, VkImageUsageFlags usage = 0);
Texture(const Context& context, VkImage depthImage, VkFormat depthFormat, VkImageAspectFlags aspect, VkComponentMapping components, VkImageViewType viewType = VK_IMAGE_VIEW_TYPE_2D, std::uint32_t baseLayer = 0, std::shared_ptr<OwnedImage> owner = {}, VkImageUsageFlags usage = 0, std::uint32_t layerCount = VK_REMAINING_ARRAY_LAYERS);
struct DepthCopyTag {};
Texture(const Context& context, const std::shared_ptr<StorageTexture>& source, const GuestTextureResource& descriptor, VkComponentMapping components, DepthCopyTag);
struct SrgbExpandTag {};
@@ -1190,7 +1190,6 @@ struct DepthPlaneSlice {
class DepthPlaneCopy {
public:
DepthPlaneCopy(const Context& context, VkExtent2D extent, VkFormat format, std::uint32_t layers) : context(context), extent(extent), format(format), layers(layers), guestLayers(layers), surfaceLayers(layers) {
this->context.bufferPool.reset();
VkImageCreateInfo info{VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO};
info.flags = layers % 6u == 0 && extent.width == extent.height ? VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT : 0u;
info.imageType = VK_IMAGE_TYPE_2D;
@@ -1205,15 +1204,16 @@ public:
info.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
try {
Check(context.Function<PFN_vkCreateImage>("vkCreateImage")(context.device, &info, nullptr, &image), "vkCreateImage depth plane copy");
owned = std::make_shared<OwnedImage>(context, image, VK_NULL_HANDLE);
VkMemoryRequirements requirements{};
context.Function<PFN_vkGetImageMemoryRequirements>("vkGetImageMemoryRequirements")(context.device, image, &requirements);
VkMemoryAllocateInfo allocation{VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO};
allocation.allocationSize = requirements.size;
allocation.memoryTypeIndex = context.MemoryType(requirements.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
memory = AllocateImageMemory(context, allocation, requirements.memoryTypeBits, "vkAllocateMemory depth plane copy");
owned->Allocate(context, allocation, requirements.memoryTypeBits, "vkAllocateMemory depth plane copy");
memory = owned->memory;
Check(context.Function<PFN_vkBindImageMemory>("vkBindImageMemory")(context.device, image, memory, 0), "vkBindImageMemory depth plane copy");
imageBytes = requirements.size;
owned = std::make_shared<OwnedImage>(context, image, memory);
} catch (...) {
release();
throw;
@@ -1224,19 +1224,21 @@ public:
DepthPlaneCopy& operator=(const DepthPlaneCopy&) = delete;
std::shared_ptr<Texture> Refresh(std::span<const DepthPlaneSlice> slices, const GuestTextureResource& resource, VkComponentMapping components, VkImageViewType viewType, std::uint32_t baseLayer) {
Require(slices.size() == layers, "depth plane copy slices do not match its layers");
const auto requested = static_cast<std::uint32_t>(slices.size());
Require(requested != 0 && requested <= layers, "depth plane copy slices exceed its layers");
Require(baseLayer < requested, "a depth plane copy view starts past the slices it was given");
const auto geometry = DescribeSurface(resource);
const bool guestFill = std::any_of(slices.begin(), slices.end(), [](const auto& slice) { return slice.image == VK_NULL_HANDLE; });
if (guestFill && (geometry.layers != layers || geometry.sliceLinearBytes < sliceBytes() || geometry.mips.empty())) {
if (guestFill && (geometry.layers != requested || geometry.sliceLinearBytes < sliceBytes() || geometry.mips.empty())) {
char text[320];
std::snprintf(text, sizeof(text), "AGC graphics: depth plane copy geometry does not match its layers (copy %ux%u, %u layers, %llu bytes a slice; texture %ux%u guest format %u, dimension %d, tile mode %u, %u layers, %llu bytes a slice, %zu mips)", extent.width, extent.height, layers, static_cast<unsigned long long>(sliceBytes()), resource.width, resource.height, resource.format, static_cast<int>(resource.dimension), resource.tileMode, geometry.layers, static_cast<unsigned long long>(geometry.sliceLinearBytes), geometry.mips.size());
throw std::runtime_error(text);
}
const auto stagingBytes = std::max<std::size_t>(static_cast<std::size_t>(geometry.sliceLinearBytes), static_cast<std::size_t>(sliceBytes())) * layers;
const auto stagingBytes = std::max<std::size_t>(static_cast<std::size_t>(geometry.sliceLinearBytes), static_cast<std::size_t>(sliceBytes())) * requested;
std::vector<std::shared_ptr<Buffer>> uploads;
std::vector<std::uint32_t> uploadLayers;
std::vector<std::uint32_t> copiedLayers;
for (std::uint32_t layer = 0; layer < layers; ++layer) {
for (std::uint32_t layer = 0; layer < requested; ++layer) {
auto& held = guestLayers[layer];
auto& surface = surfaceLayers[layer];
if (slices[layer].image != VK_NULL_HANDLE) {
@@ -1264,10 +1266,11 @@ public:
if (staging == nullptr || stagingBytes > staging->Size()) staging = std::make_shared<DeviceBuffer>(context, stagingBytes, VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT);
record(slices, resource, geometry, copiedLayers, uploads, uploadLayers, staging);
}
const std::array<std::uint32_t, 6> key{static_cast<std::uint32_t>(components.r), static_cast<std::uint32_t>(components.g), static_cast<std::uint32_t>(components.b), static_cast<std::uint32_t>(components.a), static_cast<std::uint32_t>(viewType), baseLayer};
const auto viewLayers = viewType == VK_IMAGE_VIEW_TYPE_2D ? 1u : requested - baseLayer;
const std::array<std::uint32_t, 7> key{static_cast<std::uint32_t>(components.r), static_cast<std::uint32_t>(components.g), static_cast<std::uint32_t>(components.b), static_cast<std::uint32_t>(components.a), static_cast<std::uint32_t>(viewType), baseLayer, viewLayers};
auto& texture = textures[key];
if (texture == nullptr) {
texture = std::make_shared<Texture>(context, image, format, aspect(), components, viewType, baseLayer, owned);
texture = std::make_shared<Texture>(context, image, format, aspect(), components, viewType, baseLayer, owned, 0, viewLayers);
texture->MarkRefreshedPerUse();
}
return texture;
@@ -1295,7 +1298,7 @@ private:
VkDeviceMemory memory = VK_NULL_HANDLE;
std::shared_ptr<OwnedImage> owned;
std::shared_ptr<DeviceBuffer> staging;
std::map<std::array<std::uint32_t, 6>, std::shared_ptr<Texture>> textures;
std::map<std::array<std::uint32_t, 7>, std::shared_ptr<Texture>> textures;
std::vector<GuestLayer> guestLayers;
std::vector<DepthPlaneSlice> surfaceLayers;
bool filled = false;
@@ -1385,8 +1388,8 @@ private:
}
};
std::map<std::tuple<VkDevice, std::uint64_t, std::uint32_t, std::uint32_t, VkFormat, std::uint32_t>, std::unique_ptr<DepthPlaneCopy>>& planeCopies() {
static auto* copies = new std::map<std::tuple<VkDevice, std::uint64_t, std::uint32_t, std::uint32_t, VkFormat, std::uint32_t>, std::unique_ptr<DepthPlaneCopy>>();
std::map<std::tuple<VkDevice, std::uint64_t, std::uint32_t, std::uint32_t, VkFormat>, std::unique_ptr<DepthPlaneCopy>>& planeCopies() {
static auto* copies = new std::map<std::tuple<VkDevice, std::uint64_t, std::uint32_t, std::uint32_t, VkFormat>, std::unique_ptr<DepthPlaneCopy>>();
return *copies;
}
@@ -1395,7 +1398,7 @@ std::uint64_t& planeCopyClock() {
return clock;
}
void evictPlaneCopies(const std::tuple<VkDevice, std::uint64_t, std::uint32_t, std::uint32_t, VkFormat, std::uint32_t>& keep) {
void evictPlaneCopies(const std::tuple<VkDevice, std::uint64_t, std::uint32_t, std::uint32_t, VkFormat>& keep) {
static const VkDeviceSize budget = [] {
const char* text = std::getenv("APS5_DEPTH_PLANE_COPY_MIB");
return static_cast<VkDeviceSize>(text != nullptr ? std::strtoull(text, nullptr, 10) : 256ull) << 20u;
@@ -2419,9 +2422,9 @@ std::shared_ptr<Texture> DepthSurfaceTexture(const Context& context, std::span<c
}
const auto imageFormat = integer ? VK_FORMAT_R16_UINT : d16 ? VK_FORMAT_D16_UNORM : VK_FORMAT_D32_SFLOAT;
auto& copies = planeCopies();
const std::tuple<VkDevice, std::uint64_t, std::uint32_t, std::uint32_t, VkFormat, std::uint32_t> copyKey{context.device, base.address, base.extent.width, base.extent.height, imageFormat, layers};
const std::tuple<VkDevice, std::uint64_t, std::uint32_t, std::uint32_t, VkFormat> copyKey{context.device, base.address, base.extent.width, base.extent.height, imageFormat};
auto& copy = copies[copyKey];
if (copy == nullptr) copy = std::make_unique<DepthPlaneCopy>(context, base.extent, imageFormat, layers);
if (copy == nullptr || copy->layers < layers) copy = std::make_unique<DepthPlaneCopy>(context, base.extent, imageFormat, layers);
copy->lastUse = ++planeCopyClock();
auto texture = copy->Refresh(slices, resource, components, cube || resource.dimension == TextureDimension::k2DArray ? VK_IMAGE_VIEW_TYPE_2D_ARRAY : VK_IMAGE_VIEW_TYPE_2D, layered && !cube ? resource.baseArray : 0u);
evictPlaneCopies(copyKey);
@@ -1087,7 +1087,7 @@ Texture::Texture(const Context& context, const std::shared_ptr<StorageTexture>&
}
}
Texture::Texture(const Context& context, VkImage depthImage, VkFormat depthFormat, VkImageAspectFlags aspect, VkComponentMapping components, VkImageViewType viewType, std::uint32_t baseLayer, std::shared_ptr<OwnedImage> owner, VkImageUsageFlags usage) : context(context), owned(std::move(owner)) {
Texture::Texture(const Context& context, VkImage depthImage, VkFormat depthFormat, VkImageAspectFlags aspect, VkComponentMapping components, VkImageViewType viewType, std::uint32_t baseLayer, std::shared_ptr<OwnedImage> owner, VkImageUsageFlags usage, std::uint32_t layerCount) : context(context), owned(std::move(owner)) {
Require(owned == nullptr || (owned->image == depthImage && owned->context.device == context.device), "a depth sampled view must retain its own device and image");
layout = VK_IMAGE_LAYOUT_GENERAL;
VkImageViewCreateInfo viewInfo{VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO};
@@ -1099,7 +1099,7 @@ Texture::Texture(const Context& context, VkImage depthImage, VkFormat depthForma
viewInfo.format = depthFormat;
viewFormat = depthFormat;
viewInfo.components = components;
viewInfo.subresourceRange = {aspect, 0, 1, baseLayer, viewType == VK_IMAGE_VIEW_TYPE_2D ? 1u : VK_REMAINING_ARRAY_LAYERS};
viewInfo.subresourceRange = {aspect, 0, 1, baseLayer, viewType == VK_IMAGE_VIEW_TYPE_2D ? 1u : layerCount};
Check(context.Function<PFN_vkCreateImageView>("vkCreateImageView")(context.device, &viewInfo, nullptr, &view), "vkCreateImageView depth plane");
viewRange = {viewInfo.viewType, viewInfo.subresourceRange.levelCount, viewInfo.subresourceRange.layerCount};
if (viewType != VK_IMAGE_VIEW_TYPE_2D_ARRAY) return;
@@ -1,5 +1,6 @@
#include "prx/libSceAgcDriver/Execution/include/BdaFeatures.hpp"
#include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp"
#include "prx/libSceAgcDriver/Graphics/include/BufferPool.hpp"
#include "prx/libSceAgcDriver/Graphics/include/DepthSurface.hpp"
#include "prx/libSceAgcDriver/Graphics/include/Recorder.hpp"
#include "prx/libSceAgcDriver/Graphics/include/Texture.hpp"
@@ -46,6 +47,18 @@ PFN_vkCmdCopyImageToBuffer realCopyImageToBuffer = nullptr;
PFN_vkCmdCopyBufferToImage realCopyBufferToImage = nullptr;
std::atomic<std::uint32_t> imageToBufferCopies{0};
std::atomic<std::uint32_t> bufferToImageCopies{0};
std::atomic<VkImage> lastFilledImage{VK_NULL_HANDLE};
PFN_vkAllocateMemory realAllocateMemory = nullptr;
std::atomic<std::uint32_t> memoryAllocations{0};
std::atomic<VkDeviceSize> lastAllocationBytes{0};
std::atomic<std::uint32_t> lastAllocationType{0};
VKAPI_ATTR VkResult VKAPI_CALL CountAllocateMemory(VkDevice device, const VkMemoryAllocateInfo* info, const VkAllocationCallbacks* allocator, VkDeviceMemory* memory) {
memoryAllocations.fetch_add(1);
lastAllocationBytes = info->allocationSize;
lastAllocationType = info->memoryTypeIndex;
return realAllocateMemory(device, info, allocator, memory);
}
VKAPI_ATTR void VKAPI_CALL CountImageToBuffer(VkCommandBuffer commands, VkImage image, VkImageLayout layout, VkBuffer buffer, std::uint32_t count, const VkBufferImageCopy* regions) {
imageToBufferCopies.fetch_add(1);
@@ -54,6 +67,7 @@ VKAPI_ATTR void VKAPI_CALL CountImageToBuffer(VkCommandBuffer commands, VkImage
VKAPI_ATTR void VKAPI_CALL CountBufferToImage(VkCommandBuffer commands, VkBuffer buffer, VkImage image, VkImageLayout layout, std::uint32_t count, const VkBufferImageCopy* regions) {
bufferToImageCopies.fetch_add(1);
lastFilledImage = image;
realCopyBufferToImage(commands, buffer, image, layout, count, regions);
}
@@ -64,6 +78,10 @@ VKAPI_ATTR PFN_vkVoidFunction VKAPI_CALL CountingDeviceProc(VkDevice device, con
realCopyImageToBuffer = reinterpret_cast<PFN_vkCmdCopyImageToBuffer>(function);
return reinterpret_cast<PFN_vkVoidFunction>(&CountImageToBuffer);
}
if (std::strcmp(name, "vkAllocateMemory") == 0) {
realAllocateMemory = reinterpret_cast<PFN_vkAllocateMemory>(function);
return reinterpret_cast<PFN_vkVoidFunction>(&CountAllocateMemory);
}
if (std::strcmp(name, "vkCmdCopyBufferToImage") == 0) {
realCopyBufferToImage = reinterpret_cast<PFN_vkCmdCopyBufferToImage>(function);
return reinterpret_cast<PFN_vkVoidFunction>(&CountBufferToImage);
@@ -434,6 +452,76 @@ void RecordedUploads(const Context& context, const WatchedBlock& block) {
recorder.Sync();
}
void PooledPlaneCopies(const Context& context, const WatchedBlock& first, const WatchedBlock& second) {
ClearDepthSurfaces(context.device);
FillLayer(first.Data() + LayerBytes, 0.25f);
FillLayer(second.Data() + LayerBytes, 0.75f);
DepthSurfaceView(context, DepthTarget{first.Address(), 0, {Side, Side}, VK_FORMAT_D32_SFLOAT, 1.0f, 0, 0});
DepthSurfaceView(context, DepthTarget{second.Address(), 0, {Side, Side}, VK_FORMAT_D32_SFLOAT, 1.0f, 0, 0});
const auto sample = [&](const WatchedBlock& block, int round) {
lastFilledImage = VK_NULL_HANDLE;
bufferToImageCopies = 0;
Require(DepthSurfaceTexture(context, words, ArrayOver(block.Address()), components) != nullptr, "the 2D array over a depth surface was not sampled through its depth planes in round " + std::to_string(round));
Require(DepthPlaneCopyCount() == 1, "the depth plane copies outgrew a budget that holds one in round " + std::to_string(round) + ", " + std::to_string(DepthPlaneCopyCount()) + " copies");
Require(lastFilledImage.load() != VK_NULL_HANDLE && bufferToImageCopies.load() == 1, "a plane copy made again over a full budget was not filled from its surface once in round " + std::to_string(round) + ", " + std::to_string(bufferToImageCopies.load()) + " fills");
};
sample(first, 0);
sample(second, 0);
const auto keepsCopies = [&] {
if (context.bufferPool == nullptr) return false;
const VkMemoryAllocateInfo probe{VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, nullptr, lastAllocationBytes.load(), lastAllocationType.load()};
const auto memory = context.bufferPool->TakeImageMemory(probe, "vkAllocateMemory pool probe");
const auto before = context.bufferPool->RetainedImages();
context.bufferPool->PutImageMemory(memory, probe.allocationSize, probe.memoryTypeIndex);
const bool kept = context.bufferPool->RetainedImages() > before;
return kept;
}();
memoryAllocations = 0;
for (int round = 1; round <= 3; ++round) {
sample(first, round);
sample(second, round);
}
if (!keepsCopies) {
std::printf("note: the device memory pool does not keep a block of a plane copy's size and memory type here (not device-tiered, or its heap over budget); the reuse of evicted copies' memory is not checked\n");
} else {
Require(memoryAllocations.load() == 0, "plane copies evicted and made again over a full budget allocated device memory " + std::to_string(memoryAllocations.load()) + " times instead of taking the evicted copies' memory back");
}
ClearDepthSurfaces(context.device);
}
void SharedLayerRanges(const Context& context, const WatchedBlock& block) {
ClearDepthSurfaces(context.device);
auto resource = ArrayOver(block.Address());
resource.depthOrLastArray = 3;
const auto geometry = DescribeSurface(resource);
for (std::uint32_t layer = 0; layer < 4; ++layer) {
DepthSurfaceView(context, DepthTarget{block.Address() + geometry.GuestLayerOffset(layer), 0, {Side, Side}, VK_FORMAT_D32_SFLOAT, 1.0f, 0, 0});
}
DepthSurfaceView(context, DepthTarget{block.Address() + 16u * LayerBytes, 0, {Side, Side}, VK_FORMAT_D32_SFLOAT, 1.0f, 0, 0});
const auto sample = [&](std::uint32_t lastArray, std::uint32_t baseArray, std::uint32_t fromSlices, std::uint32_t intoCopy) {
auto range = resource;
range.depthOrLastArray = lastArray;
range.baseArray = baseArray;
imageToBufferCopies = 0;
bufferToImageCopies = 0;
const auto texture = DepthSurfaceTexture(context, words, range, components);
const auto when = "layers " + std::to_string(baseArray) + "-" + std::to_string(lastArray);
Require(texture != nullptr, "the 2D array over depth surfaces was not sampled through its depth planes for " + when);
if (imageToBufferCopies.load() != fromSlices || bufferToImageCopies.load() != intoCopy) {
throw std::runtime_error("sampling " + when + " of the depth surfaces recorded " + std::to_string(imageToBufferCopies.load()) + " depth surface copies and " + std::to_string(bufferToImageCopies.load()) + " plane copy fills, expected " + std::to_string(fromSlices) + " and " + std::to_string(intoCopy));
}
const auto view = texture->SampledViewRange(false);
Require(view.layers == lastArray + 1u - baseArray, "the view sampling " + when + " covers " + std::to_string(view.layers) + " layers, not the texture's own");
return texture;
};
sample(3, 0, 4, 1);
sample(2, 0, 0, 0);
sample(1, 0, 0, 0);
sample(2, 1, 0, 0);
sample(3, 0, 0, 0);
ClearDepthSurfaces(context.device);
}
void RunCubeGather(const Context& base, const WatchedBlock& block) {
TextureDetiler detiler(base);
auto context = base;
@@ -508,6 +596,11 @@ void RunCubeTiledSlice(const Context& base, const WatchedBlock& block) {
int main() {
try {
#ifdef _WIN32
_putenv_s("APS5_DEPTH_PLANE_COPY_MIB", "1");
#else
setenv("APS5_DEPTH_PLANE_COPY_MIB", "1", 1);
#endif
if (!AgcDriver::GuestMemory::WriteWatched()) {
std::puts("skipped, guest memory has no write watch");
return Skipped;
@@ -528,6 +621,11 @@ int main() {
Require(AgcDriver::GuestMemory::Watched(block.Address(), BlockBytes), "depth plane guest layers: the guest block is not write-watched");
Run(context, block);
RecordedUploads(context, block);
{
WatchedBlock second;
PooledPlaneCopies(context, block, second);
}
SharedLayerRanges(context, block);
RunCubeGather(context, block);
RunCubeTiledSlice(context, block);
SparseGuestLayers(context);