diff --git a/flake.nix b/flake.nix index fdf3f61..b89b4eb 100644 --- a/flake.nix +++ b/flake.nix @@ -21,6 +21,8 @@ meson ninja pkg-config + glslang + shaderc ]; buildInputs = with pkgs; [ vulkan-loader diff --git a/meson.build b/meson.build index 98fbf10..88a4cb3 100644 --- a/meson.build +++ b/meson.build @@ -1,7 +1,7 @@ project('vr-compositor', 'cpp', version: '0.1', default_options: [ - 'cpp_std=c++23', + 'cpp_std=c++26', 'warning_level=everything', 'werror=true', ] @@ -28,17 +28,30 @@ vkbootstrap_dep = cc.find_library( required: true, ) +add_project_arguments('-Wpedantic', language : ['c', 'cpp']) add_project_arguments( - '-Wpedantic', - language : ['c', 'cpp'] + [ + '-Wno-c++98-compat', + '-Wno-c++98-compat-pedantic', + '-Wno-covered-switch-default', + '-Wno-undef', + '-Wno-padded', + '-Wno-unsafe-buffer-usage', + '-Wno-c23-extensions', + ], + language : 'cpp' ) +subdir('shaders') + exe = executable('vr-compositor', [ 'src/main.cpp', 'src/Impls.cpp', 'src/Util.cpp', 'src/Logger.cpp', + 'src/DescriptorLayoutBuilder.cpp', + 'src/DescriptorAllocator.cpp', 'src/Application.cpp', ], include_directories: vkbootstrap_inc, @@ -49,5 +62,8 @@ exe = executable('vr-compositor', vkbootstrap_dep, zlib_dep, sdl3_dep, - ] + ], + cpp_args: [ + '--embed-dir=' + join_paths(meson.project_build_root(), 'shaders') + ], ) diff --git a/shaders/gradient.comp b/shaders/gradient.comp new file mode 100644 index 0000000..9477b61 --- /dev/null +++ b/shaders/gradient.comp @@ -0,0 +1,23 @@ +#version 460 + +layout (local_size_x = 16, local_size_y = 16) in; +layout(rgba16f, set = 0, binding = 0) uniform image2D image; + +void main() { + ivec2 texelCoord = ivec2(gl_GlobalInvocationID.xy); + ivec2 size = imageSize(image); + + if (texelCoord.x >= size.x || texelCoord.y >= size.y) + return; + + vec2 uv = (vec2(texelCoord) + 0.5) / vec2(size); + + float v = sin(uv.x * 10.0) + cos(uv.y * 10.0); + + float r = 0.5 + 0.5 * cos(6.2831 * (uv.x + v)); + float g = 0.5 + 0.5 * cos(6.2831 * (uv.y + v + 0.33)); + float b = 0.5 + 0.5 * cos(6.2831 * (uv.x - uv.y + 0.66)); + + vec4 color = vec4(r, g, b, 1.0); + imageStore(image, texelCoord, color); +} diff --git a/shaders/meson.build b/shaders/meson.build new file mode 100644 index 0000000..3fb211f --- /dev/null +++ b/shaders/meson.build @@ -0,0 +1,30 @@ +fs = import('fs') + +glslc = find_program('glslc', required : false) +glslang = find_program('glslangValidator', required : false) + +if glslc.found() + shader_compiler = glslc + shader_compile_cmd = [shader_compiler, '-o', '@OUTPUT@', '@INPUT@'] +elif glslang.found() + shader_compiler = glslang + shader_compile_cmd = [shader_compiler, '-V', '@INPUT@', '-o', '@OUTPUT@'] +else + error('Either glslc or glslangValidator is required to build shaders') +endif + +shader_sources = files( + 'gradient.comp', +) + +spirv_shaders = [] +foreach shader : shader_sources + shader_name = fs.stem(shader) + spirv_shaders += custom_target( + shader_name + '_spv', + input : shader, + output : shader_name + '.spv', + command : shader_compile_cmd, + build_by_default : true, + ) +endforeach diff --git a/src/Application.cpp b/src/Application.cpp index 2df37d9..ae7cdf1 100644 --- a/src/Application.cpp +++ b/src/Application.cpp @@ -13,6 +13,7 @@ #include #include "Util.h" +#include "src/DescriptorLayoutBuilder.h" namespace Lunar { @@ -34,6 +35,8 @@ Application::Application() swapchain_init(); commands_init(); sync_init(); + descriptors_init(); + pipelines_init(); m_logger.info("App init done!"); } @@ -156,7 +159,7 @@ auto Application::swapchain_init() -> void { int w, h; SDL_GetWindowSize(m_window, &w, &h); - create_swapchain(w, h); + create_swapchain(static_cast(w), static_cast(h)); create_draw_image(static_cast(w), static_cast(h)); } @@ -210,12 +213,85 @@ auto Application::sync_init() -> void } } +auto Application::descriptors_init() -> void +{ + std::vector sizes { + { VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 1 }, + }; + m_vk.descriptor_allocator.init_pool(m_vkb.dev, 10, sizes); + + m_vk.draw_image_descriptor_layout + = DescriptorLayoutBuilder() + .add_binding(0, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE) + .build(m_logger, m_vkb.dev, VK_SHADER_STAGE_COMPUTE_BIT); + + m_vk.draw_image_descriptors = m_vk.descriptor_allocator.allocate( + m_logger, m_vkb.dev, m_vk.draw_image_descriptor_layout); + + update_draw_image_descriptor(); + + m_vk.deletion_queue.emplace([&]() { + m_vk.descriptor_allocator.destroy_pool(m_vkb.dev); + vkDestroyDescriptorSetLayout( + m_vkb.dev, m_vk.draw_image_descriptor_layout, nullptr); + }); +} + +auto Application::pipelines_init() -> void { background_pipelines_init(); } + +auto Application::background_pipelines_init() -> void +{ + VkPipelineLayoutCreateInfo layout_ci {}; + layout_ci.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; + layout_ci.pNext = nullptr; + layout_ci.pSetLayouts = &m_vk.draw_image_descriptor_layout; + layout_ci.setLayoutCount = 1; + + VK_CHECK(m_logger, + vkCreatePipelineLayout( + m_vkb.dev, &layout_ci, nullptr, &m_vk.gradient_pipeline_layout)); + + uint8_t compute_draw_shader_data[] { +#embed "gradient.spv" + }; + VkShaderModule compute_draw_shader {}; + if (!vkutil::load_shader_module(std::span(compute_draw_shader_data, + sizeof(compute_draw_shader_data)), + m_vkb.dev, &compute_draw_shader)) { + m_logger.err("Failed to load gradient compute shader"); + } + + VkPipelineShaderStageCreateInfo stage_ci {}; + stage_ci.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; + stage_ci.pNext = nullptr; + stage_ci.stage = VK_SHADER_STAGE_COMPUTE_BIT; + stage_ci.module = compute_draw_shader; + stage_ci.pName = "main"; + + VkComputePipelineCreateInfo compute_pip_ci {}; + compute_pip_ci.sType = VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO; + compute_pip_ci.pNext = nullptr; + compute_pip_ci.layout = m_vk.gradient_pipeline_layout; + compute_pip_ci.stage = stage_ci; + + VK_CHECK(m_logger, + vkCreateComputePipelines(m_vkb.dev, VK_NULL_HANDLE, 1, &compute_pip_ci, + nullptr, &m_vk.gradient_pipeline)); + + vkDestroyShaderModule(m_vkb.dev, compute_draw_shader, nullptr); + m_vk.deletion_queue.emplace([&]() { + vkDestroyPipelineLayout( + m_vkb.dev, m_vk.gradient_pipeline_layout, nullptr); + vkDestroyPipeline(m_vkb.dev, m_vk.gradient_pipeline, nullptr); + }); +} + auto Application::render() -> void { defer(m_vk.frame_number++); - if (m_vk.swapchain == VK_NULL_HANDLE - || m_vk.swapchain_extent.width == 0 || m_vk.swapchain_extent.height == 0) { + if (m_vk.swapchain == VK_NULL_HANDLE || m_vk.swapchain_extent.width == 0 + || m_vk.swapchain_extent.height == 0) { return; } @@ -319,20 +395,14 @@ auto Application::render() -> void auto Application::draw_background(VkCommandBuffer cmd) -> void { - VkClearColorValue clear_value; - float flash { std::abs(std::sin(m_vk.frame_number / 60.f)) }; - clear_value = { { 0x64 / 255.0f * flash, 0x95 / 255.0f * flash, - 0xED / 255.0f * flash, 1.0f } }; - - VkImageSubresourceRange clear_range { - .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, - .baseMipLevel = 0, - .levelCount = 1, - .baseArrayLayer = 0, - .layerCount = 1, - }; - vkCmdClearColorImage(cmd, m_vk.draw_image.image, VK_IMAGE_LAYOUT_GENERAL, - &clear_value, 1, &clear_range); + vkCmdBindPipeline( + cmd, VK_PIPELINE_BIND_POINT_COMPUTE, m_vk.gradient_pipeline); + vkCmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_COMPUTE, + m_vk.gradient_pipeline_layout, 0, 1, &m_vk.draw_image_descriptors, 0, + nullptr); + vkCmdDispatch(cmd, + static_cast(std::ceil(m_vk.draw_extent.width / 16.0)), + static_cast(std::ceil(m_vk.draw_extent.height / 16.0)), 1); } auto Application::create_swapchain(uint32_t width, uint32_t height) -> void @@ -404,6 +474,25 @@ auto Application::create_draw_image(uint32_t width, uint32_t height) -> void m_vkb.dev, &rview_ci, nullptr, &m_vk.draw_image.image_view)); } +auto Application::update_draw_image_descriptor() -> void +{ + // Point the storage image descriptor at the current draw image view + VkDescriptorImageInfo img_info {}; + img_info.imageLayout = VK_IMAGE_LAYOUT_GENERAL; + img_info.imageView = m_vk.draw_image.image_view; + + VkWriteDescriptorSet draw_img_write {}; + draw_img_write.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; + draw_img_write.pNext = nullptr; + draw_img_write.dstBinding = 0; + draw_img_write.dstSet = m_vk.draw_image_descriptors; + draw_img_write.descriptorCount = 1; + draw_img_write.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE; + draw_img_write.pImageInfo = &img_info; + + vkUpdateDescriptorSets(m_vkb.dev, 1, &draw_img_write, 0, nullptr); +} + auto Application::destroy_draw_image() -> void { if (m_vk.draw_image.image_view != VK_NULL_HANDLE) { @@ -435,6 +524,7 @@ auto Application::recreate_swapchain(uint32_t width, uint32_t height) -> void create_swapchain(width, height); create_draw_image(width, height); + update_draw_image_descriptor(); } auto Application::destroy_swapchain() -> void @@ -496,8 +586,8 @@ auto Application::run() -> void } else if (e.type == SDL_EVENT_WINDOW_RESIZED) { int width {}, height {}; SDL_GetWindowSize(m_window, &width, &height); - recreate_swapchain( - static_cast(width), static_cast(height)); + recreate_swapchain(static_cast(width), + static_cast(height)); } } diff --git a/src/Application.h b/src/Application.h index 90ff9c6..6b99dfa 100644 --- a/src/Application.h +++ b/src/Application.h @@ -10,6 +10,7 @@ #include "AllocatedImage.h" #include "DeletionQueue.h" #include "Logger.h" +#include "src/DescriptorAllocator.h" namespace Lunar { @@ -35,12 +36,16 @@ private: auto swapchain_init() -> void; auto commands_init() -> void; auto sync_init() -> void; + auto descriptors_init() -> void; + auto pipelines_init() -> void; + auto background_pipelines_init() -> void; auto draw_background(VkCommandBuffer cmd) -> void; auto render() -> void; auto create_swapchain(uint32_t width, uint32_t height) -> void; auto create_draw_image(uint32_t width, uint32_t height) -> void; + auto update_draw_image_descriptor() -> void; auto destroy_draw_image() -> void; auto recreate_swapchain(uint32_t width, uint32_t height) -> void; auto destroy_swapchain() -> void; @@ -54,11 +59,11 @@ private: struct { VkSwapchainKHR swapchain { VK_NULL_HANDLE }; - VkFormat swapchain_image_format; VkSurfaceKHR surface { nullptr }; + VkFormat swapchain_image_format; - VkQueue graphics_queue { nullptr }; uint32_t graphics_queue_family { 0 }; + VkQueue graphics_queue { nullptr }; std::vector swapchain_images; std::vector swapchain_image_views; @@ -75,6 +80,13 @@ private: VkExtent2D draw_extent {}; VmaAllocator allocator; + DescriptorAllocator descriptor_allocator; + + VkDescriptorSet draw_image_descriptors; + VkDescriptorSetLayout draw_image_descriptor_layout; + + VkPipeline gradient_pipeline {}; + VkPipelineLayout gradient_pipeline_layout {}; DeletionQueue deletion_queue; diff --git a/src/DescriptorAllocator.cpp b/src/DescriptorAllocator.cpp new file mode 100644 index 0000000..88b8122 --- /dev/null +++ b/src/DescriptorAllocator.cpp @@ -0,0 +1,57 @@ +#include "DescriptorAllocator.h" + +#include + +#include "Util.h" + +namespace Lunar { + +auto DescriptorAllocator::init_pool(VkDevice dev, uint32_t max_sets, + std::span pool_ratios) -> void +{ + std::vector pool_sizes; + for (auto const &ratio : pool_ratios) { + pool_sizes.emplace_back(VkDescriptorPoolSize { + .type = ratio.type, + .descriptorCount = static_cast(ratio.ratio * max_sets), + }); + } + + VkDescriptorPoolCreateInfo ci {}; + ci.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO; + ci.pNext = nullptr; + ci.flags = 0; + ci.maxSets = max_sets; + ci.poolSizeCount = static_cast(pool_sizes.size()); + ci.pPoolSizes = pool_sizes.data(); + + vkCreateDescriptorPool(dev, &ci, nullptr, &pool); +} + +auto DescriptorAllocator::clear_descriptors(VkDevice dev) -> void +{ + vkResetDescriptorPool(dev, pool, 0); +} + +auto DescriptorAllocator::destroy_pool(VkDevice dev) -> void +{ + vkDestroyDescriptorPool(dev, pool, nullptr); +} + +auto DescriptorAllocator::allocate(Logger &logger, VkDevice dev, + VkDescriptorSetLayout layout) -> VkDescriptorSet +{ + VkDescriptorSetAllocateInfo ai {}; + ai.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO; + ai.pNext = nullptr; + ai.descriptorPool = pool; + ai.descriptorSetCount = 1; + ai.pSetLayouts = &layout; + + VkDescriptorSet ds; + VK_CHECK(logger, vkAllocateDescriptorSets(dev, &ai, &ds)); + + return ds; +} + +} // namespace Lunar diff --git a/src/DescriptorAllocator.h b/src/DescriptorAllocator.h new file mode 100644 index 0000000..24b4bf8 --- /dev/null +++ b/src/DescriptorAllocator.h @@ -0,0 +1,28 @@ +#pragma once + +#include + +#include + +#include "Logger.h" + +namespace Lunar { + +struct DescriptorAllocator { + struct PoolSizeRatio { + VkDescriptorType type; + float ratio; + }; + + VkDescriptorPool pool; + + auto init_pool(VkDevice dev, uint32_t max_sets, + std::span pool_ratios) -> void; + auto clear_descriptors(VkDevice dev) -> void; + auto destroy_pool(VkDevice dev) -> void; + + auto allocate(Logger &logger, VkDevice dev, VkDescriptorSetLayout layout) + -> VkDescriptorSet; +}; + +} // namespace Lunar diff --git a/src/DescriptorLayoutBuilder.cpp b/src/DescriptorLayoutBuilder.cpp new file mode 100644 index 0000000..7c6403f --- /dev/null +++ b/src/DescriptorLayoutBuilder.cpp @@ -0,0 +1,42 @@ +#include "DescriptorLayoutBuilder.h" + +#include "Util.h" + +namespace Lunar { + +auto DescriptorLayoutBuilder::add_binding( + uint32_t binding, VkDescriptorType type) -> DescriptorLayoutBuilder & +{ + VkDescriptorSetLayoutBinding b {}; + b.binding = binding; + b.descriptorCount = 1; + b.descriptorType = type; + + bindings.emplace_back(b); + + return *this; +} + +auto DescriptorLayoutBuilder::build(Logger &logger, VkDevice dev, + VkShaderStageFlags shader_stages, void *pNext, + VkDescriptorSetLayoutCreateFlags flags) -> VkDescriptorSetLayout +{ + for (auto &&b : bindings) { + b.stageFlags |= shader_stages; + } + + VkDescriptorSetLayoutCreateInfo ci {}; + ci.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO; + ci.pNext = pNext; + + ci.pBindings = bindings.data(); + ci.bindingCount = static_cast(bindings.size()); + ci.flags = flags; + + VkDescriptorSetLayout set; + VK_CHECK(logger, vkCreateDescriptorSetLayout(dev, &ci, nullptr, &set)); + + return set; +} + +} // namespace Lunar diff --git a/src/DescriptorLayoutBuilder.h b/src/DescriptorLayoutBuilder.h new file mode 100644 index 0000000..37e74d2 --- /dev/null +++ b/src/DescriptorLayoutBuilder.h @@ -0,0 +1,22 @@ +#pragma once + +#include + +#include + +#include "Logger.h" + +namespace Lunar { + +struct DescriptorLayoutBuilder { + std::vector bindings; + + auto add_binding(uint32_t binding, VkDescriptorType type) + -> DescriptorLayoutBuilder &; + auto clear() -> void { bindings.clear(); } + auto build(Logger &logger, VkDevice dev, VkShaderStageFlags shader_stages, + void *pNext = nullptr, VkDescriptorSetLayoutCreateFlags flags = 0) + -> VkDescriptorSetLayout; +}; + +} // namespace Lunar diff --git a/src/Logger.cpp b/src/Logger.cpp index 54d4289..f4255e0 100644 --- a/src/Logger.cpp +++ b/src/Logger.cpp @@ -31,7 +31,7 @@ #define FG_GRAY "\033[90m" #define ANSI_RESET "\033[0m" -std::filesystem::path get_log_path(std::string_view app_name) +static std::filesystem::path get_log_path(std::string_view app_name) { #ifdef _WIN32 PWSTR path = nullptr; @@ -53,7 +53,7 @@ std::filesystem::path get_log_path(std::string_view app_name) } #ifndef __EMSCRIPTEN__ -int compress_file(std::filesystem::path const &input_path, +static int compress_file(std::filesystem::path const &input_path, std::filesystem::path const &output_path) { size_t const chunk_size = 4096; @@ -69,7 +69,7 @@ int compress_file(std::filesystem::path const &input_path, std::vector buffer(chunk_size); while (in) { - in.read(buffer.data(), buffer.size()); + in.read(buffer.data(), static_cast(buffer.size())); std::streamsize bytes = in.gcount(); if (bytes > 0) gzwrite(out, buffer.data(), static_cast(bytes)); @@ -99,12 +99,14 @@ Logger::Logger(std::string_view app_name) if (!file.is_regular_file()) continue; - int v; - if (std::sscanf( - file.path().filename().stem().string().c_str(), "log_%d", &v) - != 1) { + auto name = file.path().filename().stem().string(); + constexpr std::string_view prefix = "log_"; + + if (name.rfind(prefix, 0) != 0) { continue; } + + int v = std::stoi(name.substr(prefix.size())); if (v > max) max = v; @@ -139,7 +141,7 @@ auto Logger::err(std::string_view msg) -> void log(Logger::Level::Error, msg); } -std::string get_current_time_string() +static std::string get_current_time_string() { auto now { std::chrono::system_clock::now() }; auto now_c { std::chrono::system_clock::to_time_t(now) }; diff --git a/src/Util.cpp b/src/Util.cpp index 98c0230..2707d72 100644 --- a/src/Util.cpp +++ b/src/Util.cpp @@ -1,5 +1,7 @@ #include "Util.h" +#include + namespace vkutil { auto transition_image(VkCommandBuffer cmd, VkImage image, @@ -75,6 +77,32 @@ auto copy_image_to_image(VkCommandBuffer cmd, VkImage source, vkCmdBlitImage2(cmd, &blit_info); } +auto load_shader_module(std::span spirv_data, VkDevice device, + VkShaderModule *out_shader_module) -> bool +{ + if (!device || !out_shader_module) + return false; + + if (spirv_data.empty() || (spirv_data.size() % 4) != 0) + return false; + + VkShaderModuleCreateInfo create_info {}; + create_info.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; + create_info.pNext = nullptr; + create_info.flags = 0; + create_info.codeSize = spirv_data.size(); + create_info.pCode = reinterpret_cast(spirv_data.data()); + + VkResult const res = vkCreateShaderModule( + device, &create_info, nullptr, out_shader_module); + if (res != VK_SUCCESS) { + *out_shader_module = VK_NULL_HANDLE; + return false; + } + + return true; +} + } // namespace vkutil namespace vkinit { diff --git a/src/Util.h b/src/Util.h index 2790a6a..2e347e8 100644 --- a/src/Util.h +++ b/src/Util.h @@ -1,16 +1,14 @@ #pragma once -#include +#include #include #include -using namespace std::string_view_literals; - template struct privDefer { F f; - privDefer(F f) - : f(f) + privDefer(F f_) + : f(f_) { } ~privDefer() { f(); } @@ -44,6 +42,8 @@ auto transition_image(VkCommandBuffer cmd, VkImage image, VkImageLayout current_layout, VkImageLayout new_layout) -> void; auto copy_image_to_image(VkCommandBuffer cmd, VkImage source, VkImage destination, VkExtent2D src_size, VkExtent2D dst_size) -> void; +auto load_shader_module(std::span spirv_data, VkDevice device, + VkShaderModule *out_shader_module) -> bool; } // namespace vkutil