// src/vkgs/src/render_graph.cpp 시험 — GPU 없이. vkCmdPipelineBarrier 를 가짜로 정의해 그래프가 넣는 배리어를 기록한다. #include #include #include #include #include #include "render_graph.hpp" struct Rec { VkPipelineStageFlags src, dst; bool image; VkImageLayout old_layout, new_layout; }; static std::vector g_barriers; extern "C" VKAPI_ATTR void VKAPI_CALL vkCmdPipelineBarrier(VkCommandBuffer, VkPipelineStageFlags src, VkPipelineStageFlags dst, VkDependencyFlags, uint32_t, const VkMemoryBarrier*, uint32_t, const VkBufferMemoryBarrier*, uint32_t n_img, const VkImageMemoryBarrier* img) { g_barriers.push_back({src, dst, n_img > 0, n_img ? img->oldLayout : VK_IMAGE_LAYOUT_UNDEFINED, n_img ? img->newLayout : VK_IMAGE_LAYOUT_UNDEFINED}); } static int fails = 0; #define CHECK(c) \ do { \ if (!(c)) { \ std::printf("FAIL %s:%d %s\n", __FILE__, __LINE__, #c); \ ++fails; \ } \ } while (0) using nlohmann::json; static std::vector order_names(vkgs::RenderGraph& g) { std::vector o; for (int i : g.order()) o.push_back(g.passes()[i].name); return o; } int main(int argc, char** argv) { // 1) 실행 순서 = 선언 순서 (계약 — render_graph.hpp). 선언 순서가 접근 순서를 정의한다 json j = json::parse(R"({ "resources": {"a": {"type": "buffer"}, "img": {"type": "image"}}, "passes": [ {"name": "consume", "kind": "x", "uses": [["a", "storage_read"], ["img", "storage_write"]]}, {"name": "produce", "kind": "x", "uses": [["a", "storage_write"]]}, {"name": "present", "kind": "none", "uses": [["img", "transfer_src"]]} ]})"); vkgs::RenderGraph g; g.load(j); auto o = order_names(g); CHECK((o == std::vector{"consume", "produce", "present"})); // 2) 독립 패스가 섞여 있어도 선언 순서 json j2 = json::parse(R"({ "resources": {"a": {"type": "buffer"}, "b": {"type": "buffer"}}, "passes": [ {"name": "wa", "kind": "x", "uses": [["a", "storage_write"]]}, {"name": "wb", "kind": "x", "uses": [["b", "storage_write"]]}, {"name": "ra", "kind": "x", "uses": [["a", "storage_read"]]}, {"name": "rb", "kind": "x", "uses": [["b", "storage_read"]]} ]})"); g.load(j2); o = order_names(g); CHECK((o == std::vector{"wa", "wb", "ra", "rb"})); // 3) 모르는 리소스·사용 방식은 로드 때 거부 bool threw = false; try { g.load(json::parse(R"({"resources": {}, "passes": [{"name": "p", "kind": "x", "uses": [["nope", "storage_read"]]}]})")); } catch (const std::exception&) { threw = true; } CHECK(threw); threw = false; try { g.load(json::parse(R"({"resources": {"a": {}}, "passes": [{"name": "p", "kind": "x", "uses": [["a", "sideways"]]}]})")); } catch (const std::exception&) { threw = true; } CHECK(threw); // 4) 배리어: 쓰기 → 같은 단계 읽기 한 번, 두 번째 읽기는 배리어 없음, 이미지 레이아웃 전환 g.load(j2); g.begin_frame(); g_barriers.clear(); g.traverse(VK_NULL_HANDLE, [](vkgs::RGPass&) {}); // wa, wb: 처음 쓰기 — 앞선 접근이 없어 배리어 없음. ra, rb: RAW 각 1개 CHECK(g_barriers.size() == 2); for (const auto& b : g_barriers) CHECK(b.src == VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT && !b.image); json j3 = json::parse(R"({ "resources": {"img": {"type": "image"}}, "passes": [ {"name": "w", "kind": "x", "uses": [["img", "storage_write"]]}, {"name": "r1", "kind": "x", "uses": [["img", "storage_read"]]}, {"name": "r2", "kind": "x", "uses": [["img", "storage_read"]]}, {"name": "copy", "kind": "x", "uses": [["img", "transfer_src"]]} ]})"); g.load(j3); g.bind_image("img", reinterpret_cast(uintptr_t(1))); g.begin_frame(); g_barriers.clear(); g.traverse(VK_NULL_HANDLE, [](vkgs::RGPass&) {}); // w: UNDEFINED → GENERAL, r1: RAW, r2: 없음 (이미 보임), copy: RAW + GENERAL → TRANSFER_SRC CHECK(g_barriers.size() == 3); if (g_barriers.size() == 3) { CHECK(g_barriers[0].old_layout == VK_IMAGE_LAYOUT_UNDEFINED && g_barriers[0].new_layout == VK_IMAGE_LAYOUT_GENERAL); CHECK(g_barriers[1].src == VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT); CHECK(g_barriers[2].dst == VK_PIPELINE_STAGE_TRANSFER_BIT && g_barriers[2].new_layout == VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL); } // 4b) 같은 단계에서 이미 보이는 쓰기라도 레이아웃이 바뀌면 그 쓰기와 동기화한다 (전환 = 쓰기) json j4 = json::parse(R"({ "resources": {"img": {"type": "image"}}, "passes": [ {"name": "copy_in", "kind": "x", "uses": [["img", "transfer_dst"]]}, {"name": "copy_out", "kind": "x", "uses": [["img", "transfer_src"]]} ]})"); g.load(j4); g.bind_image("img", reinterpret_cast(uintptr_t(1))); g.begin_frame(); g_barriers.clear(); g.traverse(VK_NULL_HANDLE, [](vkgs::RGPass&) {}); CHECK(g_barriers.size() == 2); if (g_barriers.size() == 2) CHECK(g_barriers[1].src == VK_PIPELINE_STAGE_TRANSFER_BIT && g_barriers[1].new_layout == VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL); // 4c) follows: 짝 패스가 꺼지면 같이 건너뛴다 json j5 = json::parse(R"({ "resources": {"a": {"type": "buffer"}}, "passes": [ {"name": "lead", "kind": "x", "optional": true, "uses": [["a", "storage_write"]]}, {"name": "tail", "kind": "x", "follows": "lead", "uses": [["a", "storage_read"]]} ]})"); g.load(j5); g.find("lead")->enabled = false; g.begin_frame(); std::vector ran5; g.traverse(VK_NULL_HANDLE, [&](vkgs::RGPass& p) { ran5.push_back(p.name); }); CHECK(ran5.empty()); // 5) 꺼진 패스는 건너뛴다 g.load(j3); g.find("r1")->enabled = false; g.begin_frame(); std::vector ran; g.traverse(VK_NULL_HANDLE, [&](vkgs::RGPass& p) { ran.push_back(p.name); }); CHECK((ran == std::vector{"w", "r2", "copy"})); // 5b) follows: 짝 패스가 켜져 있으면 같이 돈다 g.load(j5); g.begin_frame(); std::vector ran5b; g.traverse(VK_NULL_HANDLE, [&](vkgs::RGPass& p) { ran5b.push_back(p.name); }); CHECK((ran5b == std::vector{"lead", "tail"})); // 5c) 색 첨부로 쓴 이미지를 컴퓨트가 읽기 (RAW) — 렌더 패스 finalLayout(GENERAL) 그대로, 색 출력 단계에서 json j6 = json::parse(R"({ "resources": {"img": {"type": "image"}, "dep": {"type": "image"}}, "passes": [ {"name": "raster", "kind": "x", "uses": [["img", "color_attachment"], ["dep", "depth_attachment"]]}, {"name": "read", "kind": "x", "uses": [["img", "storage_read"], ["dep", "storage_read"]]} ]})"); g.load(j6); g.bind_image("img", reinterpret_cast(uintptr_t(1))); g.bind_image("dep", reinterpret_cast(uintptr_t(2)), VK_IMAGE_ASPECT_DEPTH_BIT); g.begin_frame(); g_barriers.clear(); g.traverse(VK_NULL_HANDLE, [](vkgs::RGPass&) {}); CHECK(g_barriers.size() == 2); // 첨부 자체에는 배리어 없음 (렌더 패스 몫), 읽기에 각 1개 if (g_barriers.size() == 2) { CHECK(g_barriers[0].src == VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT && g_barriers[0].old_layout == VK_IMAGE_LAYOUT_GENERAL && g_barriers[0].new_layout == VK_IMAGE_LAYOUT_GENERAL); CHECK(g_barriers[1].src == VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT && g_barriers[1].old_layout == VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL && g_barriers[1].new_layout == VK_IMAGE_LAYOUT_GENERAL); } // 6) 저장소의 실제 그래프가 로드·정렬되고 DOT 를 만든다 if (argc > 1) { std::ifstream f(argv[1]); g.load(json::parse(f, nullptr, true, true)); o = order_names(g); CHECK(o.front() == "clear_draw_args" && o.back() == "to_present"); CHECK(g.to_dot().find("digraph") != std::string::npos); // 실제 그래프 순회: 강수 레이어(색 첨부) → 날씨(컴퓨트 읽기) 에 색 출력 → 컴퓨트 배리어가 있어야 한다 int k = 1; for (const auto& [name, r] : g.resources()) if (r.type == "image") g.bind_image(name, reinterpret_cast(uintptr_t(k++))); for (auto& p : g.passes()) p.enabled = true; g.begin_frame(); g_barriers.clear(); std::vector ran6; g.traverse(VK_NULL_HANDLE, [&](vkgs::RGPass& p) { ran6.push_back(p.name); }); bool color_to_compute = false; for (const auto& b : g_barriers) if (b.image && b.src == VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT && (b.dst & VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT)) color_to_compute = true; CHECK(color_to_compute); CHECK(std::find(ran6.begin(), ran6.end(), "precip") < std::find(ran6.begin(), ran6.end(), "weather")); } std::printf("%s\n", fails ? "실패" : "통과"); return fails ? 1 : 0; }