From c652f8c86d8237a0952bec38c9cc16d4c491c98f Mon Sep 17 00:00:00 2001 From: ouczbs Date: Sun, 15 Sep 2024 11:31:27 +0800 Subject: [PATCH] framegraph --- .../engine/render/include/render/type.h | 18 ++ .../engine/render/src/graph/frame_graph.cpp | 6 + .../modules/engine/zlib/include/meta/enum.h | 15 ++ .../modules/engine/zlib/include/meta/hash.h | 29 +++ .../modules/render/vulkan/include/vkn/type.h | 74 +++----- .../render/vulkan/include/vkn/vulkan_api.h | 6 +- .../modules/render/vulkan/src/vulkan_api.cpp | 173 ++++++++++++++++++ engine/modules/render/vulkan/xmake.lua | 2 +- 8 files changed, 272 insertions(+), 51 deletions(-) create mode 100644 engine/modules/engine/zlib/include/meta/enum.h create mode 100644 engine/modules/engine/zlib/include/meta/hash.h diff --git a/engine/modules/engine/render/include/render/type.h b/engine/modules/engine/render/include/render/type.h index 67e15bb..129d45e 100644 --- a/engine/modules/engine/render/include/render/type.h +++ b/engine/modules/engine/render/include/render/type.h @@ -41,4 +41,22 @@ namespace api { // For color attachment MSAA resolves. COLOR_ATTACHMENT_RESOLVE, }; + enum class TargetBufferFlags : uint32_t { + NONE = 0x0u, //!< No buffer selected. + COLOR0 = 0x00000001u, //!< Color buffer selected. + COLOR1 = 0x00000002u, //!< Color buffer selected. + COLOR2 = 0x00000004u, //!< Color buffer selected. + COLOR3 = 0x00000008u, //!< Color buffer selected. + COLOR4 = 0x00000010u, //!< Color buffer selected. + COLOR5 = 0x00000020u, //!< Color buffer selected. + COLOR6 = 0x00000040u, //!< Color buffer selected. + COLOR7 = 0x00000080u, //!< Color buffer selected. + + COLOR = COLOR0, //!< \deprecated + COLOR_ALL = COLOR0 | COLOR1 | COLOR2 | COLOR3 | COLOR4 | COLOR5 | COLOR6 | COLOR7, + DEPTH = 0x10000000u, //!< Depth buffer selected. + STENCIL = 0x20000000u, //!< Stencil buffer selected. + DEPTH_AND_STENCIL = DEPTH | STENCIL, //!< depth and stencil buffer selected. + ALL = COLOR_ALL | DEPTH | STENCIL //!< Color, depth and stencil buffer selected. + }; } \ No newline at end of file diff --git a/engine/modules/engine/render/src/graph/frame_graph.cpp b/engine/modules/engine/render/src/graph/frame_graph.cpp index 9c7d3ca..0ceb06e 100644 --- a/engine/modules/engine/render/src/graph/frame_graph.cpp +++ b/engine/modules/engine/render/src/graph/frame_graph.cpp @@ -1,5 +1,6 @@ #include "render/graph/frame_graph.h" #include "render/graph/frame_graph_builder.h" +#include "render/renderapi.h" namespace api { FrameGraphNodePtr FrameGraph::AddRenderPass(const RenderPassSetupFunction& setup, const RenderPassNodeExecuteFn& executor) { @@ -50,9 +51,14 @@ namespace api { { mGraph.clear(); mNodes.clear(); + } + void BeginRenderPass() { + } void FrameGraph::ExecuteRenderPass(RenderPassNode* node, FRenderView& view) { + BeginRenderPass(); + //RenderAPI::Ptr()->BeginRenderPass(); RenderPassContext context{}; std::get(node->executor)(*this, context); } diff --git a/engine/modules/engine/zlib/include/meta/enum.h b/engine/modules/engine/zlib/include/meta/enum.h new file mode 100644 index 0000000..f80ba10 --- /dev/null +++ b/engine/modules/engine/zlib/include/meta/enum.h @@ -0,0 +1,15 @@ +#pragma once +namespace meta { + template + concept is_enum_t = requires { std::is_enum_v; }; +} +template +inline constexpr Enum operator&(Enum lhs, Enum rhs) noexcept { + using underlying = std::underlying_type_t; + return Enum(underlying(lhs) & underlying(rhs)); +} +template +inline constexpr bool any(Enum lhs) noexcept { + using underlying = std::underlying_type_t; + return (underlying)lhs != 0; +} \ No newline at end of file diff --git a/engine/modules/engine/zlib/include/meta/hash.h b/engine/modules/engine/zlib/include/meta/hash.h new file mode 100644 index 0000000..5344998 --- /dev/null +++ b/engine/modules/engine/zlib/include/meta/hash.h @@ -0,0 +1,29 @@ +#pragma once +namespace meta { + // Hash function that takes an arbitrary swath of word-aligned data. + inline uint32_t murmur3(const uint32_t* key, size_t wordCount, uint32_t seed) noexcept { + uint32_t h = seed; + size_t i = wordCount; + do { + uint32_t k = *key++; + k *= 0xcc9e2d51u; + k = (k << 15u) | (k >> 17u); + k *= 0x1b873593u; + h ^= k; + h = (h << 13u) | (h >> 19u); + h = (h * 5u) + 0xe6546b64u; + } while (--i); + h ^= wordCount; + h ^= h >> 16u; + h *= 0x85ebca6bu; + h ^= h >> 13u; + h *= 0xc2b2ae35u; + h ^= h >> 16u; + return h; + } + template + inline uint32_t MurmurHashFn(const T& key) noexcept { + static_assert(0 == (sizeof(key) & 3u), "Hashing requires a size that is a multiple of 4."); + return murmur3((const uint32_t*)&key, sizeof(key) / 4, 0); + } +} \ No newline at end of file diff --git a/engine/modules/render/vulkan/include/vkn/type.h b/engine/modules/render/vulkan/include/vkn/type.h index 498e2b5..9b68be9 100644 --- a/engine/modules/render/vulkan/include/vkn/type.h +++ b/engine/modules/render/vulkan/include/vkn/type.h @@ -5,17 +5,19 @@ #include #define VK_NO_PROTOTYPES #include "volk/volk.h" -#include +#include "render/type.h" #define Z_RENDER_DEBUG 1 namespace vkn { using pmr::Name; using pmr::table; using std::string_view; - inline constexpr string_view VulkanEngineName = "vulkan"; class CommandBuffer; using voidFn = std::function; using commandFn = std::function; - static constexpr uint8_t MAX_SUPPORTED_RENDER_TARGET_COUNT = 8u; + using api::TargetBufferFlags; + + constexpr string_view VulkanEngineName = "vulkan"; + constexpr uint8_t MAX_SUPPORTED_RENDER_TARGET_COUNT = 8u; struct MeshVAO { uint32_t indexCount = 0; // 索引数量 @@ -24,48 +26,26 @@ namespace vkn { VkBuffer vertexBuffer = VK_NULL_HANDLE; bool inUse = false; }; - enum class TargetBufferFlags : uint32_t { - NONE = 0x0u, //!< No buffer selected. - COLOR0 = 0x00000001u, //!< Color buffer selected. - COLOR1 = 0x00000002u, //!< Color buffer selected. - COLOR2 = 0x00000004u, //!< Color buffer selected. - COLOR3 = 0x00000008u, //!< Color buffer selected. - COLOR4 = 0x00000010u, //!< Color buffer selected. - COLOR5 = 0x00000020u, //!< Color buffer selected. - COLOR6 = 0x00000040u, //!< Color buffer selected. - COLOR7 = 0x00000080u, //!< Color buffer selected. - - COLOR = COLOR0, //!< \deprecated - COLOR_ALL = COLOR0 | COLOR1 | COLOR2 | COLOR3 | COLOR4 | COLOR5 | COLOR6 | COLOR7, - DEPTH = 0x10000000u, //!< Depth buffer selected. - STENCIL = 0x20000000u, //!< Stencil buffer selected. - DEPTH_AND_STENCIL = DEPTH | STENCIL, //!< depth and stencil buffer selected. - ALL = COLOR_ALL | DEPTH | STENCIL //!< Color, depth and stencil buffer selected. - }; - - struct alignas(8) RenderPassKey { - // For each target, we need to know three image layouts: the layout BEFORE the pass, the - // layout DURING the pass, and the layout AFTER the pass. Here are the rules: - // - For depth, we explicitly specify all three layouts. - // - Color targets have their initial image layout specified with a bitmask. - // - For each color target, the pre-existing layout is either UNDEFINED (0) or GENERAL (1). - // - The render pass and final images layout for color buffers is always - // VulkanLayout::COLOR_ATTACHMENT. - uint8_t initialColorLayoutMask; - - // Note that if VulkanLayout grows beyond 16, we'd need to up this. - api::RenderLayout initialDepthLayout : 8; - uint8_t padding0; - uint8_t padding1; - - VkFormat colorFormat[MAX_SUPPORTED_RENDER_TARGET_COUNT]; // 32 bytes - VkFormat depthFormat; // 4 bytes - TargetBufferFlags clear; // 4 bytes - TargetBufferFlags discardStart; // 4 bytes - TargetBufferFlags discardEnd; // 4 bytes - uint8_t samples; // 1 byte - uint8_t needsResolveMask; // 1 byte - uint8_t subpassMask; // 1 byte - uint8_t viewCount; // 1 byte - }; + struct RenderPassKey { + VkFormat colorFormat[8]; + VkFormat depthFormat; + VkSampleCountFlagBits samples; + TargetBufferFlags clear; // 4 bytes + TargetBufferFlags discardStart; // 4 bytes + TargetBufferFlags discardEnd; // 4 bytes + uint8_t subpassMask;// 1 byte + uint8_t initialColorLayoutMask;// 1 byte + uint8_t needsResolveMask; // 1 byte + }; } +#include "meta/hash.h" +namespace std { + template<> + struct hash + { + size_t operator()(const vkn::RenderPassKey& key) const noexcept + { + return meta::MurmurHashFn(key); + } + }; +} \ No newline at end of file diff --git a/engine/modules/render/vulkan/include/vkn/vulkan_api.h b/engine/modules/render/vulkan/include/vkn/vulkan_api.h index 7539bb8..ce833f3 100644 --- a/engine/modules/render/vulkan/include/vkn/vulkan_api.h +++ b/engine/modules/render/vulkan/include/vkn/vulkan_api.h @@ -7,6 +7,7 @@ namespace vkn { class Backend; class VulkanWindow; struct MeshVAO; + struct RenderPassKey; using api::Guid; using api::Mesh; using api::Shader; @@ -18,8 +19,7 @@ namespace vkn { VulkanWindow& window; Backend backend; table MeshTable; - table RenderPassCache; - + table RenderPassCache; public: VulkanAPI(); @@ -33,7 +33,7 @@ namespace vkn { void BeginFrame()override; void EndFrame()override; - + VkPipeline GetPipeline(); VkRenderPass GetRenderPass(RenderPassKey config); Backend& GetBackend() { diff --git a/engine/modules/render/vulkan/src/vulkan_api.cpp b/engine/modules/render/vulkan/src/vulkan_api.cpp index 4c28ef3..a577d9e 100644 --- a/engine/modules/render/vulkan/src/vulkan_api.cpp +++ b/engine/modules/render/vulkan/src/vulkan_api.cpp @@ -5,7 +5,22 @@ #include "vkn/thread/buffer_worker.h" #include "vkn/thread/command_worker.h" #include "render/asset/mesh.h" +#include "meta/enum.h" namespace vkn { + inline bool operator==(const RenderPassKey& k1, const RenderPassKey& k2) { + if (k1.initialColorLayoutMask != k2.initialColorLayoutMask) return false; + for (int i = 0; i < MAX_SUPPORTED_RENDER_TARGET_COUNT; i++) { + if (k1.colorFormat[i] != k2.colorFormat[i]) return false; + } + if (k1.depthFormat != k2.depthFormat) return false; + if (k1.clear != k2.clear) return false; + if (k1.discardStart != k2.discardStart) return false; + if (k1.discardEnd != k2.discardEnd) return false; + if (k1.samples != k2.samples) return false; + if (k1.needsResolveMask != k2.needsResolveMask) return false; + if (k1.subpassMask != k2.subpassMask) return false; + return true; + } VulkanAPI::VulkanAPI() : RenderAPI(new VulkanContext()) , window(*VulkanWindow::Ptr()) , backend(VulkanEngineName) @@ -58,6 +73,55 @@ namespace vkn { window.Present(*(VulkanContext*)&context); } VkRenderPass VulkanAPI::GetRenderPass(RenderPassKey config) { + auto it = RenderPassCache.find(config); + if (it != RenderPassCache.end()) { + return it->second; + } + // Set up some const aliases for terseness. + const VkAttachmentLoadOp kClear = VK_ATTACHMENT_LOAD_OP_CLEAR; + const VkAttachmentLoadOp kDontCare = VK_ATTACHMENT_LOAD_OP_DONT_CARE; + const VkAttachmentLoadOp kKeep = VK_ATTACHMENT_LOAD_OP_LOAD; + const VkAttachmentStoreOp kDisableStore = VK_ATTACHMENT_STORE_OP_DONT_CARE; + const VkAttachmentStoreOp kEnableStore = VK_ATTACHMENT_STORE_OP_STORE; + + VkAttachmentReference inputAttachmentRef[MAX_SUPPORTED_RENDER_TARGET_COUNT] = {}; + VkAttachmentReference colorAttachmentRefs[2][MAX_SUPPORTED_RENDER_TARGET_COUNT] = {}; + VkAttachmentReference resolveAttachmentRef[MAX_SUPPORTED_RENDER_TARGET_COUNT] = {}; + VkAttachmentReference depthAttachmentRef = {}; + + const bool hasSubpasses = config.subpassMask != 0; + const bool hasDepth = config.depthFormat != VK_FORMAT_UNDEFINED; + + VkSubpassDescription subpasses[2] = { { + .pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS, + .pInputAttachments = nullptr, + .pColorAttachments = colorAttachmentRefs[0], + .pResolveAttachments = resolveAttachmentRef, + .pDepthStencilAttachment = hasDepth ? &depthAttachmentRef : nullptr + }, + { + .pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS, + .pInputAttachments = inputAttachmentRef, + .pColorAttachments = colorAttachmentRefs[1], + .pResolveAttachments = resolveAttachmentRef, + .pDepthStencilAttachment = hasDepth ? &depthAttachmentRef : nullptr + } }; + + // The attachment list contains: Color Attachments, Resolve Attachments, and Depth Attachment. + // For simplicity, create an array that can hold the maximum possible number of attachments. + // Note that this needs to have the same ordering as the corollary array in getFramebuffer. + VkAttachmentDescription attachments[MAX_SUPPORTED_RENDER_TARGET_COUNT + MAX_SUPPORTED_RENDER_TARGET_COUNT + 1] = {}; + + // We support 2 subpasses, which means we need to supply 1 dependency struct. + VkSubpassDependency dependencies[1] = { { + .srcSubpass = 0, + .dstSubpass = 1, + .srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, + .dstStageMask = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, + .srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, + .dstAccessMask = VK_ACCESS_SHADER_READ_BIT, + .dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT, + } }; // Finally, create the VkRenderPass. VkRenderPassCreateInfo renderPassInfo{ .sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO, @@ -68,7 +132,116 @@ namespace vkn { .dependencyCount = hasSubpasses ? 1u : 0u, .pDependencies = dependencies }; + int attachmentIndex = 0; + // Populate the Color Attachments. + for (int i = 0; i < MAX_SUPPORTED_RENDER_TARGET_COUNT; i++) { + if (config.colorFormat[i] == VK_FORMAT_UNDEFINED) { + continue; + } + const VkImageLayout subpassLayout = VK_IMAGE_LAYOUT_GENERAL; + uint32_t index; + + if (!hasSubpasses) { + index = subpasses[0].colorAttachmentCount++; + colorAttachmentRefs[0][index].layout = subpassLayout; + colorAttachmentRefs[0][index].attachment = attachmentIndex; + } + else { + + // The Driver API consolidates all color attachments from the first and second subpasses + // into a single list, and uses a bitmask to mark attachments that belong only to the + // second subpass and should be available as inputs. All color attachments in the first + // subpass are automatically made available to the second subpass. + + // If there are subpasses, we require the input attachment to be the first attachment. + // Breaking this assumption would likely require enhancements to the Driver API in order + // to supply Vulkan with all the information needed. + if (config.subpassMask & (1 << i)) { + index = subpasses[0].colorAttachmentCount++; + colorAttachmentRefs[0][index].layout = subpassLayout; + colorAttachmentRefs[0][index].attachment = attachmentIndex; + + index = subpasses[1].inputAttachmentCount++; + inputAttachmentRef[index].layout = subpassLayout; + inputAttachmentRef[index].attachment = attachmentIndex; + } + + index = subpasses[1].colorAttachmentCount++; + colorAttachmentRefs[1][index].layout = subpassLayout; + colorAttachmentRefs[1][index].attachment = attachmentIndex; + } + const TargetBufferFlags flag = TargetBufferFlags(int(TargetBufferFlags::COLOR0) << i); + const bool clear = any(config.clear & flag); + const bool discard = any(config.discardStart & flag); + attachments[attachmentIndex++] = { + .format = config.colorFormat[i], + .samples = config.samples, + .loadOp = clear ? kClear : (discard ? kDontCare : kKeep), + .storeOp = kEnableStore, + .stencilLoadOp = kDontCare, + .stencilStoreOp = kDisableStore, + .initialLayout = ((!discard && config.initialColorLayoutMask & (1 << i)) || clear) + ? VK_IMAGE_LAYOUT_GENERAL : VK_IMAGE_LAYOUT_UNDEFINED, + .finalLayout = VK_IMAGE_LAYOUT_GENERAL, + }; + } + // Nulling out the zero-sized lists is necessary to avoid VK_ERROR_OUT_OF_HOST_MEMORY on Adreno. + if (subpasses[0].colorAttachmentCount == 0) { + subpasses[0].pColorAttachments = nullptr; + subpasses[0].pResolveAttachments = nullptr; + subpasses[1].pColorAttachments = nullptr; + subpasses[1].pResolveAttachments = nullptr; + } + // Populate the Resolve Attachments. + VkAttachmentReference* pResolveAttachment = resolveAttachmentRef; + for (int i = 0; i < MAX_SUPPORTED_RENDER_TARGET_COUNT; i++) { + if (config.colorFormat[i] == VK_FORMAT_UNDEFINED) { + continue; + } + + if (!(config.needsResolveMask & (1 << i))) { + pResolveAttachment->attachment = VK_ATTACHMENT_UNUSED; + ++pResolveAttachment; + continue; + } + + pResolveAttachment->attachment = attachmentIndex; + pResolveAttachment->layout = VK_IMAGE_LAYOUT_GENERAL; + ++pResolveAttachment; + + attachments[attachmentIndex++] = { + .format = config.colorFormat[i], + .samples = VK_SAMPLE_COUNT_1_BIT, + .loadOp = kDontCare, + .storeOp = kEnableStore, + .stencilLoadOp = kDontCare, + .stencilStoreOp = kDisableStore, + .initialLayout = VK_IMAGE_LAYOUT_GENERAL, + .finalLayout = VK_IMAGE_LAYOUT_GENERAL, + }; + } + // Populate the Depth Attachment. + if (hasDepth) { + const bool clear = any(config.clear & TargetBufferFlags::DEPTH); + const bool discardStart = any(config.discardStart & TargetBufferFlags::DEPTH); + const bool discardEnd = any(config.discardEnd & TargetBufferFlags::DEPTH); + depthAttachmentRef.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; + depthAttachmentRef.attachment = attachmentIndex; + attachments[attachmentIndex++] = { + .format = config.depthFormat, + .samples = (VkSampleCountFlagBits)config.samples, + .loadOp = clear ? kClear : (discardStart ? kDontCare : kKeep), + .storeOp = discardEnd ? kDisableStore : kEnableStore, + .stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE, + .stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE, + .initialLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL, + .finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL, + }; + } + renderPassInfo.attachmentCount = attachmentIndex; VkRenderPass renderPass; VkResult error = vkCreateRenderPass(backend.GetDevice().Ptr(), &renderPassInfo, nullptr, &renderPass); + RenderPassCache.emplace(config, renderPass); + return renderPass; } } diff --git a/engine/modules/render/vulkan/xmake.lua b/engine/modules/render/vulkan/xmake.lua index a0c8b98..546cadb 100644 --- a/engine/modules/render/vulkan/xmake.lua +++ b/engine/modules/render/vulkan/xmake.lua @@ -1,5 +1,5 @@ shared_module("vulkan","engine") - add_headerfiles("include/**.h") + add_headerfiles("include/**.h","include/**.inl") add_files("src/**.cpp", "include/volk/volk.c") add_packages("vulkansdk", {public = true}) add_dependency("engine", {public = true})