Vulkan 学习笔记 04

本章代码在这里
本章的目标是实现初步的FrameGraph排序算法,Frame Graph介绍可以看这个,核心就是已知节点的输入输出,需要将节点排序成一个队列,代表最终的执行顺序。本次测试的图如下所示:
graph
测试主程序如下,主要就是直接构建节点数据,随后使用FrameGraph进行拓扑排序:

#include "runtime/function/render/rhi/frame_graph/frame_graph.h"

#include <filesystem>
#include <iostream>
#include <memory>

using namespace ArchViz;
using namespace std;

int main(int argc, char** argv)
{
    FrameGraph graph;
    graph.name = "test_graph";

    // create nodes
    {
        FrameGraphNode node_a;
        node_a.name = "A";
        node_a.edges_forward.push_back({3});
        node_a.edges_forward.push_back({5});

        FrameGraphNode node_b;
        node_b.name = "B";
        node_b.edges_forward.push_back({1});
        node_b.edges_forward.push_back({2});
        node_b.edges_backward.push_back({4});

        FrameGraphNode node_c;
        node_c.name = "C";
        node_c.edges_forward.push_back({0});
        node_c.edges_backward.push_back({3});

        FrameGraphNode node_d;
        node_d.name = "D";
        node_d.edges_forward.push_back({0});
        node_d.edges_backward.push_back({3});
        node_d.edges_backward.push_back({5});

        FrameGraphNode node_e;
        node_e.name = "E";
        node_e.edges_backward.push_back({1});
        node_e.edges_backward.push_back({2});

        FrameGraphNode node_f;
        node_f.name = "F";
        node_f.edges_forward.push_back({2});
        node_f.edges_backward.push_back({4});

        graph.builder.node_cache.nodes.push_back(node_e);
        graph.builder.node_cache.nodes.push_back(node_c);
        graph.builder.node_cache.nodes.push_back(node_d);
        graph.builder.node_cache.nodes.push_back(node_b);
        graph.builder.node_cache.nodes.push_back(node_a);
        graph.builder.node_cache.nodes.push_back(node_f);
    }

    // graph.nodes.resize(graph.builder.node_cache.nodes.size());

    graph.init();

    for (uint32_t i = 0; i < graph.builder.node_cache.nodes.size(); ++i)
    {
        graph.nodes.push_back({i});
    }

    graph.compile();

    graph.printResult();

    std::string result;
    generate_graphviz(graph, result);
    cout << result << endl;

    graph.shutdown();

    return 0;
}

本章介绍的FrameGraph为了提高速度,将所有节点存到一个vector,通过int来索引,避免大量的内存分配带来的低效(后续将会优化,通过allocator来解决函数内部的内存分配与销毁问题)

#pragma once
#include "runtime/function/render/rhi/frame_graph/frame_resource.h"

namespace ArchViz
{
    struct FrameGraphNode
    {
        int32_t ref_count {0};

        // TODO: render pass handle
        uint32_t render_pass;
        // TODO: framebuffer handle
        uint32_t framebuffer;

        std::vector<FrameGraphResourceHandle> inputs;
        std::vector<FrameGraphResourceHandle> outputs;

        std::vector<FrameGraphNodeHandle> edges_forward;
        std::vector<FrameGraphNodeHandle> edges_backward;

        bool enabled {true};

        std::string name;
    };

    struct FrameGraphNodeCache
    {
        std::unordered_map<uint64_t, FrameGraphNodeHandle> node_map {};

        std::vector<FrameGraphNode> nodes;
    };

    struct FrameGraphBuilder
    {
        void init();
        void shutdown();

        FrameGraphNode* getNode(const std::string& name);
        FrameGraphNode* accessNode(FrameGraphNodeHandle handle);

        static constexpr uint32_t k_max_render_pass_count = 256;
        static constexpr uint32_t k_max_resources_count   = 1024;
        static constexpr uint32_t k_max_nodes_count       = 1024;

        static constexpr const char* k_name = "frame_graph_builder";

        FrameGraphNodeCache       node_cache;
    };

    struct FrameGraph
    {
        void init();
        void shutdown();

        // NOTE(marco): each frame we rebuild the graph so that we can enable only the nodes we are interested in
        void reset();
        void compile();

        void printResult();

        void render();

        FrameGraphBuilder builder;

        // NOTE(marco): nodes sorted in topological order
        std::vector<FrameGraphNodeHandle> nodes;

        std::string name;
    };

    FrameGraphResourceType string_to_resource_type(const std::string& input_type);

    void generate_graphviz(const FrameGraph& graph, std::string& result);
} // namespace ArchViz

FrameGraph的排序很简单,主要利用了节点内部的前向边指针,可以遍历到当前支链的起始节点。由于FrameGraph是有向无环图,因此一定可以找到这个起始节点。

    void FrameGraph::compile()
    {
        LOG_DEBUG("start compile frame graph");

        // - check that input has been produced by a different node
        // - cull inactive nodes

        // TODO: first clear all edges
        // TODO: re-compute all edges

        std::vector<FrameGraphNodeHandle> sorted_nodes;

        std::vector<uint8_t> visited;
        visited.resize(nodes.size());
        memset(visited.data(), 0, sizeof(uint8_t) * visited.size());

        std::vector<FrameGraphNodeHandle> stack;

        for (uint32_t i = 0; i < nodes.size(); ++i)
        {
            FrameGraphNode* node = builder.accessNode(nodes[i]);

            if (!node->enabled)
                continue;

            stack.push_back(nodes[i]);

            while (stack.size() > 0)
            {
                auto node_handle = stack.back();
                if (visited[node_handle.index] == 2)
                {
                    stack.pop_back();
                    continue;
                }

                if (visited[node_handle.index] == 1)
                {
                    visited[node_handle.index] = 2; // added
                    sorted_nodes.push_back(node_handle);
                    stack.pop_back();
                    continue;
                }

                visited[node_handle.index] = 1; // visited

                FrameGraphNode* node = builder.accessNode(node_handle);

                // Leaf node
                if (node->edges_backward.size() == 0)
                {
                    continue;
                }

                for (uint32_t r = 0; r < node->edges_backward.size(); ++r)
                {
                    FrameGraphNodeHandle child_handle = node->edges_backward[r];
                    if (!visited[child_handle.index])
                    {
                        stack.push_back(child_handle);
                    }
                }
            }
        }

        nodes.clear();
        for (auto sort : sorted_nodes)
        {
            nodes.push_back(sort);
        }

        LOG_DEBUG("finish compile frame graph");
    }

最后,为了方便调试,我添加了输出到graphviz格式的功能,可以在这里在线查看当前FrameGraph的节点结构。本次测试的输出结果如下:

digraph test_graph{
        A -> B;
        A -> F;
        B -> C;
        B -> D;
        D -> E;
        C -> E;
        F -> D;
}