diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt index 38d13892..35ae069a 100644 --- a/app/CMakeLists.txt +++ b/app/CMakeLists.txt @@ -170,6 +170,7 @@ add_library(skyline SHARED ${source_DIR}/skyline/gpu/presentation_engine.cpp ${source_DIR}/skyline/gpu/shader_manager.cpp ${source_DIR}/skyline/gpu/cache/graphics_pipeline_cache.cpp + ${source_DIR}/skyline/gpu/cache/renderpass_cache.cpp ${source_DIR}/skyline/gpu/interconnect/command_executor.cpp ${source_DIR}/skyline/gpu/interconnect/command_nodes.cpp ${source_DIR}/skyline/gpu/interconnect/conversion/quads.cpp diff --git a/app/src/main/cpp/skyline/gpu.cpp b/app/src/main/cpp/skyline/gpu.cpp index 925f8c2a..574115b2 100644 --- a/app/src/main/cpp/skyline/gpu.cpp +++ b/app/src/main/cpp/skyline/gpu.cpp @@ -278,5 +278,6 @@ namespace skyline::gpu { buffer(*this), descriptor(*this), shader(state, *this), - graphicsPipelineCache(*this) {} + graphicsPipelineCache(*this), + renderPassCache(*this) {} } diff --git a/app/src/main/cpp/skyline/gpu.h b/app/src/main/cpp/skyline/gpu.h index 33c2268c..dea96159 100644 --- a/app/src/main/cpp/skyline/gpu.h +++ b/app/src/main/cpp/skyline/gpu.h @@ -12,6 +12,7 @@ #include "gpu/descriptor_allocator.h" #include "gpu/shader_manager.h" #include "gpu/cache/graphics_pipeline_cache.h" +#include "gpu/cache/renderpass_cache.h" namespace skyline::gpu { static constexpr u32 VkApiVersion{VK_API_VERSION_1_1}; //!< The version of core Vulkan that we require @@ -47,6 +48,7 @@ namespace skyline::gpu { ShaderManager shader; cache::GraphicsPipelineCache graphicsPipelineCache; + cache::RenderPassCache renderPassCache; GPU(const DeviceState &state); }; diff --git a/app/src/main/cpp/skyline/gpu/cache/common.h b/app/src/main/cpp/skyline/gpu/cache/common.h new file mode 100644 index 00000000..f9b16cf9 --- /dev/null +++ b/app/src/main/cpp/skyline/gpu/cache/common.h @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: MPL-2.0 +// Copyright © 2022 Skyline Team and Contributors (https://github.com/skyline-emu/) + +#pragma once + +#include +#include + +namespace skyline::gpu::cache { + /** + * @brief All unique metadata in a single attachment for a compatible render pass according to Render Pass Compatibility clause in the Vulkan specification + * @url https://www.khronos.org/registry/vulkan/specs/1.3-extensions/html/vkspec.html#renderpass-compatibility + * @url https://www.khronos.org/registry/vulkan/specs/1.3-extensions/man/html/VkAttachmentDescription.html + */ + struct AttachmentMetadata { + vk::Format format; + vk::SampleCountFlagBits sampleCount; + + constexpr AttachmentMetadata(vk::Format format, vk::SampleCountFlagBits sampleCount) : format(format), sampleCount(sampleCount) {} + + bool operator==(const AttachmentMetadata &rhs) const = default; + }; +} diff --git a/app/src/main/cpp/skyline/gpu/cache/graphics_pipeline_cache.h b/app/src/main/cpp/skyline/gpu/cache/graphics_pipeline_cache.h index a08708f0..5adbbf6c 100644 --- a/app/src/main/cpp/skyline/gpu/cache/graphics_pipeline_cache.h +++ b/app/src/main/cpp/skyline/gpu/cache/graphics_pipeline_cache.h @@ -4,6 +4,7 @@ #pragma once #include +#include "common.h" namespace skyline::gpu::cache { /** @@ -47,18 +48,6 @@ namespace skyline::gpu::cache { }; private: - /** - * @brief All unique metadata a single attachment for a compatible pipeline according to Render Pass Compatibility clause in the Vulkan specification - * @url https://www.khronos.org/registry/vulkan/specs/1.3-extensions/html/vkspec.html#renderpass-compatibility - * @url https://www.khronos.org/registry/vulkan/specs/1.3-extensions/man/html/VkAttachmentDescription.html - */ - struct AttachmentMetadata { - vk::Format format; - vk::SampleCountFlagBits sampleCount; - - bool operator==(const AttachmentMetadata &rhs) const = default; - }; - /** * @brief All data in PipelineState in value form to allow cheap heterogenous lookups with reference types while still storing a value-based key in the map */ diff --git a/app/src/main/cpp/skyline/gpu/cache/renderpass_cache.cpp b/app/src/main/cpp/skyline/gpu/cache/renderpass_cache.cpp new file mode 100644 index 00000000..c043e635 --- /dev/null +++ b/app/src/main/cpp/skyline/gpu/cache/renderpass_cache.cpp @@ -0,0 +1,175 @@ +// SPDX-License-Identifier: MPL-2.0 +// Copyright © 2022 Skyline Team and Contributors (https://github.com/skyline-emu/) + +#include +#include +#include "renderpass_cache.h" + +namespace skyline::gpu::cache { + RenderPassCache::RenderPassCache(gpu::GPU &gpu) : gpu(gpu) {} + + RenderPassCache::RenderPassMetadata::RenderPassMetadata(const vk::RenderPassCreateInfo &createInfo) { + for (const auto &attachment : span{createInfo.pAttachments, createInfo.attachmentCount}) + attachments.emplace_back(attachment.format, attachment.samples); + + subpasses.reserve(createInfo.subpassCount); + for (const auto &subpass : span{createInfo.pSubpasses, createInfo.subpassCount}) { + auto &subpassMetadata{subpasses.emplace_back()}; + + subpassMetadata.inputAttachments.reserve(subpass.inputAttachmentCount); + for (const auto &reference : span{subpass.pInputAttachments, subpass.inputAttachmentCount}) + subpassMetadata.inputAttachments.emplace_back(reference.attachment); + + subpassMetadata.colorAttachments.reserve(subpass.colorAttachmentCount); + for (const auto &reference : span{subpass.pColorAttachments, subpass.colorAttachmentCount}) + subpassMetadata.colorAttachments.emplace_back(reference.attachment); + + auto resolveAttachmentCount{subpass.pResolveAttachments ? subpass.colorAttachmentCount : 0}; + subpassMetadata.resolveAttachments.reserve(resolveAttachmentCount); + for (const auto &reference : span{subpass.pResolveAttachments, resolveAttachmentCount}) + subpassMetadata.resolveAttachments.emplace_back(reference.attachment); + + if (subpass.pDepthStencilAttachment) + subpassMetadata.depthStencilAttachment.emplace(subpass.pDepthStencilAttachment->attachment); + + subpassMetadata.preserveAttachments.reserve(subpass.preserveAttachmentCount); + for (const auto &index : span{subpass.pPreserveAttachments, subpass.preserveAttachmentCount}) + subpassMetadata.resolveAttachments.emplace_back(index); + } + } + + #define HASH(x) boost::hash_combine(hash, x) + + size_t RenderPassCache::RenderPassHash::operator()(const RenderPassMetadata &key) const { + size_t hash{}; + + HASH(key.attachments.size()); + for (const auto &attachment : key.attachments) { + HASH(attachment.format); + HASH(attachment.sampleCount); + } + + HASH(key.subpasses.size()); + for (const auto &subpass : key.subpasses) { + HASH(subpass.inputAttachments.size()); + for (const auto &reference : subpass.inputAttachments) + HASH(reference); + + HASH(subpass.colorAttachments.size()); + for (const auto &reference : subpass.colorAttachments) + HASH(reference); + + HASH(subpass.resolveAttachments.size()); + for (const auto &reference : subpass.resolveAttachments) + HASH(reference); + + HASH(subpass.depthStencilAttachment.has_value()); + if (subpass.depthStencilAttachment) + HASH(*subpass.depthStencilAttachment); + + HASH(subpass.preserveAttachments.size()); + for (const auto &index : subpass.preserveAttachments) + HASH(index); + } + + return hash; + } + + size_t RenderPassCache::RenderPassHash::operator()(const vk::RenderPassCreateInfo &key) const { + size_t hash{}; + + HASH(key.attachmentCount); + for (const auto &attachment : span{key.pAttachments, key.attachmentCount}) { + HASH(attachment.format); + HASH(attachment.samples); + } + + HASH(key.subpassCount); + for (const auto &subpass : span{key.pSubpasses, key.subpassCount}) { + HASH(subpass.inputAttachmentCount); + for (const auto &reference : span{subpass.pInputAttachments, subpass.inputAttachmentCount}) + HASH(reference.attachment); + + HASH(subpass.colorAttachmentCount); + for (const auto &reference : span{subpass.pColorAttachments, subpass.colorAttachmentCount}) + HASH(reference.attachment); + + u32 resolveAttachmentCount{subpass.pResolveAttachments ? subpass.colorAttachmentCount : 0}; + HASH(resolveAttachmentCount); + for (const auto &reference : span{subpass.pResolveAttachments, resolveAttachmentCount}) + HASH(reference.attachment); + + HASH(subpass.pDepthStencilAttachment != nullptr); + if (subpass.pDepthStencilAttachment) + HASH(subpass.pDepthStencilAttachment->attachment); + + HASH(subpass.preserveAttachmentCount); + for (const auto &index : span{subpass.pPreserveAttachments, subpass.preserveAttachmentCount}) + HASH(index); + } + + return hash; + } + + #undef HASH + + bool RenderPassCache::RenderPassEqual::operator()(const RenderPassMetadata &lhs, const RenderPassMetadata &rhs) const { + return lhs == rhs; + } + + bool RenderPassCache::RenderPassEqual::operator()(const RenderPassMetadata &lhs, const vk::RenderPassCreateInfo &rhs) const { + #define RETF(condition) if (condition) { return false; } + + RETF(lhs.attachments.size() != rhs.attachmentCount) + const vk::AttachmentDescription *vkAttachment{rhs.pAttachments}; + for (const auto &attachment : lhs.attachments) { + RETF(attachment.format != vkAttachment->format) + RETF(attachment.sampleCount != vkAttachment->samples) + vkAttachment++; + } + + RETF(lhs.subpasses.size() != rhs.subpassCount) + const vk::SubpassDescription *vkSubpass{rhs.pSubpasses}; + for (const auto &subpass : lhs.subpasses) { + RETF(subpass.inputAttachments.size() != vkSubpass->inputAttachmentCount) + const vk::AttachmentReference *vkReference{vkSubpass->pInputAttachments}; + for (const auto &reference : subpass.inputAttachments) + RETF(reference != (vkReference++)->attachment) + + RETF(subpass.colorAttachments.size() != vkSubpass->colorAttachmentCount) + vkReference = vkSubpass->pColorAttachments; + for (const auto &reference : subpass.colorAttachments) + RETF(reference != (vkReference++)->attachment) + + RETF(subpass.resolveAttachments.size() != (vkSubpass->pResolveAttachments ? vkSubpass->colorAttachmentCount : 0)) + vkReference = vkSubpass->pResolveAttachments; + for (const auto &reference : subpass.resolveAttachments) + RETF(reference != (vkReference++)->attachment) + + RETF(subpass.depthStencilAttachment.has_value() != (vkSubpass->pDepthStencilAttachment != nullptr)) + if (subpass.depthStencilAttachment) + RETF(*subpass.depthStencilAttachment != vkSubpass->pDepthStencilAttachment->attachment) + + RETF(subpass.preserveAttachments.size() != vkSubpass->preserveAttachmentCount) + const u32 *vkIndex{vkSubpass->pPreserveAttachments}; + for (const auto &attachment : subpass.preserveAttachments) + RETF(attachment != *(vkIndex++)) + + vkSubpass++; + } + + #undef RETF + + return true; + } + + vk::RenderPass RenderPassCache::GetRenderPass(const vk::RenderPassCreateInfo &createInfo) { + std::scoped_lock lock{mutex}; + auto it{renderPassCache.find(createInfo)}; + if (it != renderPassCache.end()) + return *it->second; + + auto entryIt{renderPassCache.try_emplace(RenderPassMetadata{createInfo}, gpu.vkDevice, createInfo)}; + return *entryIt.first->second; + } +} diff --git a/app/src/main/cpp/skyline/gpu/cache/renderpass_cache.h b/app/src/main/cpp/skyline/gpu/cache/renderpass_cache.h new file mode 100644 index 00000000..77fca874 --- /dev/null +++ b/app/src/main/cpp/skyline/gpu/cache/renderpass_cache.h @@ -0,0 +1,71 @@ +// SPDX-License-Identifier: MPL-2.0 +// Copyright © 2022 Skyline Team and Contributors (https://github.com/skyline-emu/) + +#pragma once + +#include "common.h" + +namespace skyline::gpu::cache { + /** + * @brief A cache for Vulkan render passes to avoid unnecessary recreation and attain stability in handles for subsequent caches + */ + class RenderPassCache { + private: + GPU &gpu; + std::mutex mutex; //!< Synchronizes access to the cache + + using AttachmentReference = u32; + + /** + * @brief All unique metadata in a single subpass for a compatible render pass according to Render Pass Compatibility clause in the Vulkan specification + * @url https://www.khronos.org/registry/vulkan/specs/1.3-extensions/html/vkspec.html#renderpass-compatibility + * @url https://www.khronos.org/registry/vulkan/specs/1.3-extensions/man/html/VkSubpassDescription.html + */ + struct SubpassMetadata { + std::vector inputAttachments; + std::vector colorAttachments; + std::vector resolveAttachments; + std::optional depthStencilAttachment; + std::vector preserveAttachments; + + bool operator==(const SubpassMetadata &rhs) const = default; + }; + + /** + * @brief All unique metadata in a render pass for a corresponding compatible render pass according to Render Pass Compatibility clause in the Vulkan specification + * @url https://www.khronos.org/registry/vulkan/specs/1.3-extensions/html/vkspec.html#renderpass-compatibility + * @url https://www.khronos.org/registry/vulkan/specs/1.3-extensions/man/html/VkRenderPassCreateInfo.html + */ + struct RenderPassMetadata { + std::vector attachments; + std::vector subpasses; + + RenderPassMetadata(const vk::RenderPassCreateInfo &createInfo); + + bool operator==(const RenderPassMetadata &other) const = default; + }; + + struct RenderPassHash { + using is_transparent = std::true_type; + + size_t operator()(const RenderPassMetadata &key) const; + + size_t operator()(const vk::RenderPassCreateInfo &key) const; + }; + + struct RenderPassEqual { + using is_transparent = std::true_type; + + bool operator()(const RenderPassMetadata &lhs, const RenderPassMetadata &rhs) const; + + bool operator()(const RenderPassMetadata &lhs, const vk::RenderPassCreateInfo &rhs) const; + }; + + std::unordered_map renderPassCache; + + public: + RenderPassCache(GPU &gpu); + + vk::RenderPass GetRenderPass(const vk::RenderPassCreateInfo &createInfo); + }; +} diff --git a/app/src/main/cpp/skyline/gpu/interconnect/command_nodes.cpp b/app/src/main/cpp/skyline/gpu/interconnect/command_nodes.cpp index b5066908..52a3bf40 100644 --- a/app/src/main/cpp/skyline/gpu/interconnect/command_nodes.cpp +++ b/app/src/main/cpp/skyline/gpu/interconnect/command_nodes.cpp @@ -19,12 +19,9 @@ namespace skyline::gpu::interconnect::node { ), storage(std::make_shared()), renderArea(renderArea) {} RenderPassNode::Storage::~Storage() { - if (device) { + if (device) if (framebuffer) (**device).destroy(framebuffer, nullptr, *device->getDispatcher()); - if (renderPass) - (**device).destroy(renderPass, nullptr, *device->getDispatcher()); - } } u32 RenderPassNode::AddAttachment(TextureView *view) { @@ -217,15 +214,14 @@ namespace skyline::gpu::interconnect::node { preserveAttachmentIt++; } - auto renderPass{(*gpu.vkDevice).createRenderPass(vk::RenderPassCreateInfo{ + auto renderPass{gpu.renderPassCache.GetRenderPass(vk::RenderPassCreateInfo{ .attachmentCount = static_cast(attachmentDescriptions.size()), .pAttachments = attachmentDescriptions.data(), .subpassCount = static_cast(subpassDescriptions.size()), .pSubpasses = subpassDescriptions.data(), .dependencyCount = static_cast(subpassDependencies.size()), .pDependencies = subpassDependencies.data(), - }, nullptr, *gpu.vkDevice.getDispatcher())}; - storage->renderPass = renderPass; + })}; auto framebuffer{(*gpu.vkDevice).createFramebuffer(vk::FramebufferCreateInfo{ .renderPass = renderPass, diff --git a/app/src/main/cpp/skyline/gpu/interconnect/command_nodes.h b/app/src/main/cpp/skyline/gpu/interconnect/command_nodes.h index 75061594..feca8ba5 100644 --- a/app/src/main/cpp/skyline/gpu/interconnect/command_nodes.h +++ b/app/src/main/cpp/skyline/gpu/interconnect/command_nodes.h @@ -34,7 +34,6 @@ namespace skyline::gpu::interconnect::node { struct Storage : public FenceCycleDependency { vk::raii::Device *device{}; vk::Framebuffer framebuffer{}; - vk::RenderPass renderPass{}; ~Storage(); };