framegraph

This commit is contained in:
ouczbs 2024-09-15 11:31:27 +08:00
parent d154ac2251
commit c652f8c86d
8 changed files with 272 additions and 51 deletions

View File

@ -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.
};
}

View File

@ -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<RenderPassExecuteFunction>(node->executor)(*this, context);
}

View File

@ -0,0 +1,15 @@
#pragma once
namespace meta {
template<typename Enum>
concept is_enum_t = requires { std::is_enum_v<Enum>; };
}
template<meta::is_enum_t Enum>
inline constexpr Enum operator&(Enum lhs, Enum rhs) noexcept {
using underlying = std::underlying_type_t<Enum>;
return Enum(underlying(lhs) & underlying(rhs));
}
template<meta::is_enum_t Enum>
inline constexpr bool any(Enum lhs) noexcept {
using underlying = std::underlying_type_t<Enum>;
return (underlying)lhs != 0;
}

View File

@ -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<typename T>
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);
}
}

View File

@ -5,17 +5,19 @@
#include <functional>
#define VK_NO_PROTOTYPES
#include "volk/volk.h"
#include <render/type.h>
#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<void()>;
using commandFn = std::function<void(CommandBuffer& cmd)>;
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<vkn::RenderPassKey>
{
size_t operator()(const vkn::RenderPassKey& key) const noexcept
{
return meta::MurmurHashFn(key);
}
};
}

View File

@ -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<Guid, MeshVAO> MeshTable;
table<Guid, VkRenderPass> RenderPassCache;
table<RenderPassKey, VkRenderPass> RenderPassCache;
public:
VulkanAPI();
@ -33,7 +33,7 @@ namespace vkn {
void BeginFrame()override;
void EndFrame()override;
VkPipeline GetPipeline();
VkRenderPass GetRenderPass(RenderPassKey config);
Backend& GetBackend() {

View File

@ -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;
}
}

View File

@ -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})