diff --git a/assets/shaders/test.slang b/assets/shaders/test.slang new file mode 100644 index 0000000..5538d2d --- /dev/null +++ b/assets/shaders/test.slang @@ -0,0 +1,19 @@ +struct post_transform_vertex { + float4 position : SV_Position; + float2 uv : TEXCOORD; +}; + +[shader("vertex")] +post_transform_vertex vert_main(uint vertex_id : SV_VertexID) { + post_transform_vertex output; + + output.uv = float2((vertex_id << 1) & 2, vertex_id & 2); + output.position = float4(output.uv * 2.0f - 1.0f, 0.0f, 1.0f); + + return output; +} + +[shader("fragment")] +float4 frag_main(post_transform_vertex input) : SV_Target { + return float4(1.0f); +} diff --git a/external/CMakeLists.txt b/external/CMakeLists.txt index f6c5d5b..0c435e6 100644 --- a/external/CMakeLists.txt +++ b/external/CMakeLists.txt @@ -1,7 +1,8 @@ include(FetchContent) set(VUK_USE_SHADERC OFF CACHE BOOL "NO SHADERC" FORCE) -set(VUK_EXTRA_IMGUI OFF CACHE BOOL "WAYLAND GLFW" FORCE) +set(VUK_EXTRA_IMGUI ON CACHE BOOL "VUK IMGUI" FORCE) +set(VUK_IMGUI_PLATFORM_BACKEND "glfw" CACHE STRING "USE IMGUI VUK GLFW" FORCE) FetchContent_Declare( vuk_fetch GIT_REPOSITORY https://github.com/martty/vuk.git diff --git a/source/CMakeLists.txt b/source/CMakeLists.txt index 0757e6d..baf9065 100644 --- a/source/CMakeLists.txt +++ b/source/CMakeLists.txt @@ -7,6 +7,8 @@ add_library(rogue-lib STATIC ui/user_input.hpp render/context.cpp render/context.hpp + render/shader_compiler.cpp + render/shader_compiler.hpp rogue.cpp rogue.hpp ) @@ -15,6 +17,7 @@ target_compile_definitions(rogue-lib PUBLIC -DGLFW_INCLUDE_VULKAN -DGLM_ENABLE_E target_link_libraries(rogue-lib PUBLIC glm vuk + vuk-extra-imgui-platform vk-bootstrap slang fastgltf diff --git a/source/render/shader_compiler.cpp b/source/render/shader_compiler.cpp new file mode 100644 index 0000000..82247d3 --- /dev/null +++ b/source/render/shader_compiler.cpp @@ -0,0 +1,220 @@ +#include "shader_compiler.hpp" + +#include +#include +#include +#include +#include + +#include +#include + +namespace rog::render { + +void shader_compiler::destroy_slang(ISlangUnknown *object) { + object->release(); +} + +shader_compiler::shader_compiler(std::filesystem::path root) : root(root) { + const auto slang_global_session_description = SlangGlobalSessionDesc{}; + slang::IGlobalSession *global_session_ptr = nullptr; + [[maybe_unused]] const auto result = slang::createGlobalSession( + &slang_global_session_description, &global_session_ptr); + global_session = slang_unique_ptr(global_session_ptr, + destroy_slang); + + auto paths_as_c_strings = std::vector(); + for (const auto &path : search_paths) { + paths_as_c_strings.push_back(path.generic_string().c_str()); + } + + auto profile = global_session->findProfile("spirv_1_4"); + + auto target = slang::TargetDesc{ + .format = SlangCompileTarget::SLANG_SPIRV, + .profile = profile, + .flags = 0, + }; + + auto optimisation_level = slang::CompilerOptionEntry{ + .name = slang::CompilerOptionName::Optimization, + .value = { + .intValue0 = SlangOptimizationLevel::SLANG_OPTIMIZATION_LEVEL_MAXIMAL, + }}; + auto debug_info = slang::CompilerOptionEntry{ + .name = slang::CompilerOptionName::DebugInformation, + .value = { + .intValue0 = SLANG_DEBUG_INFO_LEVEL_MAXIMAL, + }}; + auto line_directive = slang::CompilerOptionEntry{ + .name = slang::CompilerOptionName::LineDirectiveMode, + .value = { + .intValue0 = SLANG_LINE_DIRECTIVE_MODE_SOURCE_MAP, + }}; + auto name_main = slang::CompilerOptionEntry{ + .name = slang::CompilerOptionName::VulkanUseEntryPointName, + .value = { + .intValue0 = 1, + }}; + + auto infos = + std::array{optimisation_level, debug_info, line_directive, name_main}; + + auto slang_session_descriptor = slang::SessionDesc{}; + slang_session_descriptor.searchPaths = paths_as_c_strings.data(); + slang_session_descriptor.searchPathCount = + static_cast(paths_as_c_strings.size()); + slang_session_descriptor.targets = ⌖ + slang_session_descriptor.targetCount = 1; + slang_session_descriptor.defaultMatrixLayoutMode = + SLANG_MATRIX_LAYOUT_COLUMN_MAJOR; + slang_session_descriptor.compilerOptionEntries = infos.data(); + slang_session_descriptor.compilerOptionEntryCount = + static_cast(infos.size()); + + slang::ISession *session_ptr = nullptr; + global_session->createSession(slang_session_descriptor, &session_ptr); + session = slang_unique_ptr(session_ptr, destroy_slang); +} + +std::vector +shader_compiler::compile(const std::string_view module_name, + const std::string_view entry_name) { + const auto initial_path = root / module_name; + + const auto module = load_module(initial_path.generic_string()); + const auto entry_point = find_entry_point(module, entry_name); + const auto composed_program = create_composed_program(module, entry_point); + const auto spirv_blob = compile_to_spirv(composed_program.get()); + + auto data = std::vector(spirv_blob->getBufferSize() / 4); + std::memcpy(data.data(), spirv_blob->getBufferPointer(), data.size() * 4); + + return data; +} + +slang::IModule * +shader_compiler::load_module(const std::string_view module_name) { + slang::IBlob *module_blob_ptr = nullptr; + auto module = session->loadModule(module_name.data(), &module_blob_ptr); + auto module_blob = + slang_unique_ptr(module_blob_ptr, destroy_slang); + + if (!module) { + throw_error("loading module", module_blob.get()); + } + + return module; +} + +void shader_compiler::throw_error(const std::string_view context, + slang::IBlob *diagnostic_blob) { + const auto char_ptr = + reinterpret_cast(diagnostic_blob->getBufferPointer()); + + const auto message = std::format("{}: {}", context, char_ptr); + throw std::runtime_error(message); +} +slang::IEntryPoint * +shader_compiler::find_entry_point(slang::IModule *module, + const std::string_view entry_point_name) { + slang::IEntryPoint *entry_point; + if (module->findEntryPointByName(entry_point_name.data(), &entry_point) != + SLANG_OK) { + throw_error(std::format("finding entry point {}", entry_point_name)); + } + return entry_point; +} + +void shader_compiler::throw_error(const std::string_view context) { + throw std::runtime_error(std::string(context)); +} +shader_compiler::slang_unique_ptr +shader_compiler::create_composed_program(slang::IModule *module, + slang::IEntryPoint *entry_point) { + slang::IBlob *diagnostic_blob_ptr = nullptr; + + auto component_types = std::vector(); + component_types.push_back(module); + component_types.push_back(entry_point); + + slang::IComponentType *composed_program = nullptr; + + if (session->createCompositeComponentType( + component_types.data(), component_types.size(), &composed_program, + &diagnostic_blob_ptr) != SLANG_OK) { + throw_error("creating composed program", diagnostic_blob_ptr); + } + + return slang_unique_ptr(composed_program, + destroy_slang); +} +shader_compiler::slang_unique_ptr +shader_compiler::compile_to_spirv(slang::IComponentType *composed_program) { + slang::IBlob *diagnostic_blob_ptr = nullptr; + + slang::IBlob *spirv_blob = nullptr; + + composed_program->getEntryPointCode(0, 0, &spirv_blob, &diagnostic_blob_ptr); + if (!spirv_blob) { + throw_error("compiling to spirv", diagnostic_blob_ptr); + } + + return slang_unique_ptr(spirv_blob, destroy_slang); +} +void shader_compiler::reload_session() { + auto paths_as_c_strings = std::vector(); + for (const auto &path : search_paths) { + paths_as_c_strings.push_back(path.generic_string().c_str()); + } + + auto profile = global_session->findProfile("spirv_1_4"); + + auto target = slang::TargetDesc{ + .format = SlangCompileTarget::SLANG_SPIRV, + .profile = profile, + .flags = 0, + }; + + auto optimisation_level = slang::CompilerOptionEntry{ + .name = slang::CompilerOptionName::Optimization, + .value = { + .intValue0 = SlangOptimizationLevel::SLANG_OPTIMIZATION_LEVEL_NONE, + }}; + auto debug_info = slang::CompilerOptionEntry{ + .name = slang::CompilerOptionName::DebugInformation, + .value = { + .intValue0 = SLANG_DEBUG_INFO_LEVEL_MAXIMAL, + }}; + auto line_directive = slang::CompilerOptionEntry{ + .name = slang::CompilerOptionName::LineDirectiveMode, + .value = { + .intValue0 = SLANG_LINE_DIRECTIVE_MODE_SOURCE_MAP, + }}; + auto name_main = slang::CompilerOptionEntry{ + .name = slang::CompilerOptionName::VulkanUseEntryPointName, + .value = { + .intValue0 = 1, + }}; + + auto infos = + std::array{optimisation_level, debug_info, line_directive, name_main}; + + auto slang_session_descriptor = slang::SessionDesc{}; + slang_session_descriptor.searchPaths = paths_as_c_strings.data(); + slang_session_descriptor.searchPathCount = + static_cast(paths_as_c_strings.size()); + slang_session_descriptor.targets = ⌖ + slang_session_descriptor.targetCount = 1; + slang_session_descriptor.defaultMatrixLayoutMode = + SLANG_MATRIX_LAYOUT_COLUMN_MAJOR; + slang_session_descriptor.compilerOptionEntries = infos.data(); + slang_session_descriptor.compilerOptionEntryCount = + static_cast(infos.size()); + + slang::ISession *session_ptr = nullptr; + global_session->createSession(slang_session_descriptor, &session_ptr); + session = slang_unique_ptr(session_ptr, destroy_slang); +} + +} // namespace rog::render diff --git a/source/render/shader_compiler.hpp b/source/render/shader_compiler.hpp new file mode 100644 index 0000000..3b8fb68 --- /dev/null +++ b/source/render/shader_compiler.hpp @@ -0,0 +1,56 @@ +#ifndef SHADER_COMPILER_HPP +#define SHADER_COMPILER_HPP + +#include +#include +#include + +#include + +namespace rog::render { + +class shader_compiler { +public: + explicit shader_compiler(std::filesystem::path root); + + [[nodiscard]] std::vector + compile(const std::string_view module_name, + const std::string_view entry_name = "main"); + + void reload_session(); + +private: + static void destroy_slang(ISlangUnknown *object); + template + using slang_unique_ptr = std::unique_ptr; + + std::filesystem::path root; + + slang_unique_ptr global_session = {nullptr, + destroy_slang}; + slang_unique_ptr session = {nullptr, destroy_slang}; + + std::vector search_paths = {"assets/shaders"}; + + [[nodiscard]] slang::IModule *load_module(const std::string_view module_name); + + [[nodiscard]] slang::IEntryPoint * + find_entry_point(slang::IModule *module, + const std::string_view entry_point_name); + + [[nodiscard]] slang_unique_ptr + create_composed_program(slang::IModule *module, + slang::IEntryPoint *entry_point); + + [[nodiscard]] slang_unique_ptr + compile_to_spirv(slang::IComponentType *composed_program); + + [[noreturn]] void throw_error(const std::string_view context, + slang::IBlob *diagnostic_blob); + + [[noreturn]] void throw_error(const std::string_view context); +}; + +} // namespace rog::render + +#endif diff --git a/source/rogue.cpp b/source/rogue.cpp index dae0940..6a84ccd 100644 --- a/source/rogue.cpp +++ b/source/rogue.cpp @@ -1,23 +1,140 @@ #include "rogue.hpp" +#include "vuk/ImageAttachment.hpp" +#include "vuk/RenderGraph.hpp" +#include "vuk/Types.hpp" +#include "vuk/Value.hpp" +#include "vuk/runtime/CommandBuffer.hpp" +#include "vuk/runtime/vk/Allocator.hpp" +#include "vuk/runtime/vk/DeviceFrameResource.hpp" +#include "vuk/runtime/vk/Image.hpp" +#include "vuk/runtime/vk/VkRuntime.hpp" +#include "vuk/vsl/Core.hpp" +#include +#include +#include +#include +#include + +[[nodiscard]] auto composite() { + return vuk::make_pass("composite", [](vuk::CommandBuffer &command_buffer, + VUK_IA(vuk::eColorWrite) target) { + command_buffer.bind_graphics_pipeline("test") + .set_dynamic_state(vuk::DynamicStateFlagBits::eScissor | + vuk::DynamicStateFlagBits::eViewport) + .set_viewport(0, vuk::Rect2D::framebuffer()) + .set_scissor(0, vuk::Rect2D::framebuffer()) + .set_rasterization({}) + .set_depth_stencil({ + .depthTestEnable = false, + .depthWriteEnable = false, + .depthCompareOp = vuk::CompareOp::eLess, + }) + .set_color_blend(target, {}) + .draw(3, 1, 0, 0); + + return std::make_tuple(std::move(target)); + }); +} + namespace rog { rogue::rogue(logger *log) : log(log), display(&user_input, {1920, 1080}, "Rogue PT"), - context(&display) { + context(&display), shader_compiler("../assets/shaders") { + create_pipelines(); if (!context.rtx_supported) { log->warn("rtx is not supported on {}. inline ray tracing and ray tracing " "pipeline will not be available", context.gpu_name); } + IMGUI_CHECKVERSION(); + ImGui::CreateContext(); + + ImGui_ImplGlfw_InitForOther(display.raw(), true); + imgui_data = vuk::extra::ImGui_ImplVuk_Init(context.superframe_allocator); } void rogue::stop() { close_requested = true; } +void rogue::do_tick() { + auto [frame_allocator, frame_target] = get_next_frame_resources(); + + std::tie(frame_target) = composite()(std::move(frame_target)); + + ImGui_ImplGlfw_NewFrame(); + ImGui::NewFrame(); + ImGui::Begin("Test"); + ImGui::Text("Hello World!"); + ImGui::End(); + ImGui::Render(); + + frame_target = vuk::extra::ImGui_ImplVuk_Render( + frame_allocator, std::move(frame_target), imgui_data); + + auto entire_thing = vuk::enqueue_presentation(frame_target); + entire_thing.submit(frame_allocator, context.compiler); +} + void rogue::run() { while (!close_requested.load() && !display.should_close()) { + do_tick(); + display.poll_events(); } } +vuk::PipelineBaseCreateInfo +rogue::create_compute_pipeline(std::string_view comp_module, + std::string_view comp_entry) { + const auto comp_bytes = shader_compiler.compile(comp_module, comp_entry); + + auto pipeline_info = vuk::PipelineBaseCreateInfo(); + pipeline_info.add_spirv(comp_bytes, comp_module.data(), comp_entry.data()); + + return pipeline_info; +} + +vuk::PipelineBaseCreateInfo rogue::create_graphics_pipeline( + std::string_view vs_module, std::string_view vs_entry, + std::string_view fs_module, std::string_view fs_entry) { + const auto vs_bytes = shader_compiler.compile(vs_module, vs_entry); + const auto fs_bytes = shader_compiler.compile(fs_module, fs_entry); + + auto pipeline_info = vuk::PipelineBaseCreateInfo(); + pipeline_info.add_spirv(vs_bytes, vs_module.data(), vs_entry.data()); + pipeline_info.add_spirv(fs_bytes, fs_module.data(), fs_entry.data()); + return pipeline_info; +} + +vuk::PipelineBaseCreateInfo +rogue::create_graphics_pipeline(std::string_view module_name) { + return create_graphics_pipeline(module_name, "vert_main", module_name, + "frag_main"); +} + +void rogue::create_pipelines() { + log->debug("creating pipelines"); + context.runtime.create_named_pipeline("test", + create_graphics_pipeline("test.slang")); + log->debug("created pipelines"); +} + +vuk::Value rogue::get_swap_target() { + auto imported_swapchain = vuk::acquire_swapchain(context.swapchain); + auto swapchain_image = + vuk::acquire_next_image("swp_img", std::move(imported_swapchain)); + return vuk::clear_image(std::move(swapchain_image), + vuk::ClearColor{0.0f, 0.0f, 1.0f, 1.0f}); +} + +rogue::vuk_frame_resources rogue::get_next_frame_resources() { + auto &frame_resource = context.superframe_resource.get_next_frame(); + context.runtime.next_frame(); + return { + .allocator = vuk::Allocator(frame_resource), + .swap_target = get_swap_target(), + }; +} + } // namespace rog diff --git a/source/rogue.hpp b/source/rogue.hpp index 824c03e..8d6df5a 100644 --- a/source/rogue.hpp +++ b/source/rogue.hpp @@ -4,9 +4,11 @@ #include #include "render/context.hpp" +#include "render/shader_compiler.hpp" #include "ui/display.hpp" #include "ui/user_input.hpp" #include "util/logger.hpp" +#include "vuk/extra/ImGuiIntegration.hpp" namespace rog { @@ -16,9 +18,35 @@ private: ui::user_input user_input = {}; ui::display display; render::context context; + render::shader_compiler shader_compiler; std::atomic close_requested = false; + vuk::extra::ImGuiData imgui_data; + + [[nodiscard]] vuk::PipelineBaseCreateInfo + create_compute_pipeline(std::string_view comp_module, + std::string_view comp_entry); + + [[nodiscard]] vuk::PipelineBaseCreateInfo create_graphics_pipeline( + std::string_view vs_module, std::string_view vs_entry, + std::string_view fs_module, std::string_view fs_entry); + + [[nodiscard]] vuk::PipelineBaseCreateInfo + create_graphics_pipeline(std::string_view module); + + void create_pipelines(); + + [[nodiscard]] vuk::Value get_swap_target(); + + struct vuk_frame_resources { + vuk::Allocator allocator; + vuk::Value swap_target; + }; + [[nodiscard]] vuk_frame_resources get_next_frame_resources(); + + void do_tick(); + public: explicit rogue(logger *log);