diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp index 0895917a6..398a8d8ec 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp @@ -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 detiler; std::unique_ptr colorTransfer; std::shared_ptr bufferPool; + std::shared_ptr descriptorCache; + std::shared_ptr samplerCache; std::unique_ptr textureCache; std::unique_ptr pipelineCache; std::unique_ptr drawQueue; @@ -174,6 +178,8 @@ struct VulkanDevice::State { rgbaScaler.reset(); pipelineCache.reset(); bufferPool.reset(); + descriptorCache.reset(); + samplerCache.reset(); const auto destroyFence = reinterpret_cast(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("vkCreateCommandPool")(state->device, &poolInfo, nullptr, &state->pool), "vkCreateCommandPool"); state->bufferPool = std::make_shared(graphicsContext()); + state->descriptorCache = std::make_shared(); + state->samplerCache = std::make_shared(); state->pipelineCache = std::make_unique(graphicsContext()); state->detiler = std::make_unique(graphicsContext()); state->drawQueue = std::make_unique(); @@ -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 }; } diff --git a/core/libs/prx/libSceAgcDriver/Graphics/include/BufferPool.hpp b/core/libs/prx/libSceAgcDriver/Graphics/include/BufferPool.hpp index f1918f8ca..5e78a981c 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/include/BufferPool.hpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/include/BufferPool.hpp @@ -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 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, 64> free; + std::array slots; + std::array buckets; + std::array 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; }; diff --git a/core/libs/prx/libSceAgcDriver/Graphics/include/Context.hpp b/core/libs/prx/libSceAgcDriver/Graphics/include/Context.hpp index 3007049fa..d5c2ec543 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/include/Context.hpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/include/Context.hpp @@ -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; + mutable std::shared_ptr samplerCache; template TFunction Function(const char* name) const { diff --git a/core/libs/prx/libSceAgcDriver/Graphics/include/DescriptorCache.hpp b/core/libs/prx/libSceAgcDriver/Graphics/include/DescriptorCache.hpp new file mode 100644 index 000000000..56f0c8307 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/Graphics/include/DescriptorCache.hpp @@ -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 +#include +#include +#include + +namespace AgcDriver::Graphics { + +class DescriptorAllocation { +public: + DescriptorAllocation(const Context& context, std::span key, std::span bindings, std::span 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 candidate) const; + +private: + void release() noexcept; + VkDevice device; + PFN_vkDestroyDescriptorPool destroyPool; + PFN_vkDestroyDescriptorSetLayout destroyLayout; + std::vector key; + VkDescriptorSetLayout layout = VK_NULL_HANDLE; + VkDescriptorSet set = VK_NULL_HANDLE; + VkDescriptorPool pool = VK_NULL_HANDLE; +}; + +class DescriptorCache { +public: + std::unique_ptr Take(std::span key); + void Put(std::unique_ptr allocation); + +private: + std::mutex mutex; + std::array, 256> available; + std::size_t cursor = 0; +}; + +} + +#endif diff --git a/core/libs/prx/libSceAgcDriver/Graphics/include/DrawQueue.hpp b/core/libs/prx/libSceAgcDriver/Graphics/include/DrawQueue.hpp index 6195af0d8..3f7081e9d 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/include/DrawQueue.hpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/include/DrawQueue.hpp @@ -8,8 +8,9 @@ namespace AgcDriver::Graphics { class DrawQueue { public: ~DrawQueue(); - std::unique_ptr Begin(const Context& context); - void Submit(std::unique_ptr commands, std::shared_ptr resources, std::shared_ptr storage); + VkCommandBuffer Begin(const Context& context); + void Enqueue(std::shared_ptr resources, std::shared_ptr 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 storage; std::shared_ptr resources; + }; + struct Batch { + std::vector entries; std::unique_ptr commands; }; - std::vector pending; + Batch recording; + std::vector pending; std::vector> available; + std::size_t drawCount = 0; }; } diff --git a/core/libs/prx/libSceAgcDriver/Graphics/include/Sampler.hpp b/core/libs/prx/libSceAgcDriver/Graphics/include/Sampler.hpp index 6cd875af4..2e190d67c 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/include/Sampler.hpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/include/Sampler.hpp @@ -3,6 +3,9 @@ #include "prx/libSceAgcDriver/Graphics/include/Context.hpp" #include "prx/libSceAgcDriver/Graphics/include/GuestSamplerResource.hpp" +#include +#include +#include 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 Get(const Context& context, std::span words, const GuestSamplerResource& descriptor); + +private: + std::mutex mutex; + std::map, std::shared_ptr> entries; +}; + } #endif diff --git a/core/libs/prx/libSceAgcDriver/Graphics/include/ShaderResources.hpp b/core/libs/prx/libSceAgcDriver/Graphics/include/ShaderResources.hpp index 1133c6461..773633095 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/include/ShaderResources.hpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/include/ShaderResources.hpp @@ -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 @@ -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 descriptors; std::vector allocations; std::vector> textures; - std::vector> samplers; + std::vector> samplers; }; } diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/BufferPool.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/BufferPool.cpp index 147caafbb..6c4477baf 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/BufferPool.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/BufferPool.cpp @@ -2,14 +2,40 @@ namespace AgcDriver::Graphics { -BufferPool::BufferPool(const Context& context) : device(context.device), unmap(context.Function("vkUnmapMemory")), destroyBuffer(context.Function("vkDestroyBuffer")), freeMemory(context.Function("vkFreeMemory")) {} +BufferPool::BufferPool(const Context& context) : device(context.device), unmap(context.Function("vkUnmapMemory")), destroyBuffer(context.Function("vkDestroyBuffer")), freeMemory(context.Function("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 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 GetBufferPool(const Context& context) { diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/DescriptorCache.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/DescriptorCache.cpp new file mode 100644 index 000000000..2f40339b7 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/DescriptorCache.cpp @@ -0,0 +1,65 @@ +#include "prx/libSceAgcDriver/Graphics/include/DescriptorCache.hpp" +#include +#include + +namespace AgcDriver::Graphics { + +DescriptorAllocation::DescriptorAllocation(const Context& context, std::span key, std::span bindings, std::span sizes) : device(context.device), destroyPool(context.Function("vkDestroyDescriptorPool")), destroyLayout(context.Function("vkDestroyDescriptorSetLayout")), key(key.begin(), key.end()) { + try { + VkDescriptorSetLayoutCreateInfo info{VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO}; + info.bindingCount = static_cast(bindings.size()); + info.pBindings = bindings.data(); + Check(context.Function("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(sizes.size()); + poolInfo.pPoolSizes = sizes.data(); + Check(context.Function("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("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 candidate) const { + return std::equal(key.begin(), key.end(), candidate.begin(), candidate.end()); +} + +std::unique_ptr DescriptorCache::Take(std::span 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 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(); +} + +} diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp index 07d912b68..b0a7ba2d6 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp @@ -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("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"); } } diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/DrawQueue.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/DrawQueue.cpp index 5fc498e55..67f39029f 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/DrawQueue.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/DrawQueue.cpp @@ -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 DrawQueue::Begin(const Context& context) { - if (available.empty()) return std::make_unique(context); - auto commands = std::move(available.back()); - available.pop_back(); - commands->Reset(); - return commands; -} - -void DrawQueue::Submit(std::unique_ptr commands, std::shared_ptr resources, std::shared_ptr 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(context); + else { + recording.commands = std::move(available.back()); + available.pop_back(); + recording.commands->Reset(); + } } + return recording.commands->Handle(); +} + +void DrawQueue::Enqueue(std::shared_ptr resources, std::shared_ptr 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; } } diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/RenderTexture.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/RenderTexture.cpp index 6df77b1f1..a8bdbac8f 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/RenderTexture.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/RenderTexture.cpp @@ -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& s viewInfo.components = components; viewInfo.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1}; Check(context.Function("vkCreateImageView")(context.device, &viewInfo, nullptr, &view), "vkCreateImageView resident texture"); + if (context.drawQueue) context.drawQueue->Flush(); upload = std::make_unique(context); const auto commands = upload->Handle(); source->Transition(commands, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL); diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/Sampler.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/Sampler.cpp index f47ec76f7..5a0e4cf8f 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/Sampler.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/Sampler.cpp @@ -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("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("vkDestroySampler")(context.device, sampler, nullptr); + if (sampler) destroySampler(device, sampler, nullptr); } VkSampler Sampler::Handle() const { return sampler; } + std::shared_ptr SamplerCache::Get(const Context& context, std::span words, const GuestSamplerResource& descriptor) { + Require(words.size() == 4, "sampler cache descriptor must contain four DWORDs"); + const std::array 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(context, descriptor); + if (entries.size() >= 256) entries.erase(entries.begin()); + entries.emplace(key, sampler); + return sampler; + } + } diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp index b3aa6017a..45db4dda8 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp @@ -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 @@ -70,6 +71,7 @@ ShaderResources::ShaderResources(const Context& context, const CompiledShader& c } void ShaderResources::build(std::span 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 bindings; @@ -112,36 +114,35 @@ void ShaderResources::build(std::span 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(context, guestMemory); else if (usesFaultBuffer) bda = std::make_unique(context); + timing.Mark("memory_upload"); std::vector description; for (const auto& binding : bindings) { description.push_back(binding.layout); layoutKey.insert(layoutKey.end(), {binding.layout.binding, static_cast(binding.layout.descriptorType), binding.layout.descriptorCount, binding.layout.stageFlags}); } - VkDescriptorSetLayoutCreateInfo info{VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO}; - info.bindingCount = static_cast(description.size()); - info.pBindings = description.data(); - Check(context.Function("vkCreateDescriptorSetLayout")(context.device, &info, nullptr, &_layout), "vkCreateDescriptorSetLayout"); - if (bindings.empty()) return; std::vector sizes; if (storageBuffers != 0) sizes.push_back({VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, static_cast(storageBuffers)}); if (!textures.empty()) sizes.push_back({VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, static_cast(textures.size())}); if (!samplers.empty()) sizes.push_back({VK_DESCRIPTOR_TYPE_SAMPLER, static_cast(samplers.size())}); - VkDescriptorPoolCreateInfo poolInfo{VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO}; - poolInfo.maxSets = 1; - poolInfo.poolSizeCount = static_cast(sizes.size()); - poolInfo.pPoolSizes = sizes.data(); - Check(context.Function("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("vkAllocateDescriptorSets")(context.device, &allocation, &_set), "vkAllocateDescriptorSets"); + if (!context.descriptorCache) context.descriptorCache = std::make_shared(); + descriptors = context.descriptorCache->Take(layoutKey); + if (!descriptors) descriptors = std::make_unique(context, layoutKey, description, sizes); + _layout = descriptors->Layout(); + _set = descriptors->Set(); + timing.Mark("descriptor_acquire"); + std::vector buffers; + std::vector images; + std::vector writes; + buffers.reserve(allocations.size()); + images.reserve(textures.size() + samplers.size()); + writes.reserve(bindings.size()); for (const auto& binding : bindings) { - std::vector buffers; - std::vector 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 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("vkUpdateDescriptorSets")(context.device, 1, &write, 0, nullptr); + writes.push_back(write); } + if (!writes.empty()) context.Function("vkUpdateDescriptorSets")(context.device, static_cast(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(binding.guestDescriptor).subspan(static_cast(element) * elementWords, elementWords); auto resource = DecodeSamplerResource(words); resource.compareEnable = binding.samplerDepthCompare.at(element); - samplers.push_back(std::make_unique(context, resource)); + if (!context.samplerCache) context.samplerCache = std::make_shared(); + 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("vkDestroyDescriptorPool")(context.device, pool, nullptr); - if (_layout) context.Function("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 { diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp index 46f44ca8c..35bf09e0d 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp @@ -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(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(); diff --git a/core/libs/prx/libSceAgcDriver/PostCompile.cmake b/core/libs/prx/libSceAgcDriver/PostCompile.cmake index 1b768a562..0232474ba 100644 --- a/core/libs/prx/libSceAgcDriver/PostCompile.cmake +++ b/core/libs/prx/libSceAgcDriver/PostCompile.cmake @@ -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()