Merge pull request #6566 from TheAggie/perf/agc-draw-fewer-allocations

perf(agc): fewer heap allocations per recorded draw
This commit is contained in:
Adrià authored and GitHub committed 2026-10-19 08:53:07 +02:00
commit c1ca83479f
5 files changed
+67 -31

No files matched your search

@@ -323,12 +323,14 @@ public:
// the GPU copies out of an import (gpuCopy) notes its read itself when the copy is recorded.
// For the recorder's read tracking (ShaderResources::MarkGpuWrites); nothing once committed.
std::vector<std::pair<std::uint64_t, std::uint64_t>> InPlaceReads() const;
void InPlaceReads(std::vector<std::pair<std::uint64_t, std::uint64_t>>& out) const;
struct SplitReads {
std::shared_ptr<const std::vector<std::pair<std::uint64_t, std::uint64_t>>> space;
std::vector<std::pair<std::uint64_t, std::uint64_t>> own;
};
std::optional<SplitReads> SplitInPlaceReads() const;
std::vector<std::pair<std::uint64_t, std::uint64_t>> DeviceReads() const;
void DeviceReads(std::vector<std::pair<std::uint64_t, std::uint64_t>>& out) const;
private:
struct Region {
@@ -234,8 +234,10 @@ public:
// ranges, the regions read in place, and the storage images with whether each is written.
const std::vector<std::pair<std::uint64_t, std::uint64_t>>& GpuWrites() const { return guestMemory.Writes(); }
std::vector<std::pair<std::uint64_t, std::uint64_t>> InPlaceReads() const { return guestMemory.InPlaceReads(); }
void InPlaceReads(std::vector<std::pair<std::uint64_t, std::uint64_t>>& out) const { guestMemory.InPlaceReads(out); }
std::vector<std::pair<VkImage, bool>> StorageImages() const;
std::vector<std::pair<std::uint64_t, std::uint64_t>> DeviceReads() const { return guestMemory.DeviceReads(); }
void DeviceReads(std::vector<std::pair<std::uint64_t, std::uint64_t>>& out) const { guestMemory.DeviceReads(out); }
bool ReadsImage(const StorageTexture* image) const;
bool HasCachedStorageImages() const;
bool HoldsReleasedTexture() const;
@@ -257,6 +259,7 @@ public:
// (their count and size remain): a compute template then serves dispatches whose constants
// differ, and the hit refreshes its data buffers with the dispatch's words (RefreshData).
static std::vector<std::uint32_t> ContentKey(const CompiledShader& shader, bool dataWords = true, bool movableBuffers = false);
static void AppendContentKey(const CompiledShader& shader, bool dataWords, bool movableBuffers, std::vector<std::uint32_t>& key);
static bool DiffContentKeys(std::span<const std::uint32_t> previous, std::span<const std::uint32_t> key, bool draw, ContentKeyDiff& diff);
// Records the shader's ShaderData and FlattenedSrt words into this object's data buffers
// (vkCmdUpdateBuffer, a transfer write the caller's pre-dispatch barrier makes visible; a
@@ -18,6 +18,7 @@
#include "prx/libc/include/General.hpp"
#include "prx/libc/include/GuestAllocations.hpp"
#include "Optimization/include/Optimization/ShaderStageInputInfo.hpp"
#include "ThreadOwned.hpp"
#include <algorithm>
#include <array>
#include <atomic>
@@ -764,7 +765,9 @@ bool MovableBuffers() {
}
ResourceCache::Key DrawResourceKey(const Context& context, std::span<const CompiledShader> shaders, const ColorTarget& target, std::uint32_t colorAttachments, std::uint64_t indexAddress, std::uint64_t indexBytes, bool ranges) {
ResourceCache::Key key{0xffffffffu};
ResourceCache::Key key;
key.reserve(256);
key.push_back(0xffffffffu);
const auto append64 = [&](std::uint64_t value) {
key.push_back(static_cast<std::uint32_t>(value));
key.push_back(static_cast<std::uint32_t>(value >> 32u));
@@ -772,9 +775,10 @@ ResourceCache::Key DrawResourceKey(const Context& context, std::span<const Compi
append64(reinterpret_cast<std::uint64_t>(context.device));
key.push_back(static_cast<std::uint32_t>(shaders.size()));
for (const auto& shader : shaders) {
const auto part = ShaderResources::ContentKey(shader, true, MovableBuffers());
key.push_back(static_cast<std::uint32_t>(part.size()));
key.insert(key.end(), part.begin(), part.end());
const auto at = key.size();
key.push_back(0);
ShaderResources::AppendContentKey(shader, true, MovableBuffers(), key);
key[at] = static_cast<std::uint32_t>(key.size() - at - 1);
}
key.push_back(colorAttachments);
if (ranges) {
@@ -1911,7 +1915,10 @@ void recordDraw(const Context& context, const State& state, const Pm4::DrawParam
const bool capture = CaptureInputsEnabled();
const bool meshIndirect = state.stages.mesh && args != nullptr;
const bool addressBased = resources.HoldsLease();
auto passReads = addressBased ? std::vector<std::pair<std::uint64_t, std::uint64_t>>{} : resources.DeviceReads();
thread_local std::vector<std::pair<std::uint64_t, std::uint64_t>>* passReadsSlot = nullptr;
auto& passReads = ShaderRecompiler::ThreadOwned(passReadsSlot);
passReads.clear();
if (!addressBased) resources.DeviceReads(passReads);
if (gpuIndirect) {
passReads.emplace_back(args->arguments, args->arguments + args->RangeBytes());
if (args->countIndirect) passReads.emplace_back(args->countAddress, args->countAddress + 4);
@@ -4965,9 +4965,9 @@ std::uint64_t GuestBufferMemory::inPlaceEnd(const Region& region) {
return region.direct != nullptr && region.runCut ? std::min(region.end, region.direct->base + region.direct->bytes) : region.end;
}
std::vector<std::pair<std::uint64_t, std::uint64_t>> GuestBufferMemory::InPlaceReads() const {
std::vector<std::pair<std::uint64_t, std::uint64_t>> result;
if (!uploaded || committed) return result;
void GuestBufferMemory::InPlaceReads(std::vector<std::pair<std::uint64_t, std::uint64_t>>& result) const {
result.clear();
if (!uploaded || committed) return;
if (space != nullptr) {
for (const auto& region : space->base) {
if (region.direct != nullptr) result.emplace_back(region.begin, region.end);
@@ -4976,6 +4976,11 @@ std::vector<std::pair<std::uint64_t, std::uint64_t>> GuestBufferMemory::InPlaceR
for (const auto& region : regions) {
if (region.direct != nullptr) result.emplace_back(region.begin, inPlaceEnd(region));
}
}
std::vector<std::pair<std::uint64_t, std::uint64_t>> GuestBufferMemory::InPlaceReads() const {
std::vector<std::pair<std::uint64_t, std::uint64_t>> result;
InPlaceReads(result);
return result;
}
@@ -4988,9 +4993,9 @@ std::optional<GuestBufferMemory::SplitReads> GuestBufferMemory::SplitInPlaceRead
return split;
}
std::vector<std::pair<std::uint64_t, std::uint64_t>> GuestBufferMemory::DeviceReads() const {
std::vector<std::pair<std::uint64_t, std::uint64_t>> result;
if (!uploaded || committed) return result;
void GuestBufferMemory::DeviceReads(std::vector<std::pair<std::uint64_t, std::uint64_t>>& result) const {
result.clear();
if (!uploaded || committed) return;
if (space != nullptr) {
for (const auto& region : space->base) {
if (region.direct != nullptr || region.mirror != nullptr) result.emplace_back(region.begin, region.end);
@@ -4999,6 +5004,11 @@ std::vector<std::pair<std::uint64_t, std::uint64_t>> GuestBufferMemory::DeviceRe
for (const auto& region : regions) {
if (region.direct != nullptr || region.mirror != nullptr) result.emplace_back(region.begin, region.end);
}
}
std::vector<std::pair<std::uint64_t, std::uint64_t>> GuestBufferMemory::DeviceReads() const {
std::vector<std::pair<std::uint64_t, std::uint64_t>> result;
DeviceReads(result);
return result;
}
@@ -2468,9 +2468,14 @@ void VisitContentKey(const CompiledShader& shader, bool dataWords, bool movableB
}
std::vector<std::uint32_t> ShaderResources::ContentKey(const CompiledShader& shader, bool dataWords, bool movableBuffers) {
Require(shader.program != nullptr, "missing compiled shader");
std::vector<std::uint32_t> key;
std::size_t words = 5;
AppendContentKey(shader, dataWords, movableBuffers, key);
return key;
}
void ShaderResources::AppendContentKey(const CompiledShader& shader, bool dataWords, bool movableBuffers, std::vector<std::uint32_t>& key) {
Require(shader.program != nullptr, "missing compiled shader");
std::size_t words = key.size() + 5;
const auto addWords = [&](std::size_t count) {
if (count > key.max_size() - words) throw std::length_error("resource content key exceeds vector limits");
words += count;
@@ -2484,7 +2489,6 @@ std::vector<std::uint32_t> ShaderResources::ContentKey(const CompiledShader& sha
}
key.reserve(words);
VisitContentKey(shader, dataWords, movableBuffers, [&](std::uint32_t word) { key.push_back(word); });
return key;
}
namespace {
@@ -5144,7 +5148,9 @@ std::size_t ShaderResources::DrawUploadLimit() {
std::shared_ptr<ShaderResources::DrawBindings> ShaderResources::PrepareDrawBindings(Recorder& recorder, std::span<const MovedBuffer> moved, bool movedOnly) const {
if ((_set == VK_NULL_HANDLE && fragment.set == VK_NULL_HANDLE) || usesBda) return {};
const auto reads = guestMemory.InPlaceReads();
thread_local std::vector<std::pair<std::uint64_t, std::uint64_t>>* readsSlot = nullptr;
auto& reads = ShaderRecompiler::ThreadOwned(readsSlot);
guestMemory.InPlaceReads(reads);
const auto maxSnapshotBytes = maxDrawSnapshotBytes;
const auto uploadLimit = DrawUploadLimit();
std::size_t totalSnapshotBytes = 0;
@@ -5236,35 +5242,43 @@ std::shared_ptr<ShaderResources::DrawBindings> ShaderResources::PrepareDrawBindi
}
if (selected.empty()) return {};
Require(context.descriptorCache != nullptr, "draw snapshots require a descriptor cache");
std::array<std::vector<VkDescriptorSetLayoutBinding>, 2> descriptions;
std::array<std::map<VkDescriptorType, std::uint32_t>, 2> counts;
std::array<bool, 2> described{};
thread_local std::array<std::vector<VkDescriptorPoolSize>, 2>* countsSlot = nullptr;
auto& counts = ShaderRecompiler::ThreadOwned(countsSlot);
for (auto& sizes : counts) sizes.clear();
for (const auto& binding : bindings) {
const auto set = setOf(binding);
descriptions[set].push_back(binding.layout);
counts[set][binding.layout.descriptorType] += (binding.flags & VK_DESCRIPTOR_BINDING_VARIABLE_DESCRIPTOR_COUNT_BIT) != 0 ? std::max(1u, static_cast<std::uint32_t>(binding.imageAllocations.size())) : binding.layout.descriptorCount;
described[set] = true;
const auto count = (binding.flags & VK_DESCRIPTOR_BINDING_VARIABLE_DESCRIPTOR_COUNT_BIT) != 0 ? std::max(1u, static_cast<std::uint32_t>(binding.imageAllocations.size())) : binding.layout.descriptorCount;
if (count == 0) continue;
auto& sizes = counts[set];
const auto held = std::find_if(sizes.begin(), sizes.end(), [&](const VkDescriptorPoolSize& entry) { return entry.type == binding.layout.descriptorType; });
if (held != sizes.end()) held->descriptorCount += count;
else sizes.push_back({binding.layout.descriptorType, count});
}
const auto sizesOf = [&](std::size_t set) {
std::vector<VkDescriptorPoolSize> sizes;
for (const auto& [type, count] : counts[set]) {
if (count != 0) sizes.push_back({type, count});
}
return sizes;
};
for (auto& sizes : counts) std::sort(sizes.begin(), sizes.end(), [](const VkDescriptorPoolSize& a, const VkDescriptorPoolSize& b) { return a.type < b.type; });
const auto sizesOf = [&](std::size_t set) { return std::span<const VkDescriptorPoolSize>(counts[set]); };
const auto variableOf = [&](std::size_t set) { return tableBindings[set] != std::numeric_limits<std::size_t>::max() ? std::optional<std::uint32_t>(std::max(1u, static_cast<std::uint32_t>(bindings[tableBindings[set]].imageAllocations.size()))) : std::nullopt; };
result->cache = context.descriptorCache;
if (!descriptions[0].empty() || splitSets) {
if (described[0] || splitSets) {
result->allocation = result->cache->AllocateRewritten(_layout, sizesOf(0), updateAfterBind, variableOf(0));
result->layout = _layout;
Require(result->allocation.set != VK_NULL_HANDLE, "draw snapshot descriptor allocation failed");
}
if (!descriptions[1].empty() || splitSets) {
if (described[1] || splitSets) {
result->fragmentAllocation = result->cache->AllocateRewritten(fragment.layout, sizesOf(1), updateAfterBind, variableOf(1));
result->fragmentLayout = fragment.layout;
Require(result->fragmentAllocation.set != VK_NULL_HANDLE, "draw snapshot descriptor allocation failed");
}
auto buffers = descriptorBuffers;
auto writes = descriptorWrites;
std::vector<std::size_t> snapshotIndices(allocations.size(), selected.size());
thread_local std::vector<VkDescriptorBufferInfo>* buffersSlot = nullptr;
auto& buffers = ShaderRecompiler::ThreadOwned(buffersSlot);
buffers = descriptorBuffers;
thread_local std::vector<VkWriteDescriptorSet>* writesSlot = nullptr;
auto& writes = ShaderRecompiler::ThreadOwned(writesSlot);
writes = descriptorWrites;
thread_local std::vector<std::size_t>* indicesSlot = nullptr;
auto& snapshotIndices = ShaderRecompiler::ThreadOwned(indicesSlot);
snapshotIndices.assign(allocations.size(), selected.size());
for (std::size_t index = 0; index < selected.size(); ++index) snapshotIndices[selected[index]] = index;
auto nextWrite = writes.begin();
for (const auto& binding : bindings) {