perf(agc): batch draws and cache descriptors, samplers, and buffers
This commit is contained in:
parent
6b8e16b73e
commit
598caac0ce
|
|
@ -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
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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");
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue