perf(agc): batch draws and cache descriptors, samplers, and buffers

This commit is contained in:
boykopovar 2026-09-25 11:40:03 +03:00
parent 6b8e16b73e
commit 598caac0ce
16 changed files with 302 additions and 83 deletions

View File

@ -11,6 +11,8 @@
#include "prx/libSceAgcDriver/Graphics/include/TextureDetiler.hpp"
#include "prx/libSceAgcDriver/Graphics/include/GpuColorTransfer.hpp"
#include "prx/libSceAgcDriver/Graphics/include/BufferPool.hpp"
#include "prx/libSceAgcDriver/Graphics/include/DescriptorCache.hpp"
#include "prx/libSceAgcDriver/Graphics/include/Sampler.hpp"
#include "prx/libSceAgcDriver/Graphics/include/TextureCache.hpp"
#include "prx/libSceAgcDriver/Graphics/include/PipelineCache.hpp"
#include "prx/libc/include/General.hpp"
@ -84,6 +86,8 @@ struct VulkanDevice::State {
std::unique_ptr<Graphics::TextureDetiler> detiler;
std::unique_ptr<Graphics::GpuColorTransfer> colorTransfer;
std::shared_ptr<Graphics::BufferPool> bufferPool;
std::shared_ptr<Graphics::DescriptorCache> descriptorCache;
std::shared_ptr<Graphics::SamplerCache> samplerCache;
std::unique_ptr<Graphics::TextureCache> textureCache;
std::unique_ptr<Graphics::PipelineCache> pipelineCache;
std::unique_ptr<Graphics::DrawQueue> drawQueue;
@ -174,6 +178,8 @@ struct VulkanDevice::State {
rgbaScaler.reset();
pipelineCache.reset();
bufferPool.reset();
descriptorCache.reset();
samplerCache.reset();
const auto destroyFence = reinterpret_cast<PFN_vkDestroyFence>(deviceProc(device, "vkDestroyFence"));
if (acquireFence) destroyFence(device, acquireFence, nullptr);
if (renderFence) destroyFence(device, renderFence, nullptr);
@ -417,6 +423,8 @@ VulkanDevice::VulkanDevice(const PresentationWindow* window) : state(std::make_u
poolInfo.queueFamilyIndex = family;
check(state->DeviceFunction<PFN_vkCreateCommandPool>("vkCreateCommandPool")(state->device, &poolInfo, nullptr, &state->pool), "vkCreateCommandPool");
state->bufferPool = std::make_shared<Graphics::BufferPool>(graphicsContext());
state->descriptorCache = std::make_shared<Graphics::DescriptorCache>();
state->samplerCache = std::make_shared<Graphics::SamplerCache>();
state->pipelineCache = std::make_unique<Graphics::PipelineCache>(graphicsContext());
state->detiler = std::make_unique<Graphics::TextureDetiler>(graphicsContext());
state->drawQueue = std::make_unique<Graphics::DrawQueue>();
@ -731,7 +739,9 @@ Graphics::Context VulkanDevice::graphicsContext() const {
state->pipelineCache ? state->pipelineCache->Handle() : VK_NULL_HANDLE,
state->renderCache.get(),
state->drawQueue.get(),
state->graphicsPipelines.get()
state->graphicsPipelines.get(),
state->descriptorCache,
state->samplerCache
};
}

View File

@ -30,15 +30,30 @@ public:
void Put(const BufferAllocation& allocation) noexcept;
private:
static constexpr std::size_t capacity = 1024;
static constexpr std::size_t bucketCount = 256;
static constexpr std::size_t none = capacity;
struct Slot {
std::optional<BufferAllocation> allocation;
std::size_t next = none;
std::size_t older = none;
std::size_t newer = none;
};
static std::size_t bucket(std::size_t bytes, VkBufferUsageFlags usage, VkMemoryPropertyFlags properties) noexcept;
BufferAllocation remove(std::size_t index) noexcept;
void destroy(const BufferAllocation& allocation) noexcept;
VkDevice device;
PFN_vkUnmapMemory unmap;
PFN_vkDestroyBuffer destroyBuffer;
PFN_vkFreeMemory freeMemory;
std::mutex mutex;
std::array<std::optional<BufferAllocation>, 64> free;
std::array<Slot, capacity> slots;
std::array<std::size_t, bucketCount> buckets;
std::array<std::size_t, capacity> freeSlots;
std::size_t freeCount = capacity;
std::size_t oldest = none;
std::size_t newest = none;
VkDeviceSize retainedBytes = 0;
std::size_t cursor = 0;
static constexpr VkDeviceSize budget = 512ull * 1024 * 1024;
};

View File

@ -19,6 +19,8 @@ class TextureCache;
class RenderCache;
class DrawQueue;
class GraphicsPipelineCache;
class DescriptorCache;
class SamplerCache;
inline void Require(bool condition, const std::string& reason) {
if (!condition) throw std::runtime_error("AGC graphics: " + reason);
@ -60,6 +62,8 @@ struct Context {
RenderCache* renderCache = nullptr;
DrawQueue* drawQueue = nullptr;
GraphicsPipelineCache* graphicsPipelines = nullptr;
mutable std::shared_ptr<DescriptorCache> descriptorCache;
mutable std::shared_ptr<SamplerCache> samplerCache;
template<typename TFunction>
TFunction Function(const char* name) const {

View File

@ -0,0 +1,46 @@
#ifndef CORE_LIBS_PRX_LIBSCEAGCDRIVER_GRAPHICS_INCLUDE_DESCRIPTORCACHE_HPP
#define CORE_LIBS_PRX_LIBSCEAGCDRIVER_GRAPHICS_INCLUDE_DESCRIPTORCACHE_HPP
#include "prx/libSceAgcDriver/Graphics/include/Context.hpp"
#include <array>
#include <mutex>
#include <span>
#include <vector>
namespace AgcDriver::Graphics {
class DescriptorAllocation {
public:
DescriptorAllocation(const Context& context, std::span<const std::uint32_t> key, std::span<const VkDescriptorSetLayoutBinding> bindings, std::span<const VkDescriptorPoolSize> sizes);
~DescriptorAllocation();
DescriptorAllocation(const DescriptorAllocation&) = delete;
DescriptorAllocation& operator=(const DescriptorAllocation&) = delete;
VkDescriptorSetLayout Layout() const { return layout; }
VkDescriptorSet Set() const { return set; }
bool Matches(std::span<const std::uint32_t> candidate) const;
private:
void release() noexcept;
VkDevice device;
PFN_vkDestroyDescriptorPool destroyPool;
PFN_vkDestroyDescriptorSetLayout destroyLayout;
std::vector<std::uint32_t> key;
VkDescriptorSetLayout layout = VK_NULL_HANDLE;
VkDescriptorSet set = VK_NULL_HANDLE;
VkDescriptorPool pool = VK_NULL_HANDLE;
};
class DescriptorCache {
public:
std::unique_ptr<DescriptorAllocation> Take(std::span<const std::uint32_t> key);
void Put(std::unique_ptr<DescriptorAllocation> allocation);
private:
std::mutex mutex;
std::array<std::unique_ptr<DescriptorAllocation>, 256> available;
std::size_t cursor = 0;
};
}
#endif

View File

@ -8,8 +8,9 @@ namespace AgcDriver::Graphics {
class DrawQueue {
public:
~DrawQueue();
std::unique_ptr<CommandBatch> Begin(const Context& context);
void Submit(std::unique_ptr<CommandBatch> commands, std::shared_ptr<ShaderResources> resources, std::shared_ptr<void> storage);
VkCommandBuffer Begin(const Context& context);
void Enqueue(std::shared_ptr<ShaderResources> resources, std::shared_ptr<void> storage);
void Flush();
void Resolve(std::uint64_t address, std::size_t bytes);
void Wait();
@ -17,10 +18,15 @@ private:
struct Entry {
std::shared_ptr<void> storage;
std::shared_ptr<ShaderResources> resources;
};
struct Batch {
std::vector<Entry> entries;
std::unique_ptr<CommandBatch> commands;
};
std::vector<Entry> pending;
Batch recording;
std::vector<Batch> pending;
std::vector<std::unique_ptr<CommandBatch>> available;
std::size_t drawCount = 0;
};
}

View File

@ -3,6 +3,9 @@
#include "prx/libSceAgcDriver/Graphics/include/Context.hpp"
#include "prx/libSceAgcDriver/Graphics/include/GuestSamplerResource.hpp"
#include <array>
#include <map>
#include <mutex>
namespace AgcDriver::Graphics {
@ -18,10 +21,20 @@ public:
private:
void release() noexcept;
Context context;
VkDevice device;
PFN_vkDestroySampler destroySampler;
VkSampler sampler = VK_NULL_HANDLE;
};
class SamplerCache {
public:
std::shared_ptr<Sampler> Get(const Context& context, std::span<const std::uint32_t> words, const GuestSamplerResource& descriptor);
private:
std::mutex mutex;
std::map<std::array<std::uint32_t, 5>, std::shared_ptr<Sampler>> entries;
};
}
#endif

View File

@ -5,6 +5,7 @@
#include "prx/libSceAgcDriver/Graphics/include/BdaResources.hpp"
#include "prx/libSceAgcDriver/Graphics/include/Texture.hpp"
#include "prx/libSceAgcDriver/Graphics/include/Sampler.hpp"
#include "prx/libSceAgcDriver/Graphics/include/DescriptorCache.hpp"
#include "Recompiler.hpp"
#include "prx/libSceAgcDriver/Graphics/include/Shaders.hpp"
#include <array>
@ -57,10 +58,10 @@ private:
bool usesFaultBuffer = false;
VkDescriptorSetLayout _layout = VK_NULL_HANDLE;
VkDescriptorSet _set = VK_NULL_HANDLE;
VkDescriptorPool pool = VK_NULL_HANDLE;
std::unique_ptr<DescriptorAllocation> descriptors;
std::vector<Allocation> allocations;
std::vector<std::shared_ptr<Texture>> textures;
std::vector<std::unique_ptr<Sampler>> samplers;
std::vector<std::shared_ptr<Sampler>> samplers;
};
}

View File

@ -2,14 +2,40 @@
namespace AgcDriver::Graphics {
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")) {}
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")) {
buckets.fill(none);
for (std::size_t index = 0; index < capacity; ++index) freeSlots[index] = index;
}
BufferPool::~BufferPool() {
for (const auto& allocation : free) {
if (allocation) destroy(*allocation);
for (const auto& slot : slots) {
if (slot.allocation) destroy(*slot.allocation);
}
}
std::size_t BufferPool::bucket(std::size_t bytes, VkBufferUsageFlags usage, VkMemoryPropertyFlags properties) noexcept {
auto hash = bytes ^ (bytes >> 16u);
hash = hash * 16777619u ^ usage;
hash = hash * 16777619u ^ properties;
return (hash ^ (hash >> 8u) ^ (hash >> 16u)) % bucketCount;
}
BufferAllocation BufferPool::remove(std::size_t index) noexcept {
auto& slot = slots[index];
const auto allocation = *slot.allocation;
auto* link = &buckets[bucket(allocation.bytes, allocation.usage, allocation.properties)];
while (*link != index) link = &slots[*link].next;
*link = slot.next;
if (slot.older != none) slots[slot.older].newer = slot.newer;
else oldest = slot.newer;
if (slot.newer != none) slots[slot.newer].older = slot.older;
else newest = slot.older;
retainedBytes -= allocation.allocationBytes;
slot = Slot{};
freeSlots[freeCount++] = index;
return allocation;
}
void BufferPool::destroy(const BufferAllocation& allocation) noexcept {
if (allocation.mapping != nullptr) unmap(device, allocation.memory);
destroyBuffer(device, allocation.buffer, nullptr);
@ -18,12 +44,9 @@ void BufferPool::destroy(const BufferAllocation& allocation) noexcept {
std::optional<BufferAllocation> BufferPool::Take(std::size_t bytes, VkBufferUsageFlags usage, VkMemoryPropertyFlags properties) {
std::lock_guard lock(mutex);
for (auto& allocation : free) {
if (!allocation || allocation->bytes != bytes || allocation->usage != usage || allocation->properties != properties) continue;
auto result = allocation;
retainedBytes -= allocation->allocationBytes;
allocation.reset();
return result;
for (auto index = buckets[bucket(bytes, usage, properties)]; index != none; index = slots[index].next) {
const auto& allocation = *slots[index].allocation;
if (allocation.bytes == bytes && allocation.usage == usage && allocation.properties == properties) return remove(index);
}
return std::nullopt;
}
@ -34,18 +57,18 @@ void BufferPool::Put(const BufferAllocation& allocation) noexcept {
destroy(allocation);
return;
}
while (retainedBytes + allocation.allocationBytes > budget || free[cursor].has_value()) {
auto& old = free[cursor];
if (old) {
retainedBytes -= old->allocationBytes;
destroy(*old);
old.reset();
}
cursor = (cursor + 1) % free.size();
}
free[cursor] = allocation;
while (retainedBytes > budget - allocation.allocationBytes || freeCount == 0) destroy(remove(oldest));
const auto index = freeSlots[--freeCount];
auto& slot = slots[index];
auto& head = buckets[bucket(allocation.bytes, allocation.usage, allocation.properties)];
slot.allocation = allocation;
slot.next = head;
slot.older = newest;
if (newest != none) slots[newest].newer = index;
else oldest = index;
newest = index;
head = index;
retainedBytes += allocation.allocationBytes;
cursor = (cursor + 1) % free.size();
}
std::shared_ptr<BufferPool> GetBufferPool(const Context& context) {

View File

@ -0,0 +1,65 @@
#include "prx/libSceAgcDriver/Graphics/include/DescriptorCache.hpp"
#include <algorithm>
#include <utility>
namespace AgcDriver::Graphics {
DescriptorAllocation::DescriptorAllocation(const Context& context, std::span<const std::uint32_t> key, std::span<const VkDescriptorSetLayoutBinding> bindings, std::span<const VkDescriptorPoolSize> sizes) : device(context.device), destroyPool(context.Function<PFN_vkDestroyDescriptorPool>("vkDestroyDescriptorPool")), destroyLayout(context.Function<PFN_vkDestroyDescriptorSetLayout>("vkDestroyDescriptorSetLayout")), key(key.begin(), key.end()) {
try {
VkDescriptorSetLayoutCreateInfo info{VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO};
info.bindingCount = static_cast<std::uint32_t>(bindings.size());
info.pBindings = bindings.data();
Check(context.Function<PFN_vkCreateDescriptorSetLayout>("vkCreateDescriptorSetLayout")(device, &info, nullptr, &layout), "vkCreateDescriptorSetLayout");
if (bindings.empty()) return;
VkDescriptorPoolCreateInfo poolInfo{VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO};
poolInfo.maxSets = 1;
poolInfo.poolSizeCount = static_cast<std::uint32_t>(sizes.size());
poolInfo.pPoolSizes = sizes.data();
Check(context.Function<PFN_vkCreateDescriptorPool>("vkCreateDescriptorPool")(device, &poolInfo, nullptr, &pool), "vkCreateDescriptorPool");
VkDescriptorSetAllocateInfo allocation{VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO};
allocation.descriptorPool = pool;
allocation.descriptorSetCount = 1;
allocation.pSetLayouts = &layout;
Check(context.Function<PFN_vkAllocateDescriptorSets>("vkAllocateDescriptorSets")(device, &allocation, &set), "vkAllocateDescriptorSets");
} catch (...) {
release();
throw;
}
}
DescriptorAllocation::~DescriptorAllocation() {
release();
}
void DescriptorAllocation::release() noexcept {
if (pool) destroyPool(device, pool, nullptr);
if (layout) destroyLayout(device, layout, nullptr);
}
bool DescriptorAllocation::Matches(std::span<const std::uint32_t> candidate) const {
return std::equal(key.begin(), key.end(), candidate.begin(), candidate.end());
}
std::unique_ptr<DescriptorAllocation> DescriptorCache::Take(std::span<const std::uint32_t> key) {
std::lock_guard lock(mutex);
for (auto& allocation : available) {
if (allocation && allocation->Matches(key)) return std::move(allocation);
}
return nullptr;
}
void DescriptorCache::Put(std::unique_ptr<DescriptorAllocation> allocation) {
Require(allocation != nullptr, "cannot cache an empty descriptor allocation");
std::lock_guard lock(mutex);
for (std::size_t offset = 0; offset < available.size(); ++offset) {
const auto index = (cursor + offset) % available.size();
if (available[index]) continue;
available[index] = std::move(allocation);
cursor = (index + 1) % available.size();
return;
}
available[cursor] = std::move(allocation);
cursor = (cursor + 1) % available.size();
}
}

View File

@ -107,8 +107,7 @@ void Draw(const Context& context, const State& state, const Pm4::DrawParameters&
storage->pipeline = context.graphicsPipelines->Get(state, storage->color, *resources, shaders);
auto& pipeline = *storage->pipeline;
timing.Mark("pipeline_cache");
auto batch = context.drawQueue->Begin(context);
const auto commands = batch->Handle();
const auto commands = context.drawQueue->Begin(context);
VkMemoryBarrier upload{VK_STRUCTURE_TYPE_MEMORY_BARRIER};
upload.srcAccessMask = VK_ACCESS_HOST_WRITE_BIT;
upload.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_INDEX_READ_BIT | VK_ACCESS_VERTEX_ATTRIBUTE_READ_BIT | VK_ACCESS_UNIFORM_READ_BIT | VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT;
@ -134,8 +133,8 @@ void Draw(const Context& context, const State& state, const Pm4::DrawParameters&
download.dstAccessMask = VK_ACCESS_HOST_READ_BIT;
context.Function<PFN_vkCmdPipelineBarrier>("vkCmdPipelineBarrier")(commands, VK_PIPELINE_STAGE_TRANSFER_BIT | shaderStages, VK_PIPELINE_STAGE_HOST_BIT, 0, 1, &download, 0, nullptr, 0, nullptr);
timing.Mark("command_record");
context.drawQueue->Submit(std::move(batch), std::move(resources), std::move(storage));
timing.Mark("submit");
context.drawQueue->Enqueue(std::move(resources), std::move(storage));
timing.Mark("enqueue");
}
}

View File

@ -6,44 +6,60 @@
namespace AgcDriver::Graphics {
DrawQueue::~DrawQueue() {
for (auto& entry : pending) entry.commands.reset();
recording.commands.reset();
for (auto& batch : pending) batch.commands.reset();
}
std::unique_ptr<CommandBatch> DrawQueue::Begin(const Context& context) {
if (available.empty()) return std::make_unique<CommandBatch>(context);
auto commands = std::move(available.back());
available.pop_back();
commands->Reset();
return commands;
}
void DrawQueue::Submit(std::unique_ptr<CommandBatch> commands, std::shared_ptr<ShaderResources> resources, std::shared_ptr<void> storage) {
if (pending.size() >= 64) Wait();
pending.push_back({std::move(storage), std::move(resources), std::move(commands)});
try {
pending.back().commands->Submit();
} catch (...) {
pending.pop_back();
throw;
VkCommandBuffer DrawQueue::Begin(const Context& context) {
if (drawCount >= 64) Wait();
if (!recording.commands) {
if (available.empty()) recording.commands = std::make_unique<CommandBatch>(context);
else {
recording.commands = std::move(available.back());
available.pop_back();
recording.commands->Reset();
}
}
return recording.commands->Handle();
}
void DrawQueue::Enqueue(std::shared_ptr<ShaderResources> resources, std::shared_ptr<void> storage) {
Require(recording.commands != nullptr && resources != nullptr && storage != nullptr, "draw batch is incomplete");
recording.entries.push_back({std::move(storage), std::move(resources)});
++drawCount;
if (recording.entries.size() >= 8) Flush();
}
void DrawQueue::Flush() {
if (!recording.commands) return;
Require(!recording.entries.empty(), "cannot submit an incomplete draw batch");
pending.push_back(std::move(recording));
recording = Batch{};
pending.back().commands->Submit();
}
void DrawQueue::Resolve(std::uint64_t address, std::size_t bytes) {
if (std::any_of(pending.begin(), pending.end(), [&](const auto& entry) { return entry.resources->WritesOverlap(address, bytes); })) Wait();
const auto overlaps = [&](const auto& entry) { return entry.resources->WritesOverlap(address, bytes); };
if (std::any_of(recording.entries.begin(), recording.entries.end(), overlaps) || std::any_of(pending.begin(), pending.end(), [&](const auto& batch) { return std::any_of(batch.entries.begin(), batch.entries.end(), overlaps); })) Wait();
}
void DrawQueue::Wait() {
if (pending.empty()) return;
if (pending.empty() && !recording.commands) return;
PerformanceTimer timing("Graphics.DrawQueue.Wait");
const GuestMemory::MemoryAccessScope suspended(nullptr, nullptr);
for (auto& entry : pending) {
entry.commands->Wait();
Flush();
timing.Mark("submit");
for (auto& batch : pending) {
batch.commands->Wait();
timing.Mark("fence_wait");
entry.resources->WriteBack();
for (auto& entry : batch.entries) entry.resources->WriteBack();
timing.Mark("resources_writeback");
available.push_back(std::move(entry.commands));
available.push_back(std::move(batch.commands));
batch.entries.clear();
timing.Mark("resources_release");
}
pending.clear();
drawCount = 0;
}
}

View File

@ -1,5 +1,6 @@
#include "prx/libSceAgcDriver/Graphics/include/Texture.hpp"
#include "prx/libSceAgcDriver/Graphics/include/RenderCache.hpp"
#include "prx/libSceAgcDriver/Graphics/include/DrawQueue.hpp"
#include "prx/libSceAgcDriver/Graphics/include/TextureFormat.hpp"
namespace AgcDriver::Graphics {
@ -38,6 +39,7 @@ Texture::Texture(const Context& context, const std::shared_ptr<ResidentColor>& s
viewInfo.components = components;
viewInfo.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1};
Check(context.Function<PFN_vkCreateImageView>("vkCreateImageView")(context.device, &viewInfo, nullptr, &view), "vkCreateImageView resident texture");
if (context.drawQueue) context.drawQueue->Flush();
upload = std::make_unique<CommandBatch>(context);
const auto commands = upload->Handle();
source->Transition(commands, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL);

View File

@ -2,7 +2,7 @@
namespace AgcDriver::Graphics {
Sampler::Sampler(const Context& context, const GuestSamplerResource& descriptor) : context(context) {
Sampler::Sampler(const Context& context, const GuestSamplerResource& descriptor) : device(context.device), destroySampler(context.Function<PFN_vkDestroySampler>("vkDestroySampler")) {
Require(!descriptor.anisotropyEnable || context.samplerAnisotropy, "guest sampler descriptor requests anisotropic filtering which the device does not support");
Require(descriptor.maxAnisotropy <= context.limits.maxSamplerAnisotropy, "guest sampler descriptor requests an anisotropy ratio beyond the device limit");
Require(descriptor.lodBias >= -context.limits.maxSamplerLodBias && descriptor.lodBias <= context.limits.maxSamplerLodBias, "guest sampler descriptor requests a LOD bias beyond the device limit");
@ -31,11 +31,23 @@ namespace AgcDriver::Graphics {
}
void Sampler::release() noexcept {
if (sampler) context.Function<PFN_vkDestroySampler>("vkDestroySampler")(context.device, sampler, nullptr);
if (sampler) destroySampler(device, sampler, nullptr);
}
VkSampler Sampler::Handle() const {
return sampler;
}
std::shared_ptr<Sampler> SamplerCache::Get(const Context& context, std::span<const std::uint32_t> words, const GuestSamplerResource& descriptor) {
Require(words.size() == 4, "sampler cache descriptor must contain four DWORDs");
const std::array<std::uint32_t, 5> key{words[0], words[1], words[2], words[3], descriptor.compareEnable ? 1u : 0u};
std::lock_guard lock(mutex);
const auto found = entries.find(key);
if (found != entries.end()) return found->second;
auto sampler = std::make_shared<Sampler>(context, descriptor);
if (entries.size() >= 256) entries.erase(entries.begin());
entries.emplace(key, sampler);
return sampler;
}
}

View File

@ -1,6 +1,7 @@
#include "prx/libSceAgcDriver/Graphics/include/ShaderResources.hpp"
#include "prx/libSceAgcDriver/Graphics/include/TextureCache.hpp"
#include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp"
#include "prx/libSceAgcDriver/Execution/include/PerformanceTimer.hpp"
#include "prx/libc/include/General.hpp"
#include "Optimization/include/Optimization/ShaderStageInputInfo.hpp"
#include <cstring>
@ -70,6 +71,7 @@ ShaderResources::ShaderResources(const Context& context, const CompiledShader& c
}
void ShaderResources::build(std::span<const CompiledShader> shaders, const ColorTarget* target, std::uint64_t indexAddress, std::size_t indexBytes) {
PerformanceTimer timing("Graphics.ShaderResources");
try {
Require(!shaders.empty() && context.limits.maxBoundDescriptorSets >= 1, "shader descriptor set exceeds device limits");
std::vector<Binding> bindings;
@ -112,36 +114,35 @@ void ShaderResources::build(std::span<const CompiledShader> shaders, const Color
}
}
Require(storageBuffers <= context.limits.maxDescriptorSetStorageBuffers, "pipeline descriptors exceed device limits");
timing.Mark("bindings");
guestMemory.Upload(usesBda);
if (usesBda) bda = std::make_unique<BdaResources>(context, guestMemory);
else if (usesFaultBuffer) bda = std::make_unique<BdaResources>(context);
timing.Mark("memory_upload");
std::vector<VkDescriptorSetLayoutBinding> description;
for (const auto& binding : bindings) {
description.push_back(binding.layout);
layoutKey.insert(layoutKey.end(), {binding.layout.binding, static_cast<std::uint32_t>(binding.layout.descriptorType), binding.layout.descriptorCount, binding.layout.stageFlags});
}
VkDescriptorSetLayoutCreateInfo info{VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO};
info.bindingCount = static_cast<std::uint32_t>(description.size());
info.pBindings = description.data();
Check(context.Function<PFN_vkCreateDescriptorSetLayout>("vkCreateDescriptorSetLayout")(context.device, &info, nullptr, &_layout), "vkCreateDescriptorSetLayout");
if (bindings.empty()) return;
std::vector<VkDescriptorPoolSize> sizes;
if (storageBuffers != 0) sizes.push_back({VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, static_cast<std::uint32_t>(storageBuffers)});
if (!textures.empty()) sizes.push_back({VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, static_cast<std::uint32_t>(textures.size())});
if (!samplers.empty()) sizes.push_back({VK_DESCRIPTOR_TYPE_SAMPLER, static_cast<std::uint32_t>(samplers.size())});
VkDescriptorPoolCreateInfo poolInfo{VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO};
poolInfo.maxSets = 1;
poolInfo.poolSizeCount = static_cast<std::uint32_t>(sizes.size());
poolInfo.pPoolSizes = sizes.data();
Check(context.Function<PFN_vkCreateDescriptorPool>("vkCreateDescriptorPool")(context.device, &poolInfo, nullptr, &pool), "vkCreateDescriptorPool");
VkDescriptorSetAllocateInfo allocation{VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO};
allocation.descriptorPool = pool;
allocation.descriptorSetCount = 1;
allocation.pSetLayouts = &_layout;
Check(context.Function<PFN_vkAllocateDescriptorSets>("vkAllocateDescriptorSets")(context.device, &allocation, &_set), "vkAllocateDescriptorSets");
if (!context.descriptorCache) context.descriptorCache = std::make_shared<DescriptorCache>();
descriptors = context.descriptorCache->Take(layoutKey);
if (!descriptors) descriptors = std::make_unique<DescriptorAllocation>(context, layoutKey, description, sizes);
_layout = descriptors->Layout();
_set = descriptors->Set();
timing.Mark("descriptor_acquire");
std::vector<VkDescriptorBufferInfo> buffers;
std::vector<VkDescriptorImageInfo> images;
std::vector<VkWriteDescriptorSet> writes;
buffers.reserve(allocations.size());
images.reserve(textures.size() + samplers.size());
writes.reserve(bindings.size());
for (const auto& binding : bindings) {
std::vector<VkDescriptorBufferInfo> buffers;
std::vector<VkDescriptorImageInfo> images;
const auto bufferOffset = buffers.size();
const auto imageOffset = images.size();
VkWriteDescriptorSet write{VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET};
write.dstSet = _set;
write.dstBinding = binding.layout.binding;
@ -150,20 +151,22 @@ void ShaderResources::build(std::span<const CompiledShader> shaders, const Color
switch (binding.layout.descriptorType) {
case VK_DESCRIPTOR_TYPE_STORAGE_BUFFER:
for (const auto index : binding.allocations) buffers.push_back(descriptor(allocations[index]));
write.pBufferInfo = buffers.data();
write.pBufferInfo = buffers.data() + bufferOffset;
break;
case VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE:
for (const auto index : binding.imageAllocations) images.push_back({VK_NULL_HANDLE, textures[index]->View(), VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL});
write.pImageInfo = images.data();
write.pImageInfo = images.data() + imageOffset;
break;
case VK_DESCRIPTOR_TYPE_SAMPLER:
for (const auto index : binding.imageAllocations) images.push_back({samplers[index]->Handle(), VK_NULL_HANDLE, VK_IMAGE_LAYOUT_UNDEFINED});
write.pImageInfo = images.data();
write.pImageInfo = images.data() + imageOffset;
break;
default: throw std::runtime_error("AGC graphics: ShaderResources encountered an unknown descriptor type while writing the descriptor set");
}
context.Function<PFN_vkUpdateDescriptorSets>("vkUpdateDescriptorSets")(context.device, 1, &write, 0, nullptr);
writes.push_back(write);
}
if (!writes.empty()) context.Function<PFN_vkUpdateDescriptorSets>("vkUpdateDescriptorSets")(context.device, static_cast<std::uint32_t>(writes.size()), writes.data(), 0, nullptr);
timing.Mark("descriptor_update");
} catch (...) {
release();
throw;
@ -233,7 +236,8 @@ void ShaderResources::addImageBinding(const ShaderRecompiler::DescriptorBinding&
const auto words = std::span<const std::uint32_t>(binding.guestDescriptor).subspan(static_cast<std::size_t>(element) * elementWords, elementWords);
auto resource = DecodeSamplerResource(words);
resource.compareEnable = binding.samplerDepthCompare.at(element);
samplers.push_back(std::make_unique<Sampler>(context, resource));
if (!context.samplerCache) context.samplerCache = std::make_shared<SamplerCache>();
samplers.push_back(context.samplerCache->Get(context, words, resource));
item.imageAllocations.push_back(samplers.size() - 1);
}
Require(samplers.size() <= context.limits.maxDescriptorSetSamplers, "pipeline sampler descriptors exceed device limits");
@ -247,8 +251,9 @@ ShaderResources::~ShaderResources() {
}
void ShaderResources::release() noexcept {
if (pool) context.Function<PFN_vkDestroyDescriptorPool>("vkDestroyDescriptorPool")(context.device, pool, nullptr);
if (_layout) context.Function<PFN_vkDestroyDescriptorSetLayout>("vkDestroyDescriptorSetLayout")(context.device, _layout, nullptr);
if (descriptors) context.descriptorCache->Put(std::move(descriptors));
_layout = VK_NULL_HANDLE;
_set = VK_NULL_HANDLE;
}
VkDescriptorSetLayout ShaderResources::Layout() const {

View File

@ -1,5 +1,6 @@
#include "prx/libSceAgcDriver/Graphics/include/Texture.hpp"
#include "prx/libSceAgcDriver/Graphics/include/Resources.hpp"
#include "prx/libSceAgcDriver/Graphics/include/DrawQueue.hpp"
#include "prx/libSceAgcDriver/Graphics/include/TextureFormat.hpp"
#include "prx/libSceAgcDriver/Graphics/include/TextureTiling.hpp"
#include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp"
@ -94,6 +95,7 @@ Texture::Texture(const Context& context, TextureDetiler& detiler, const GuestTex
Buffer linear(context, static_cast<std::size_t>(linearBytes), VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT);
detiler.BeginBatch();
if (context.drawQueue) context.drawQueue->Flush();
CommandBatch batch(context);
const auto commands = batch.Handle();

View File

@ -37,7 +37,7 @@ endif()
foreach(agcTarget IN ITEMS libSceAgcDriver agc_driver_visual_test agc_driver_graphics_tests)
if(TARGET ${agcTarget})
target_sources(${agcTarget} PRIVATE Graphics/src/RenderCache.cpp Graphics/src/DrawQueue.cpp Graphics/src/RenderTexture.cpp)
target_sources(${agcTarget} PRIVATE Graphics/src/RenderCache.cpp Graphics/src/DrawQueue.cpp Graphics/src/RenderTexture.cpp Graphics/src/DescriptorCache.cpp)
endif()
endforeach()