diff --git a/.github/workflows/cmake.yml b/.github/workflows/cmake.yml new file mode 100644 index 0000000..38aa20d --- /dev/null +++ b/.github/workflows/cmake.yml @@ -0,0 +1,47 @@ +name: CMake + +on: [push, pull_request] + +env: + # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) + BUILD_TYPE: Release + +jobs: + windows-build: + # The CMake configure and build commands are platform agnostic and should work equally well on Windows or Mac. + # You can convert this to a matrix build if you need cross-platform coverage. + # See: https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix + runs-on: windows-latest + + steps: + - uses: actions/checkout@v3 + with: + submodules: recursive + + - uses: suisei-cn/actions-download-file@v1 + id: glslc-download + name: Download glslc + with: + url: https://storage.googleapis.com/shaderc/artifacts/prod/graphics_shader_compiler/shaderc/windows/continuous_release_2017/386/20220602-094729/install.zip + target: glslc-dir + auto-match: false + + - name: Extract glslc archive + run: Expand-Archive glslc-dir/install.zip glslc-dir/install/bin + + - name: Setup Vulkan SDK + uses: sjcobb2022/setup-vulkan-sdk@c2612401009bbce8002630e838bf91cc67f8b3c3 + with: + vulkan-query-version: 1.2.198.1 + vulkan-components: Vulkan-Headers, Vulkan-Loader + vulkan-use-cache: true + + - name: Configure CMake + # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. + # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type + run: cmake -B ${{github.workspace}}/build -GNinja -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DGLSLC_DIR=glslc-dir/install/bin -DPHOBOS_ENABLE_TEST_APP=OFF + + - name: Build + # Build your program with the given configuration + run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} + diff --git a/README.md b/README.md index 9761d16..d885bc2 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,8 @@ # Phobos Vulkan Renderer +This project is being continued in Rust, [here](https://github.com/NotAPenguin0/phobos-rs) + +[![Build](https://github.com/NotAPenguin0/Phobos/actions/workflows/cmake.yml/badge.svg?branch=master)](https://github.com/NotAPenguin0/Phobos/actions/workflows/cmake.yml) + Phobos is a Vulkan renderer written in C++. It supports custom rendering commands, as well as a fixed pipeline implementing deferred -rendering. This fixed pipeline is aimed at being used for 3D scene viewing/editing. \ No newline at end of file +rendering. This fixed pipeline is aimed at being used for 3D scene viewing/editing. diff --git a/include/phobos/acceleration_structure.hpp b/include/phobos/acceleration_structure.hpp index 000cea6..53a2025 100644 --- a/include/phobos/acceleration_structure.hpp +++ b/include/phobos/acceleration_structure.hpp @@ -67,9 +67,6 @@ struct AccelerationStructureInstance { class AccelerationStructureBuilder { public: static AccelerationStructureBuilder create(ph::Context& ctx); - // Update an acceleration structure. This will keep the BLAS, but clear the TLAS so new - // or different instances can be added. - // static AccelerationStructureBuilder from(ph::Context& ctx, AccelerationStructure to_update) // Add a mesh to the acceleration structure. This mesh will be added to the list of meshes in the bottom-level acceleration structure. // Returns the index of the mesh (used to create instances of it). @@ -77,9 +74,29 @@ class AccelerationStructureBuilder { void add_instance(AccelerationStructureInstance const& instance); + // Remove all instances in the TLAS builder (but keeps the buffer around, so it can be reused). + // Doesn't actually change the acceleration structure. + void clear_instances(); + + // Remove all meshes in the BLAS builder. + // Doesn't actually change the underlying acceleration structure object. + void clear_meshes(); + + // Builds only the BLAS + void build_blas_only(uint32_t thread_index = 0); + + // Builds only the TLAS using the same BLAS structures. + void build_tlas_only(uint32_t thread_index = 0); + // Builds the acceleration structure. If done on a different thread, you must supply the thread index to ensure // queue access is properly synchronized. AccelerationStructure build(uint32_t thread_index = 0); + + // Gets resulting AS. + AccelerationStructure get(); + + ~AccelerationStructureBuilder(); + private: ph::Context& ctx; std::vector meshes; @@ -93,9 +110,9 @@ class AccelerationStructureBuilder { // Query pool containing the compacted BLAS size. VkQueryPool compacted_blas_size_qp{}; - AccelerationStructureBuilder(ph::Context& ctx); + explicit AccelerationStructureBuilder(ph::Context& ctx); - // Creates a single DedicatedAccelerationStructure from a create info. This means a buffer is created and + // Creates a single DedicatedAccelerationStructure from a CreateInfo. This means a buffer is created and // bound to the acceleration structure DedicatedAccelerationStructure create_acceleration_structure(VkAccelerationStructureCreateInfoKHR create_info); diff --git a/include/phobos/attachment.hpp b/include/phobos/attachment.hpp index 4d575f6..4b248ea 100644 --- a/include/phobos/attachment.hpp +++ b/include/phobos/attachment.hpp @@ -1,11 +1,17 @@ #pragma once #include +#include namespace ph { struct Attachment { - ph::ImageView view{ }; + ph::ImageView view {}; + std::optional image = std::nullopt; + + explicit inline operator bool() const { + return view && true; // cast view to bool + } }; } \ No newline at end of file diff --git a/include/phobos/command_buffer.hpp b/include/phobos/command_buffer.hpp index 290c0d1..e63854a 100644 --- a/include/phobos/command_buffer.hpp +++ b/include/phobos/command_buffer.hpp @@ -72,12 +72,16 @@ class CommandBuffer { CommandBuffer& copy_buffer(BufferSlice src, BufferSlice dst); CommandBuffer& copy_buffer_to_image(BufferSlice src, ph::ImageView dst, VkImageLayout layout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL); + CommandBuffer& copy_image_to_buffer(ph::ImageView src, BufferSlice dst, VkImageLayout layout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL); + + CommandBuffer& blit_image(ph::RawImage const& src, VkImageLayout src_layout, ph::RawImage const& dst, VkImageLayout dst_layout, VkImageBlit blit, VkFilter filter = VK_FILTER_LINEAR); #if PHOBOS_ENABLE_RAY_TRACING CommandBuffer& bind_ray_tracing_pipeline(std::string_view name); CommandBuffer& build_acceleration_structure(VkAccelerationStructureBuildGeometryInfoKHR const& info, VkAccelerationStructureBuildRangeInfoKHR const* ranges); CommandBuffer& write_acceleration_structure_properties(VkAccelerationStructureKHR as, VkQueryType query_type, VkQueryPool query_pool, uint32_t index); + CommandBuffer& write_acceleration_structure_properties(std::span handles, VkQueryType query_type, VkQueryPool query_pool, uint32_t first); CommandBuffer& copy_acceleration_structure(VkAccelerationStructureKHR src, VkAccelerationStructureKHR dst, VkCopyAccelerationStructureModeKHR mode); CommandBuffer& compact_acceleration_structure(VkAccelerationStructureKHR src, VkAccelerationStructureKHR dst); diff --git a/include/phobos/context.hpp b/include/phobos/context.hpp index 89fd480..d783a3e 100644 --- a/include/phobos/context.hpp +++ b/include/phobos/context.hpp @@ -1,5 +1,4 @@ #pragma once - #include #include #include @@ -112,6 +111,7 @@ struct PhysicalDevice { std::optional surface = std::nullopt; #if PHOBOS_ENABLE_RAY_TRACING VkPhysicalDeviceRayTracingPipelinePropertiesKHR ray_tracing_properties{}; + VkPhysicalDeviceAccelerationStructurePropertiesKHR accel_structure_properties{}; #endif }; @@ -131,6 +131,11 @@ struct Swapchain { uint32_t image_index = 0; }; +struct WaitSemaphore { + VkSemaphore handle = nullptr; + plib::bit_flag stage_flags = {}; +}; + struct PerThreadContext { ph::ScratchAllocator vbo_allocator; ph::ScratchAllocator ibo_allocator; @@ -237,13 +242,14 @@ namespace impl { class Context { public: - Context(AppSettings settings); + Context(AppSettings const& settings); ~Context(); bool is_headless() const; bool validation_enabled() const; uint32_t thread_count() const; + VkDevice device(); PhysicalDevice const& get_physical_device() const; Queue* get_queue(QueueType type); @@ -267,10 +273,14 @@ class Context { void name_object(VkQueue queue, std::string const& name); void name_object(VkCommandPool pool, std::string const& name); void name_object(ph::CommandBuffer const& cmd_buf, std::string const& name); +#if PHOBOS_ENABLE_RAY_TRACING + void name_object(VkAccelerationStructureKHR as, std::string const& name); +#endif size_t max_frames_in_flight() const; [[nodiscard]] InFlightContext wait_for_frame(); void submit_frame_commands(Queue& queue, CommandBuffer& cmd_buf); + void submit_frame_commands(Queue& queue, CommandBuffer& cmd_buf, std::vector const& wait_semaphores); void present(Queue& queue); // These must be called at the start and end of a thread context. Note that end_thread must be called when all work on the thread is complete, so be sure to add @@ -294,8 +304,10 @@ class Context { VkQueryPool create_query_pool(VkQueryType type, uint32_t count); void destroy_query_pool(VkQueryPool pool); - Attachment* get_attachment(std::string_view name); - void create_attachment(std::string_view name, VkExtent2D size, VkFormat format); + Attachment get_attachment(std::string_view name); + void create_attachment(std::string_view name, VkExtent2D size, VkFormat format, ImageType type); + void create_attachment(std::string_view name, VkExtent2D size, VkFormat format, VkSampleCountFlagBits samples, ImageType type); + void create_attachment(std::string_view name, VkExtent2D size, VkFormat format, VkSampleCountFlagBits samples, uint32_t layers, ImageType type); // Does not actually resize if the new size is identical to the old size. void resize_attachment(std::string_view name, VkExtent2D new_size); bool is_swapchain_attachment(std::string const& name); @@ -323,8 +335,12 @@ class Context { #endif RawImage create_image(ImageType type, VkExtent2D size, VkFormat format, uint32_t mips = 1); + RawImage create_image(ImageType type, VkExtent2D size, VkFormat format, VkSampleCountFlagBits samples, uint32_t mips = 1); + RawImage create_image(ImageType type, VkExtent2D size, VkFormat format, VkSampleCountFlagBits samples, uint32_t mips, uint32_t layers); void destroy_image(RawImage& image); ImageView create_image_view(RawImage const& target, ImageAspect aspect = ImageAspect::Color); + ImageView create_image_view(RawImage const& target, uint32_t mip, ImageAspect aspect = ImageAspect::Color); + ImageView create_image_view(RawImage const& target, uint32_t mip, uint32_t layer, ImageAspect aspect = ImageAspect::Color); void destroy_image_view(ImageView& view); ImageView get_image_view(uint64_t id); @@ -347,7 +363,20 @@ class Context { VkDeviceAddress get_device_address(BufferSlice slice); #if PHOBOS_ENABLE_RAY_TRACING + void destroy_acceleration_structure(VkAccelerationStructureKHR handle); void destroy_acceleration_structure(AccelerationStructure& as); + + struct RTXExtensionFunctions { + PFN_vkCreateAccelerationStructureKHR _vkCreateAccelerationStructureKHR = nullptr; + PFN_vkDestroyAccelerationStructureKHR _vkDestroyAccelerationStructureKHR = nullptr; + PFN_vkGetAccelerationStructureBuildSizesKHR _vkGetAccelerationStructureBuildSizesKHR = nullptr; + PFN_vkCmdBuildAccelerationStructuresKHR _vkCmdBuildAccelerationStructuresKHR = nullptr; + PFN_vkCmdWriteAccelerationStructuresPropertiesKHR _vkCmdWriteAccelerationStructuresPropertiesKHR = nullptr; + PFN_vkCmdCopyAccelerationStructureKHR _vkCmdCopyAccelerationStructureKHR = nullptr; + PFN_vkGetAccelerationStructureDeviceAddressKHR _vkGetAccelerationStructureDeviceAddressKHR = nullptr; + PFN_vkCreateRayTracingPipelinesKHR _vkCreateRayTracingPipelinesKHR = nullptr; + PFN_vkCmdTraceRaysKHR _vkCmdTraceRaysKHR = nullptr; + } rtx_fun; #endif private: @@ -377,8 +406,6 @@ class Context { #endif friend class impl::CacheImpl; - VkDevice device(); - VkFramebuffer get_or_create(VkFramebufferCreateInfo const& info, std::string const& name = ""); VkRenderPass get_or_create(VkRenderPassCreateInfo const& info, std::string const& name = ""); VkDescriptorSetLayout get_or_create(DescriptorSetLayoutCreateInfo const& dslci); @@ -388,27 +415,13 @@ class Context { #if PHOBOS_ENABLE_RAY_TRACING Pipeline get_or_create_ray_tracing_pipeline(std::string_view name); #endif - VkDescriptorSet get_or_create(DescriptorSetBinding set_binding, Pipeline const& pipeline, void* pNext = nullptr); + VkDescriptorSet get_or_create(DescriptorSetBinding const& set_binding, Pipeline const& pipeline, void* pNext = nullptr); ShaderMeta const& get_shader_meta(std::string_view pipeline_name); ShaderMeta const& get_compute_shader_meta(std::string_view pipeline_name); #if PHOBOS_ENABLE_RAY_TRACING ShaderMeta const& get_ray_tracing_shader_meta(std::string_view pipeline_name); #endif - -#if PHOBOS_ENABLE_RAY_TRACING - struct RTXExtensionFunctions { - PFN_vkCreateAccelerationStructureKHR _vkCreateAccelerationStructureKHR = nullptr; - PFN_vkDestroyAccelerationStructureKHR _vkDestroyAccelerationStructureKHR = nullptr; - PFN_vkGetAccelerationStructureBuildSizesKHR _vkGetAccelerationStructureBuildSizesKHR = nullptr; - PFN_vkCmdBuildAccelerationStructuresKHR _vkCmdBuildAccelerationStructuresKHR = nullptr; - PFN_vkCmdWriteAccelerationStructuresPropertiesKHR _vkCmdWriteAccelerationStructuresPropertiesKHR = nullptr; - PFN_vkCmdCopyAccelerationStructureKHR _vkCmdCopyAccelerationStructureKHR = nullptr; - PFN_vkGetAccelerationStructureDeviceAddressKHR _vkGetAccelerationStructureDeviceAddressKHR = nullptr; - PFN_vkCreateRayTracingPipelinesKHR _vkCreateRayTracingPipelinesKHR = nullptr; - PFN_vkCmdTraceRaysKHR _vkCmdTraceRaysKHR = nullptr; - } rtx_fun; -#endif }; } \ No newline at end of file diff --git a/include/phobos/hash.hpp b/include/phobos/hash.hpp index 0570544..54c10be 100644 --- a/include/phobos/hash.hpp +++ b/include/phobos/hash.hpp @@ -199,17 +199,36 @@ struct hash { } }; +#if PHOBOS_ENABLE_RAY_TRACING +template<> +struct hash { + size_t operator()(ph::DescriptorAccelerationStructureInfo const& x) const noexcept { + size_t h = 0; + ph::hash_combine(h, x.structure); + return h; + } +}; +#endif + template<> struct hash { size_t operator()(ph::DescriptorBinding const& x) const noexcept { size_t h = 0; ph::hash_combine(h, x.binding, x.type); for (auto const& d : x.descriptors) { - if (x.type == VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER + if (x.type == VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER || x.type == VK_DESCRIPTOR_TYPE_STORAGE_IMAGE || x.type == VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE) { ph::hash_combine(h, d.image); } - else { ph::hash_combine(h, d.buffer); } + else if (x.type == VK_DESCRIPTOR_TYPE_STORAGE_BUFFER || x.type == VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER + || x.type == VK_DESCRIPTOR_TYPE_STORAGE_BUFFER_DYNAMIC || x.type == VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC) { + ph::hash_combine(h, d.buffer); + } +#if PHOBOS_ENABLE_RAY_TRACING + else if (x.type == VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_KHR) { + ph::hash_combine(h, d.accel_structure); + } +#endif } return h; } diff --git a/include/phobos/image.hpp b/include/phobos/image.hpp index 87a2998..fe31d4c 100644 --- a/include/phobos/image.hpp +++ b/include/phobos/image.hpp @@ -54,6 +54,10 @@ struct ImageView { inline bool operator==(ImageView const& rhs) const { return id == rhs.id; } + + inline explicit operator bool() const { + return id != static_cast(-1) && image != nullptr && handle != nullptr; + } }; // Common utilities diff --git a/include/phobos/impl/attachment.hpp b/include/phobos/impl/attachment.hpp index 3d10435..278a879 100644 --- a/include/phobos/impl/attachment.hpp +++ b/include/phobos/impl/attachment.hpp @@ -12,8 +12,10 @@ class AttachmentImpl { // PUBLIC API FUNCTIONS - Attachment* get_attachment(std::string_view name); - void create_attachment(std::string_view name, VkExtent2D size, VkFormat format); + Attachment get_attachment(std::string_view name); + void create_attachment(std::string_view name, VkExtent2D size, VkFormat format, ImageType type); + void create_attachment(std::string_view name, VkExtent2D size, VkFormat format, VkSampleCountFlagBits samples, ImageType type); + void create_attachment(std::string_view name, VkExtent2D size, VkFormat format, VkSampleCountFlagBits samples, uint32_t layers, ImageType type); void resize_attachment(std::string_view name, VkExtent2D new_size); bool is_swapchain_attachment(std::string const& name); bool is_attachment(ImageView view); @@ -31,8 +33,9 @@ class AttachmentImpl { static inline std::string swapchain_attachment_name = "swapchain"; + // Note that the only difference between this and Attachment is the const-ness of the image handle. struct InternalAttachment { - Attachment attachment; + ph::ImageView view; std::optional image; }; std::unordered_map attachments{}; diff --git a/include/phobos/impl/cache.hpp b/include/phobos/impl/cache.hpp index e0c2a3d..50be8a3 100644 --- a/include/phobos/impl/cache.hpp +++ b/include/phobos/impl/cache.hpp @@ -19,7 +19,7 @@ class CacheImpl { #if PHOBOS_ENABLE_RAY_TRACING Pipeline get_or_create_ray_tracing_pipeline(ph::RayTracingPipelineCreateInfo& pci); #endif - VkDescriptorSet get_or_create_descriptor_set(DescriptorSetBinding set_binding, Pipeline const& pipeline, void* pNext = nullptr); + VkDescriptorSet get_or_create_descriptor_set(DescriptorSetBinding const& set_binding, Pipeline const& pipeline, void* pNext = nullptr); void next_frame(); diff --git a/include/phobos/impl/context.hpp b/include/phobos/impl/context.hpp index 234b7c2..bd843a9 100644 --- a/include/phobos/impl/context.hpp +++ b/include/phobos/impl/context.hpp @@ -11,7 +11,7 @@ class ImageImpl; class ContextImpl { public: - ContextImpl(AppSettings settings); + ContextImpl(AppSettings const& settings); ~ContextImpl(); bool is_headless() const; @@ -39,6 +39,9 @@ class ContextImpl { void name_object(VkQueue queue, std::string const& name); void name_object(VkCommandPool pool, std::string const& name); void name_object(ph::CommandBuffer const& cmd_buf, std::string const& name); +#if PHOBOS_ENABLE_RAY_TRACING + void name_object(VkAccelerationStructureKHR as, std::string const& name); +#endif VkFence create_fence(); VkResult wait_for_fence(VkFence fence, uint64_t timeout); diff --git a/include/phobos/impl/frame.hpp b/include/phobos/impl/frame.hpp index b9e0d06..3083836 100644 --- a/include/phobos/impl/frame.hpp +++ b/include/phobos/impl/frame.hpp @@ -15,7 +15,7 @@ class FrameImpl { uint32_t max_frames_in_flight() const; [[nodiscard]] InFlightContext wait_for_frame(); - void submit_frame_commands(Queue& queue, CommandBuffer& cmd_buf); + void submit_frame_commands(Queue& queue, CommandBuffer& cmd_buf, std::vector const& wait_semaphores); void present(Queue& queue); void next_frame(); diff --git a/include/phobos/impl/image.hpp b/include/phobos/impl/image.hpp index 2800801..765054d 100644 --- a/include/phobos/impl/image.hpp +++ b/include/phobos/impl/image.hpp @@ -15,9 +15,13 @@ class ImageImpl { // PUBLIC API RawImage create_image(ImageType type, VkExtent2D size, VkFormat format, uint32_t mips = 1); + RawImage create_image(ImageType type, VkExtent2D size, VkFormat format, VkSampleCountFlagBits samples, uint32_t mips = 1); + RawImage create_image(ImageType type, VkExtent2D size, VkFormat format, VkSampleCountFlagBits samples, uint32_t mips, uint32_t layers); void destroy_image(RawImage& image); ImageView create_image_view(RawImage const& target, ImageAspect aspect = ImageAspect::Color); + ImageView create_image_view(RawImage const& target, uint32_t mip, ImageAspect aspect = ImageAspect::Color); + ImageView create_image_view(RawImage const& target, uint32_t mip, uint32_t layer, ImageAspect aspect = ImageAspect::Color); void destroy_image_view(ImageView& view); ImageView get_image_view(uint64_t id); diff --git a/include/phobos/pass.hpp b/include/phobos/pass.hpp index 269aef4..f482b06 100644 --- a/include/phobos/pass.hpp +++ b/include/phobos/pass.hpp @@ -56,9 +56,12 @@ struct ResourceUsage { plib::bit_flag access{}; struct Attachment { - std::string name = ""; + std::string name; LoadOp load_op = LoadOp::DontCare; ClearValue clear{ .color{} }; + // If no view is set when declaring resources, this will be assumed to be get_attachment(name)->view instead. + // Otherwise, this may be set to a view to the attachment with for example a specific array layer. + ImageView view {}; }; struct Image { @@ -79,7 +82,7 @@ struct Pass { // Resource usage data std::vector resources{}; // Name of this pass - std::string name = ""; + std::string name; // Execution callback std::function execute{}; bool no_renderpass = false; @@ -101,10 +104,17 @@ class PassBuilder { // Adds an attachment to render to in this render pass. PassBuilder& add_attachment(std::string_view name, LoadOp load_op, ClearValue clear = { .color {} }); + // Add an attachment to render to, but specify your own ImageView. + PassBuilder& add_attachment(std::string_view name, ImageView view, LoadOp load_op, ClearValue clear = {.color {}}); // Adds a depth attachment to render to in this render pass. PassBuilder& add_depth_attachment(std::string_view name, LoadOp load_op, ClearValue clear = { .color {} }); + // Add a depth attachment to render to, but specify your own ImageView + PassBuilder& add_depth_attachment(std::string_view name, ImageView view, LoadOp load_op, ClearValue clear = {.color {}}); // If you sample from an attachment that was rendered to in a previous pass, you must call this function to properly synchronize access and transition the image layout. PassBuilder& sample_attachment(std::string_view name, plib::bit_flag stage); + // If you sample from an attachment that was rendered to in a previous pass, you must call this function to properly synchronize access and transition the image layout. + // This overload allows you to select a layer in the image + PassBuilder& sample_attachment(std::string_view name, ImageView view, plib::bit_flag stage); // If you read from a buffer that was written to in an earlier pass, you must call this function to synchronize access automatically. PassBuilder& shader_read_buffer(BufferSlice slice, plib::bit_flag stage); // If you write to a buffer that will be read from in a later pass, you must call this function to synchronize access automatically. diff --git a/include/phobos/pipeline.hpp b/include/phobos/pipeline.hpp index 038221e..2658849 100644 --- a/include/phobos/pipeline.hpp +++ b/include/phobos/pipeline.hpp @@ -25,6 +25,7 @@ struct AccelerationStructure; class Context; enum class PipelineStage { + AllCommands = VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, TopOfPipe = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, Transfer = VK_PIPELINE_STAGE_TRANSFER_BIT, VertexShader = VK_PIPELINE_STAGE_VERTEX_SHADER_BIT, @@ -81,8 +82,15 @@ struct DescriptorAccelerationStructureInfo { #endif struct DescriptorBinding { + DescriptorBinding() = default; + DescriptorBinding(DescriptorBinding const&) = default; + DescriptorBinding(DescriptorBinding&&) = default; + + DescriptorBinding& operator=(DescriptorBinding const&) = default; + DescriptorBinding& operator=(DescriptorBinding&&) = default; + uint32_t binding = 0; - VkDescriptorType type; + VkDescriptorType type{}; struct DescriptorContents { DescriptorBufferInfo buffer{}; @@ -102,6 +110,11 @@ struct DescriptorSetLayoutCreateInfo { }; struct DescriptorSetBinding { + DescriptorSetBinding() = default; + DescriptorSetBinding(DescriptorSetBinding const&) = default; + + DescriptorSetBinding& operator=(DescriptorSetBinding const&) = default; + std::vector bindings; VkDescriptorPool pool = nullptr; private: @@ -246,7 +259,7 @@ struct RayTracingPipelineCreateInfo { struct ShaderBindingTable { ph::RawBuffer buffer; - // Offset of the ray generation groups in elemetns + // Offset of the ray generation groups in elements uint32_t raygen_offset = 0; // Amount of ray generation groups. uint32_t raygen_count = 0; @@ -298,9 +311,10 @@ class DescriptorBuilder { DescriptorBuilder& add_storage_buffer(std::string_view binding, BufferSlice buffer); #if PHOBOS_ENABLE_RAY_TRACING - DescriptorBuilder& add_acceleration_structure(uint32_t binding, AccelerationStructure const& as); - DescriptorBuilder& add_acceleration_structure(ShaderMeta::Binding const& binding, AccelerationStructure const& as); DescriptorBuilder& add_acceleration_structure(std::string_view binding, AccelerationStructure const& as); + DescriptorBuilder& add_acceleration_structure(uint32_t binding, VkAccelerationStructureKHR const& as); + DescriptorBuilder& add_acceleration_structure(ShaderMeta::Binding const& binding, VkAccelerationStructureKHR const& as); + DescriptorBuilder& add_acceleration_structure(std::string_view binding, VkAccelerationStructureKHR as); #endif DescriptorBuilder& add_pNext(void* p); @@ -325,11 +339,14 @@ class PipelineBuilder { PipelineBuilder& set_depth_test(bool test); PipelineBuilder& set_depth_write(bool write); PipelineBuilder& set_depth_op(VkCompareOp op); + PipelineBuilder& set_depth_clamp(bool clamp); PipelineBuilder& add_dynamic_state(VkDynamicState state); PipelineBuilder& set_polygon_mode(VkPolygonMode mode); PipelineBuilder& set_cull_mode(VkCullModeFlags mode); PipelineBuilder& set_front_face(VkFrontFace face); PipelineBuilder& set_samples(VkSampleCountFlagBits samples); + // enables sample shading and sets the min ratio + PipelineBuilder& set_sample_shading(float value); PipelineBuilder& add_blend_attachment(bool enable = false, VkBlendFactor src_color_factor = VK_BLEND_FACTOR_ONE, VkBlendFactor dst_color_factor = VK_BLEND_FACTOR_ONE, VkBlendOp color_op = VK_BLEND_OP_ADD, VkBlendFactor src_alpha_factor = VK_BLEND_FACTOR_ONE, VkBlendFactor dst_alpha_factor = VK_BLEND_FACTOR_ONE, VkBlendOp alpha_op = VK_BLEND_OP_ADD, diff --git a/include/phobos/render_graph.hpp b/include/phobos/render_graph.hpp index da77c2d..be25f7e 100644 --- a/include/phobos/render_graph.hpp +++ b/include/phobos/render_graph.hpp @@ -53,12 +53,14 @@ class RenderGraph { }; - std::pair find_previous_usage(Context& ctx, Pass* current_pass, Attachment* attachment); - std::pair find_next_usage(Context& ctx, Pass* current_pass, Attachment* attachment); + // Compares by name, since this is used for layout transitions. + std::pair find_previous_usage(Context& ctx, Pass* current_pass, std::string_view attachment); + std::pair find_next_usage(Context& ctx, Pass* current_pass, std::string_view attachment); std::pair find_previous_usage(Pass* current_pass, BufferSlice const* buffer); std::pair find_next_usage(Pass* current_pass, BufferSlice const* buffer); + // Compares by ImageView, since this is used for barriers. std::pair find_previous_usage(Context& ctx, Pass* current_pass, ImageView const* image); std::pair find_next_usage(Context& ctx, Pass* current_pass, ImageView const* image); diff --git a/src/acceleration_structure.cpp b/src/acceleration_structure.cpp index b3bd975..4657da3 100644 --- a/src/acceleration_structure.cpp +++ b/src/acceleration_structure.cpp @@ -25,6 +25,41 @@ void AccelerationStructureBuilder::add_instance(AccelerationStructureInstance co instances.push_back(instance); } +void AccelerationStructureBuilder::clear_instances() { + instances.clear(); +} + +void AccelerationStructureBuilder::clear_meshes() { + meshes.clear(); +} + +void AccelerationStructureBuilder::build_blas_only(uint32_t thread_index) { + fence = ctx.create_fence(); + + // Delete old BLAS. TODO: Check if this doesn't cause free-during-use errors. + for (auto& blas : build_result.bottom_level) { + PH_RTX_CALL(vkDestroyAccelerationStructureKHR, ctx.device(), blas.handle, nullptr); + ctx.destroy_buffer(blas.buffer); + } + build_result.bottom_level.clear(); + + create_bottom_level(thread_index); + ctx.destroy_fence(fence); +} + +void AccelerationStructureBuilder::build_tlas_only(uint32_t thread_index) { + fence = ctx.create_fence(); + + // Delete old TLAS. // TODO: Make sure this doesn't cause free-during-use. + + PH_RTX_CALL(vkDestroyAccelerationStructureKHR, ctx.device(), build_result.top_level.handle, nullptr); + ctx.destroy_buffer(build_result.top_level.buffer); + // We don't destroy the instance buffer because we might be able to re-use it. + + create_top_level(thread_index); + ctx.destroy_fence(fence); +} + AccelerationStructure AccelerationStructureBuilder::build(uint32_t thread_index) { // Initialize reusable objects. fence = ctx.create_fence(); @@ -37,6 +72,14 @@ AccelerationStructure AccelerationStructureBuilder::build(uint32_t thread_index) return build_result; } +AccelerationStructure AccelerationStructureBuilder::get() { + return build_result; +} + +AccelerationStructureBuilder::~AccelerationStructureBuilder() { + +} + DedicatedAccelerationStructure AccelerationStructureBuilder::create_acceleration_structure(VkAccelerationStructureCreateInfoKHR create_info) { DedicatedAccelerationStructure result{}; @@ -167,7 +210,7 @@ void AccelerationStructureBuilder::build_blas(uint32_t thread_index, VkDeviceSiz // Build acceleration structures std::vector cmd_bufs; - ph::Queue& queue = *ctx.get_queue(ph::QueueType::Graphics); + ph::Queue& queue = *ctx.get_queue(ph::QueueType::Compute); for (uint32_t i = 0; i < num_blas; ++i) { // Create command buffer and store it. cmd_bufs.push_back(queue.begin_single_time(thread_index)); @@ -180,7 +223,6 @@ void AccelerationStructureBuilder::build_blas(uint32_t thread_index, VkDeviceSiz submit_blas_build(thread_index, queue, cmd_bufs); - // Free resources ctx.destroy_buffer(scratch_buffer); } @@ -200,7 +242,7 @@ void AccelerationStructureBuilder::compact_blas(uint32_t thread_index, std::vect // Old acceleration structures to destroy when done std::vector old_as{ num_blas }; - ph::Queue& queue = *ctx.get_queue(ph::QueueType::Graphics); + ph::Queue& queue = *ctx.get_queue(ph::QueueType::Compute); // We can do compaction with a single command buffer; ph::CommandBuffer cmd_buf = queue.begin_single_time(thread_index); for (uint32_t i = 0; i < num_blas; ++i) { @@ -266,13 +308,13 @@ void AccelerationStructureBuilder::create_bottom_level(uint32_t thread_index) { // Calculate size info and create BLAS. max_scratch_needed = std::max(max_scratch_needed, calculate_size_info(build_info, entry)); DedicatedAccelerationStructure blas = create_blas(build_info); - // Now we know the dstAccelerationStructure field for VkAccelerationStructureBUildGeometryInfoKHR. + // Now we know the dstAccelerationStructure field for VkAccelerationStructureBuildGeometryInfoKHR. build_infos[i].geometry.dstAccelerationStructure = blas.handle; // Save created BLAS. build_result.bottom_level[i] = blas; } - // We created the acceleration structures, now we'll build them by lauching a command buffer for every + // We created the acceleration structures, now we'll build them by launching a command buffer for every // BLAS we create. build_blas(thread_index, max_scratch_needed, build_infos, entries); // Compact the final BLAS @@ -304,7 +346,14 @@ ph::RawBuffer AccelerationStructureBuilder::create_instance_buffer(uint32_t thre uint32_t const num_instances = instance_data.size(); VkDeviceSize const size = sizeof(VkAccelerationStructureInstanceKHR) * num_instances; - ph::RawBuffer instance_buffer = ctx.create_buffer(ph::BufferType::AccelerationStructureInstance, size); + ph::RawBuffer instance_buffer; + // Only recreate if it's too small + if (size > build_result.instance_buffer.size) { + ctx.destroy_buffer(build_result.instance_buffer); + instance_buffer = ctx.create_buffer(ph::BufferType::AccelerationStructureInstance, size); + } else { + instance_buffer = build_result.instance_buffer; + } ph::RawBuffer scratch = ctx.create_buffer(ph::BufferType::TransferBuffer, size); std::byte* memory = ctx.map_memory(scratch); @@ -313,7 +362,7 @@ ph::RawBuffer AccelerationStructureBuilder::create_instance_buffer(uint32_t thre ph::Queue& queue = *ctx.get_queue(ph::QueueType::Transfer); ph::CommandBuffer cmd = queue.begin_single_time(thread_index); - cmd.copy_buffer(scratch, instance_buffer); + cmd.copy_buffer(scratch, instance_buffer.slice(0, size)); queue.end_single_time(cmd, fence); ctx.wait_for_fence(fence); @@ -350,7 +399,6 @@ void AccelerationStructureBuilder::create_top_level(uint32_t thread_index) { instances_vk.arrayOfPointers = false; instances_vk.data.deviceAddress = ctx.get_device_address(build_result.instance_buffer); - VkAccelerationStructureGeometryKHR geometry_data{}; tlas_instances.instances.sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_GEOMETRY_KHR; tlas_instances.instances.geometryType = VK_GEOMETRY_TYPE_INSTANCES_KHR; tlas_instances.instances.geometry.instances = instances_vk; @@ -381,7 +429,7 @@ void AccelerationStructureBuilder::create_top_level(uint32_t thread_index) { ph::RawBuffer scratch = ctx.create_buffer(ph::BufferType::AccelerationStructureScratch, scratch_space); VkDeviceAddress scratch_address = ctx.get_device_address(scratch); - ph::Queue& queue = *ctx.get_queue(ph::QueueType::Graphics); + ph::Queue& queue = *ctx.get_queue(ph::QueueType::Compute); ph::CommandBuffer cmd = queue.begin_single_time(thread_index); build_info.geometry.dstAccelerationStructure = build_result.top_level.handle; diff --git a/src/command_buffer.cpp b/src/command_buffer.cpp index 29c63a0..1fe1142 100644 --- a/src/command_buffer.cpp +++ b/src/command_buffer.cpp @@ -282,7 +282,7 @@ CommandBuffer& CommandBuffer::copy_buffer_to_image(BufferSlice src, ph::ImageVie regions[mip] = copy; - VkDeviceSize level_size = format_size(dst.format) * level_width * level_height; + VkDeviceSize level_size = format_size(dst.format) * level_width * level_height * (dst.layer_count - dst.base_layer); offset += level_size; } @@ -291,6 +291,40 @@ CommandBuffer& CommandBuffer::copy_buffer_to_image(BufferSlice src, ph::ImageVie return *this; } +CommandBuffer& CommandBuffer::copy_image_to_buffer(ph::ImageView src, BufferSlice dst, VkImageLayout layout) { + VkDeviceSize offset = dst.offset; + std::vector regions{ src.level_count }; + for (uint32_t mip = src.base_level; mip < src.level_count; ++mip) { + uint32_t const level_width = src.size.width / pow(2, mip); + uint32_t const level_height = src.size.height / pow(2, mip); + VkBufferImageCopy copy{ + .bufferOffset = offset, + .bufferRowLength = 0, + .bufferImageHeight = 0, + .imageSubresource = { + .aspectMask = static_cast(src.aspect), + .mipLevel = mip, + .baseArrayLayer = src.base_layer, + .layerCount = src.layer_count + }, + .imageOffset = {}, + .imageExtent = { level_width, level_height, 1 } + }; + regions[mip] = copy; + VkDeviceSize level_size = format_size(src.format) * level_width * level_height * (src.layer_count - src.base_layer); + offset += level_size; + } + + vkCmdCopyImageToBuffer(cmd_buf, src.image, layout, dst.buffer, regions.size(), regions.data()); + return *this; +} + +CommandBuffer &CommandBuffer::blit_image(const RawImage &src, VkImageLayout src_layout, const RawImage &dst, + VkImageLayout dst_layout, VkImageBlit blit, VkFilter filter) { + vkCmdBlitImage(cmd_buf, src.handle, src_layout, dst.handle, dst_layout, 1, &blit, filter); + return *this; +} + #if PHOBOS_ENABLE_RAY_TRACING CommandBuffer& CommandBuffer::bind_ray_tracing_pipeline(std::string_view name) { @@ -310,6 +344,11 @@ CommandBuffer& CommandBuffer::write_acceleration_structure_properties(VkAccelera return *this; } +CommandBuffer& CommandBuffer::write_acceleration_structure_properties(std::span handles, VkQueryType query_type, VkQueryPool query_pool, uint32_t first) { + PH_RTX_CALL(vkCmdWriteAccelerationStructuresPropertiesKHR, cmd_buf, handles.size(), handles.data(), query_type, query_pool, first); + return *this; +} + CommandBuffer& CommandBuffer::copy_acceleration_structure(VkAccelerationStructureKHR src, VkAccelerationStructureKHR dst, VkCopyAccelerationStructureModeKHR mode) { VkCopyAccelerationStructureInfoKHR info{ .sType = VK_STRUCTURE_TYPE_COPY_ACCELERATION_STRUCTURE_INFO_KHR, @@ -343,6 +382,7 @@ VkCommandBuffer CommandBuffer::handle() const { return cmd_buf; } + } #if PHOBOS_ENABLE_RAY_TRACING diff --git a/src/context.cpp b/src/context.cpp index 8f8a5db..3d854bf 100644 --- a/src/context.cpp +++ b/src/context.cpp @@ -20,7 +20,7 @@ namespace ph { -Context::Context(AppSettings settings) { +Context::Context(AppSettings const& settings) { context_impl = std::make_unique(settings); image_impl = std::make_unique(*context_impl); buffer_impl = std::make_unique(*context_impl); @@ -30,7 +30,9 @@ Context::Context(AppSettings settings) { frame_impl = std::make_unique(*context_impl, *attachment_impl, *cache_impl, settings); } context_impl->post_init(*this, *image_impl, settings); - frame_impl->post_init(*this, settings); + if (!is_headless()) { + frame_impl->post_init(*this, settings); + } pipeline_impl = std::make_unique(*context_impl, *cache_impl, *buffer_impl); #if PHOBOS_ENABLE_RAY_TRACING @@ -138,6 +140,12 @@ void Context::name_object(VkQueue queue, std::string const& name) { context_impl->name_object(queue, name); } +#if PHOBOS_ENABLE_RAY_TRACING +void Context::name_object(VkAccelerationStructureKHR as, std::string const& name) { + context_impl->name_object(as, name); +} +#endif + [[nodiscard]] InThreadContext Context::begin_thread(uint32_t thread_index) { return context_impl->begin_thread(thread_index); } @@ -194,6 +202,8 @@ void Context::destroy_query_pool(VkQueryPool pool) { // FRAME size_t Context::max_frames_in_flight() const { + // If context is headless, there is only one "frame" in flight + if (is_headless()) return 1; return frame_impl->max_frames_in_flight(); } @@ -202,7 +212,11 @@ size_t Context::max_frames_in_flight() const { } void Context::submit_frame_commands(Queue& queue, CommandBuffer& cmd_buf) { - frame_impl->submit_frame_commands(queue, cmd_buf); + submit_frame_commands(queue, cmd_buf, {}); +} + +void Context::submit_frame_commands(Queue& queue, CommandBuffer& cmd_buf, std::vector const& wait_semaphores) { + frame_impl->submit_frame_commands(queue, cmd_buf, wait_semaphores); } void Context::present(Queue& queue) { @@ -211,12 +225,20 @@ void Context::present(Queue& queue) { // ATTACHMENT -Attachment* Context::get_attachment(std::string_view name) { +Attachment Context::get_attachment(std::string_view name) { return attachment_impl->get_attachment(name); } -void Context::create_attachment(std::string_view name, VkExtent2D size, VkFormat format) { - attachment_impl->create_attachment(name, size, format); +void Context::create_attachment(std::string_view name, VkExtent2D size, VkFormat format, ImageType type) { + attachment_impl->create_attachment(name, size, format, type); +} + +void Context::create_attachment(std::string_view name, VkExtent2D size, VkFormat format, VkSampleCountFlagBits samples, ImageType type) { + attachment_impl->create_attachment(name, size, format, samples, type); +} + +void Context::create_attachment(std::string_view name, VkExtent2D size, VkFormat format, VkSampleCountFlagBits samples, uint32_t layers, ImageType type) { + attachment_impl->create_attachment(name, size,format, samples, layers, type); } void Context::resize_attachment(std::string_view name, VkExtent2D new_size) { @@ -315,6 +337,14 @@ RawImage Context::create_image(ImageType type, VkExtent2D size, VkFormat format, return image_impl->create_image(type, size, format, mips); } +RawImage Context::create_image(ImageType type, VkExtent2D size, VkFormat format, VkSampleCountFlagBits samples, uint32_t mips) { + return image_impl->create_image(type, size, format, samples, mips); +} + +RawImage Context::create_image(ImageType type, VkExtent2D size, VkFormat format, VkSampleCountFlagBits samples, uint32_t mips, uint32_t layers) { + return image_impl->create_image(type, size, format, samples, mips, layers); +} + void Context::destroy_image(RawImage& image) { return image_impl->destroy_image(image); } @@ -323,6 +353,14 @@ ImageView Context::create_image_view(RawImage const& target, ImageAspect aspect) return image_impl->create_image_view(target, aspect); } +ImageView Context::create_image_view(RawImage const& target, uint32_t mip, ImageAspect aspect) { + return image_impl->create_image_view(target, mip, aspect); +} + +ImageView Context::create_image_view(RawImage const& target, uint32_t mip, uint32_t layer, ImageAspect aspect) { + return image_impl->create_image_view(target, mip, layer, aspect); +} + void Context::destroy_image_view(ImageView& view) { return image_impl->destroy_image_view(view); } @@ -408,7 +446,7 @@ Pipeline Context::get_or_create_ray_tracing_pipeline(std::string_view name) { #endif -VkDescriptorSet Context::get_or_create(DescriptorSetBinding set_binding, Pipeline const& pipeline, void* pNext) { +VkDescriptorSet Context::get_or_create(DescriptorSetBinding const& set_binding, Pipeline const& pipeline, void* pNext) { return cache_impl->get_or_create_descriptor_set(set_binding, pipeline, pNext); } @@ -416,6 +454,10 @@ VkDescriptorSet Context::get_or_create(DescriptorSetBinding set_binding, Pipelin // RTX +void Context::destroy_acceleration_structure(VkAccelerationStructureKHR handle) { + rtx_fun._vkDestroyAccelerationStructureKHR(device(), handle, nullptr); +} + void Context::destroy_acceleration_structure(AccelerationStructure& as) { for (auto& blas : as.bottom_level) { rtx_fun._vkDestroyAccelerationStructureKHR(device(), blas.handle, nullptr); diff --git a/src/image.cpp b/src/image.cpp index 0c08a0a..55754a6 100644 --- a/src/image.cpp +++ b/src/image.cpp @@ -1,5 +1,7 @@ #include +#include + namespace ph { bool is_depth_format(VkFormat format) { @@ -20,12 +22,19 @@ VkDeviceSize format_size(VkFormat format) { case VK_FORMAT_R8G8B8_UNORM: case VK_FORMAT_R8G8B8_SRGB: return 3; + case VK_FORMAT_R8G8_UNORM: + case VK_FORMAT_R8G8_SRGB: + return 2; + case VK_FORMAT_R8_UNORM: + case VK_FORMAT_R8_SRGB: + return 1; case VK_FORMAT_R32G32B32A32_SFLOAT: return 4 * sizeof(float); case VK_FORMAT_R32G32B32_SFLOAT: return 3 * sizeof(float); + default: - return 0; + assert(false && "Invalid format!"); } } diff --git a/src/impl/attachment.cpp b/src/impl/attachment.cpp index 54c9740..3a277bf 100644 --- a/src/impl/attachment.cpp +++ b/src/impl/attachment.cpp @@ -6,6 +6,8 @@ #include #include +using namespace std::literals::string_literals; + namespace ph { namespace impl { @@ -17,7 +19,7 @@ AttachmentImpl::~AttachmentImpl() { for (auto& [name, attachment] : attachments) { // Don't destroy swapchain attachment reference, as we don't own it and it will be destroyed later if (name != swapchain_attachment_name) { - img->destroy_image_view(attachment.attachment.view); + img->destroy_image_view(attachment.view); if (attachment.image) { img->destroy_image(*attachment.image); } @@ -26,17 +28,17 @@ AttachmentImpl::~AttachmentImpl() { } -Attachment* AttachmentImpl::get_attachment(std::string_view name) { +Attachment AttachmentImpl::get_attachment(std::string_view name) { std::string key{ name }; if (auto it = attachments.find(key); it != attachments.end()) { - return &it->second.attachment; + return { it->second.view, it->second.image }; } - return nullptr; + return {}; } bool AttachmentImpl::is_attachment(ImageView view) { auto it = std::find_if(attachments.begin(), attachments.end(), [view](auto const& att) { - return att.second.attachment.view.id == view.id; + return att.second.view.id == view.id; }); if (it != attachments.end()) return true; return false; @@ -44,29 +46,40 @@ bool AttachmentImpl::is_attachment(ImageView view) { std::string AttachmentImpl::get_attachment_name(ImageView view) { auto it = std::find_if(attachments.begin(), attachments.end(), [view](auto const& att) { - return att.second.attachment.view.id == view.id; + return att.second.view.id == view.id; }); if (it != attachments.end()) return it->first; return ""; } -void AttachmentImpl::create_attachment(std::string_view name, VkExtent2D size, VkFormat format) { - InternalAttachment attachment{}; - // Create image and image view - attachment.image = img->create_image(is_depth_format(format) ? ImageType::DepthStencilAttachment : ImageType::ColorAttachment, size, format); - attachment.attachment.view = img->create_image_view(*attachment.image, is_depth_format(format) ? ImageAspect::Depth : ImageAspect::Color); - attachments[std::string{ name }] = attachment; +void AttachmentImpl::create_attachment(std::string_view name, VkExtent2D size, VkFormat format, ImageType type) { + create_attachment(name, size, format, VK_SAMPLE_COUNT_1_BIT, type); +} + +void AttachmentImpl::create_attachment(std::string_view name, VkExtent2D size, VkFormat format, VkSampleCountFlagBits samples, ImageType type) { + create_attachment(name, size, format, samples, 1, type); +} + +void AttachmentImpl::create_attachment(std::string_view name, VkExtent2D size, VkFormat format, VkSampleCountFlagBits samples, uint32_t layers, ImageType type) { + InternalAttachment attachment{}; + // Create image and image view + attachment.image = img->create_image(type, size, format, samples, 1, layers); + attachment.view = img->create_image_view(*attachment.image, is_depth_format(format) ? ImageAspect::Depth : ImageAspect::Color); + attachments[std::string{ name }] = attachment; + + ctx->name_object(attachment.image->handle, name.data() + " - image"s); + ctx->name_object(attachment.view, name.data() + " - view"s); } void AttachmentImpl::resize_attachment(std::string_view name, VkExtent2D new_size) { - Attachment* att = get_attachment(name); - if (att == nullptr) { + Attachment att = get_attachment(name); + if (!att) { ctx->log(ph::LogSeverity::Error, "Tried to resize nonexistent attachment"); return; } // No resize needed - if (att->view.size.width == new_size.width && att->view.size.height == new_size.height) { + if (att.view.size.width == new_size.width && att.view.size.height == new_size.height) { return; } @@ -75,9 +88,14 @@ void AttachmentImpl::resize_attachment(std::string_view name, VkExtent2D new_siz // Prepare old data for deferred deletion deferred_delete.push_back({ .attachment = data, .frames_left = ctx->max_frames_in_flight + 2 }); // Might be too many frames, but the extra safety doesn't hurt. // Create new attachment - VkFormat format = att->view.format; - data.image = img->create_image(is_depth_format(format) ? ImageType::DepthStencilAttachment : ImageType::ColorAttachment, new_size, format); - data.attachment.view = img->create_image_view(*data.image, is_depth_format(format) ? ImageAspect::Depth : ImageAspect::Color); + VkFormat format = att.view.format; + VkSampleCountFlagBits samples = att.view.samples; + uint32_t layers = att.view.layer_count; + data.image = img->create_image(data.image->type, new_size, format, samples, 1, layers); + data.view = img->create_image_view(*data.image, is_depth_format(format) ? ImageAspect::Depth : ImageAspect::Color); + + ctx->name_object(data.image->handle, name.data() + " - image"s); + ctx->name_object(data.view, name.data() + " - view"s); } bool AttachmentImpl::is_swapchain_attachment(std::string const& name) { @@ -90,14 +108,14 @@ std::string AttachmentImpl::get_swapchain_attachment_name() const { void AttachmentImpl::new_frame(ImageView& swapchain_view) { - attachments[swapchain_attachment_name] = InternalAttachment{ Attachment{.view = swapchain_view }, std::nullopt }; + attachments[swapchain_attachment_name] = InternalAttachment{ .view = swapchain_view , std::nullopt }; // Update deferred deletion list for (auto& deferred : deferred_delete) { deferred.frames_left -= 1; // Lifetime expired, deleting if (deferred.frames_left == 0) { - img->destroy_image_view(deferred.attachment.attachment.view); + img->destroy_image_view(deferred.attachment.view); img->destroy_image(*deferred.attachment.image); } } diff --git a/src/impl/buffer.cpp b/src/impl/buffer.cpp index 900ea44..e763eb2 100644 --- a/src/impl/buffer.cpp +++ b/src/impl/buffer.cpp @@ -40,7 +40,7 @@ static VkBufferUsageFlags get_usage_flags(BufferType buf_type) { case BufferType::AccelerationStructureScratch: return VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT; case BufferType::AccelerationStructureInstance: - return VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT; + return VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_ACCELERATION_STRUCTURE_BUILD_INPUT_READ_ONLY_BIT_KHR; case BufferType::ShaderBindingTable: return VK_BUFFER_USAGE_SHADER_BINDING_TABLE_BIT_KHR | VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT; diff --git a/src/impl/cache.cpp b/src/impl/cache.cpp index 7659071..b287b55 100644 --- a/src/impl/cache.cpp +++ b/src/impl/cache.cpp @@ -389,7 +389,8 @@ Pipeline CacheImpl::get_or_create_ray_tracing_pipeline(ph::RayTracingPipelineCre #endif -VkDescriptorSet CacheImpl::get_or_create_descriptor_set(DescriptorSetBinding set_binding, Pipeline const& pipeline, void* pNext) { +VkDescriptorSet CacheImpl::get_or_create_descriptor_set(DescriptorSetBinding const& sb, Pipeline const& pipeline, void* pNext) { + auto set_binding = sb; set_binding.set_layout = pipeline.layout.set_layout; auto set_opt = this->descriptor_set.current().get(set_binding); if (set_opt) { diff --git a/src/impl/context.cpp b/src/impl/context.cpp index 03b77fc..6db4f81 100644 --- a/src/impl/context.cpp +++ b/src/impl/context.cpp @@ -64,9 +64,13 @@ static PhysicalDevice select_physical_device(VkInstance instance, std::optional< VkPhysicalDeviceRayTracingPipelinePropertiesKHR rtx_properties{ .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_RAY_TRACING_PIPELINE_PROPERTIES_KHR }; + VkPhysicalDeviceAccelerationStructurePropertiesKHR accel_structure_properties{ + .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_ACCELERATION_STRUCTURE_PROPERTIES_KHR, + .pNext = &rtx_properties + }; VkPhysicalDeviceProperties2 properties_2{ .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2, - .pNext = &rtx_properties + .pNext = &accel_structure_properties }; vkGetPhysicalDeviceProperties2(device, &properties_2); #endif @@ -132,7 +136,8 @@ static PhysicalDevice select_physical_device(VkInstance instance, std::optional< .found_queues = std::move(found_queues), .surface = surface, #if PHOBOS_ENABLE_RAY_TRACING - .ray_tracing_properties = rtx_properties + .ray_tracing_properties = rtx_properties, + .accel_structure_properties = accel_structure_properties #endif }; } @@ -211,11 +216,12 @@ static VKAPI_ATTR VkBool32 VKAPI_CALL vk_debug_callback( return VK_FALSE; } -ContextImpl::ContextImpl(AppSettings settings) - : max_unbounded_array_size(settings.max_unbounded_array_size), - max_frames_in_flight(settings.max_frames_in_flight), - num_threads(settings.num_threads), - has_validation(settings.enable_validation) { +ContextImpl::ContextImpl(AppSettings const& s) + : max_unbounded_array_size(s.max_unbounded_array_size), + max_frames_in_flight(s.max_frames_in_flight), + num_threads(s.num_threads), + has_validation(s.enable_validation) { + AppSettings settings = s; if (!settings.create_headless) { wsi = settings.wsi; } @@ -225,6 +231,7 @@ ContextImpl::ContextImpl(AppSettings settings) // If ray tracing is enabled, add the required extensions for it settings.gpu_requirements.device_extensions.push_back(VK_KHR_RAY_TRACING_PIPELINE_EXTENSION_NAME); settings.gpu_requirements.device_extensions.push_back(VK_KHR_ACCELERATION_STRUCTURE_EXTENSION_NAME); + settings.gpu_requirements.device_extensions.push_back(VK_KHR_RAY_QUERY_EXTENSION_NAME); settings.gpu_requirements.device_extensions.push_back(VK_KHR_DEFERRED_HOST_OPERATIONS_EXTENSION_NAME); settings.gpu_requirements.features_1_2.bufferDeviceAddress = true; @@ -238,7 +245,13 @@ ContextImpl::ContextImpl(AppSettings settings) .pNext = nullptr, .accelerationStructure = true }; + VkPhysicalDeviceRayQueryFeaturesKHR ray_query_features{ + .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_RAY_QUERY_FEATURES_KHR, + .pNext = nullptr, + .rayQuery = true + }; rtx_features.pNext = &accel_structure_features; + accel_structure_features.pNext = &ray_query_features; #endif { @@ -642,6 +655,14 @@ void ContextImpl::name_object(ph::CommandBuffer const& cmd_buf, std::string cons } } +#if PHOBOS_ENABLE_RAY_TRACING +void ContextImpl::name_object(VkAccelerationStructureKHR as, std::string const& name) { + if (validation_enabled() && !name.empty()) { + name_object_impl(as, VK_OBJECT_TYPE_ACCELERATION_STRUCTURE_KHR, name, device, set_debug_utils_name_fun); + } +} +#endif + VkFence ContextImpl::create_fence() { VkFenceCreateInfo info{}; info.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO; diff --git a/src/impl/frame.cpp b/src/impl/frame.cpp index 12ceb65..62c1572 100644 --- a/src/impl/frame.cpp +++ b/src/impl/frame.cpp @@ -142,13 +142,32 @@ uint32_t FrameImpl::max_frames_in_flight() const { return InFlightContext(frame_data.cmd_buf, frame_data.vbo_allocator, frame_data.ibo_allocator, frame_data.ubo_allocator, frame_data.ssbo_allocator); } -void FrameImpl::submit_frame_commands(Queue& queue, CommandBuffer& cmd_buf) { +void FrameImpl::submit_frame_commands(Queue& queue, CommandBuffer& cmd_buf, std::vector const& wait_semaphores) { PerFrame& frame_data = per_frame.current(); - // Reset our fence from last time so we can use it again now + // Reset our fence from last time, so we can use it again now vkResetFences(ctx->device, 1, &frame_data.fence); - queue.submit(cmd_buf, frame_data.fence, VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, frame_data.image_ready, frame_data.gpu_finished); + std::vector semaphores { frame_data.image_ready }; + std::vector wait_stages { VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT }; + + // Fill user-supplied semaphores + for (auto const& sem : wait_semaphores) { + semaphores.push_back(sem.handle); + wait_stages.push_back(sem.stage_flags.value()); + } + + VkSubmitInfo info{}; + info.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; + info.commandBufferCount = 1; + VkCommandBuffer cbuf = cmd_buf.handle(); + info.pCommandBuffers = &cbuf; + info.signalSemaphoreCount = 1; + info.pSignalSemaphores = &frame_data.gpu_finished; + info.waitSemaphoreCount = semaphores.size(); + info.pWaitSemaphores = semaphores.data(); + info.pWaitDstStageMask = wait_stages.data(); + queue.submit(info, frame_data.fence); } void FrameImpl::present(Queue& queue) { diff --git a/src/impl/image.cpp b/src/impl/image.cpp index 9a8f37c..1dc0c11 100644 --- a/src/impl/image.cpp +++ b/src/impl/image.cpp @@ -85,28 +85,37 @@ ImageImpl::ImageImpl(ContextImpl& ctx) : ctx(&ctx) { } RawImage ImageImpl::create_image(ImageType type, VkExtent2D size, VkFormat format, uint32_t mips) { + return create_image(type, size, format, VK_SAMPLE_COUNT_1_BIT, mips); +} + +RawImage ImageImpl::create_image(ImageType type, VkExtent2D size, VkFormat format, VkSampleCountFlagBits samples, uint32_t mips) { + if (type == ImageType::Cubemap || type == ImageType::EnvMap) return create_image(type, size, format, samples, mips, 6); + else return create_image(type, size, format, samples, mips, 1); +} + +RawImage ImageImpl::create_image(ImageType type, VkExtent2D size, VkFormat format, VkSampleCountFlagBits samples, uint32_t mips, uint32_t layers) { RawImage image; image.size = size; image.format = format; image.type = type; - image.layers = 1; + image.layers = layers; image.mip_levels = mips; - image.samples = VK_SAMPLE_COUNT_1_BIT; + image.samples = samples; VkImageCreateInfo info{ - .sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO, - .pNext = nullptr, - .flags = get_image_flags(type), - .imageType = VK_IMAGE_TYPE_2D, - .format = format, - .extent = VkExtent3D{size.width, size.height, 1}, - .mipLevels = mips, - .arrayLayers = 1, - .samples = VK_SAMPLE_COUNT_1_BIT, - .tiling = get_image_tiling(type), - .usage = get_image_usage(type), - .sharingMode = get_sharing_mode(type), - .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED + .sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO, + .pNext = nullptr, + .flags = get_image_flags(type), + .imageType = VK_IMAGE_TYPE_2D, + .format = format, + .extent = VkExtent3D{size.width, size.height, 1}, + .mipLevels = mips, + .arrayLayers = image.layers, + .samples = samples, + .tiling = get_image_tiling(type), + .usage = get_image_usage(type), + .sharingMode = get_sharing_mode(type), + .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED }; if (info.sharingMode == VK_SHARING_MODE_CONCURRENT) { @@ -119,7 +128,7 @@ RawImage ImageImpl::create_image(ImageType type, VkExtent2D size, VkFormat forma alloc_info.usage = VmaMemoryUsage::VMA_MEMORY_USAGE_GPU_ONLY; alloc_info.flags = VmaAllocationCreateFlagBits::VMA_ALLOCATION_CREATE_WITHIN_BUDGET_BIT; vmaCreateImage(ctx->allocator, reinterpret_cast(&info), &alloc_info, - reinterpret_cast(&image.handle), &image.memory, nullptr); + reinterpret_cast(&image.handle), &image.memory, nullptr); return image; } @@ -148,6 +157,10 @@ ImageView ImageImpl::create_image_view(RawImage const& target, ImageAspect aspec info.subresourceRange.baseMipLevel = 0; info.subresourceRange.levelCount = target.mip_levels; + if (target.layers > 1 && info.viewType == VK_IMAGE_VIEW_TYPE_2D) { + info.viewType = VK_IMAGE_VIEW_TYPE_2D_ARRAY; + } + vkCreateImageView(ctx->device, &info, nullptr, &view.handle); view.id = get_unique_image_view_id(); view.format = info.format; @@ -168,6 +181,84 @@ ImageView ImageImpl::create_image_view(RawImage const& target, ImageAspect aspec return view; } +ImageView ImageImpl::create_image_view(RawImage const& target, uint32_t mip, ImageAspect aspect) { + ImageView view; + + VkImageViewCreateInfo info{}; + info.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; + info.image = target.handle; + info.viewType = get_view_type(target.type); + info.format = target.format; + info.components.r = VK_COMPONENT_SWIZZLE_IDENTITY; + info.components.g = VK_COMPONENT_SWIZZLE_IDENTITY; + info.components.b = VK_COMPONENT_SWIZZLE_IDENTITY; + info.components.a = VK_COMPONENT_SWIZZLE_IDENTITY; + + info.subresourceRange.aspectMask = static_cast(aspect); + info.subresourceRange.baseArrayLayer = 0; + info.subresourceRange.layerCount = target.layers; + info.subresourceRange.baseMipLevel = mip; + info.subresourceRange.levelCount = 1; + + vkCreateImageView(ctx->device, &info, nullptr, &view.handle); + view.id = get_unique_image_view_id(); + view.format = info.format; + view.samples = target.samples; + view.size = target.size; + view.image = target.handle; + view.aspect = aspect; + view.base_layer = 0; + view.layer_count = target.layers; + view.base_level = mip; + view.level_count = 1; + + { + std::lock_guard lock{ mutex }; + all_image_views.emplace(view.id, view); + } + + return view; +} + +ImageView ImageImpl::create_image_view(RawImage const& target, uint32_t mip, uint32_t layer, ImageAspect aspect) { + ImageView view; + + VkImageViewCreateInfo info{}; + info.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; + info.image = target.handle; + info.viewType = get_view_type(target.type); + info.format = target.format; + info.components.r = VK_COMPONENT_SWIZZLE_IDENTITY; + info.components.g = VK_COMPONENT_SWIZZLE_IDENTITY; + info.components.b = VK_COMPONENT_SWIZZLE_IDENTITY; + info.components.a = VK_COMPONENT_SWIZZLE_IDENTITY; + + info.subresourceRange.aspectMask = static_cast(aspect); + info.subresourceRange.baseArrayLayer = layer; + info.subresourceRange.layerCount = 1; + info.subresourceRange.baseMipLevel = mip; + info.subresourceRange.levelCount = 1; + + vkCreateImageView(ctx->device, &info, nullptr, &view.handle); + view.id = get_unique_image_view_id(); + view.format = info.format; + view.samples = target.samples; + view.size = target.size; + view.image = target.handle; + view.aspect = aspect; + view.base_layer = layer; + view.layer_count = 1; + view.base_level = mip; + view.level_count = 1; + + { + std::lock_guard lock{ mutex }; + all_image_views.emplace(view.id, view); + } + + return view; +} + void ImageImpl::destroy_image_view(ImageView& view) { vkDestroyImageView(ctx->device, view.handle, nullptr); { diff --git a/src/impl/pipeline.cpp b/src/impl/pipeline.cpp index 65ccabe..57697da 100644 --- a/src/impl/pipeline.cpp +++ b/src/impl/pipeline.cpp @@ -148,6 +148,9 @@ namespace ph { } else { binding.descriptorCount = type.array[0]; + // Always add PartiallyBound flag for arrays. + dslci.flags.resize(dslci.bindings.size() + 1); + dslci.flags.back() = VK_DESCRIPTOR_BINDING_PARTIALLY_BOUND_BIT; } } else { @@ -239,6 +242,7 @@ namespace ph { find_acceleration_structures(info, *refl, dslci); #endif } + dslci.flags.resize(dslci.bindings.size()); // Make sure size matches so we don't index out of bounds. collapse_bindings(dslci); return dslci; } diff --git a/src/pass.cpp b/src/pass.cpp index f296885..7fb8e82 100644 --- a/src/pass.cpp +++ b/src/pass.cpp @@ -25,37 +25,52 @@ PassBuilder PassBuilder::create_ray_tracing(std::string_view name) { #endif PassBuilder& PassBuilder::add_attachment(std::string_view name, LoadOp load_op, ClearValue clear) { - ResourceUsage usage; - usage.type = ResourceType::Attachment; - usage.access = ResourceAccess::ColorAttachmentOutput; - usage.stage = PipelineStage::AttachmentOutput; - usage.attachment.name = name; - usage.attachment.load_op = load_op; - usage.attachment.clear = clear; - pass.resources.push_back(std::move(usage)); - return *this; + return add_attachment(name, {}, load_op, clear); +} + +PassBuilder& PassBuilder::add_attachment(std::string_view name, ImageView view, LoadOp load_op, ClearValue clear) { + ResourceUsage usage; + usage.type = ResourceType::Attachment; + usage.access = ResourceAccess::ColorAttachmentOutput; + usage.stage = PipelineStage::AttachmentOutput; + usage.attachment.name = name; + usage.attachment.load_op = load_op; + usage.attachment.clear = clear; + usage.attachment.view = view; + pass.resources.push_back(std::move(usage)); + return *this; } PassBuilder& PassBuilder::add_depth_attachment(std::string_view name, LoadOp load_op, ClearValue clear) { - ResourceUsage usage; - usage.type = ResourceType::Attachment; - usage.access = ResourceAccess::DepthStencilAttachmentOutput; - usage.stage = PipelineStage::AttachmentOutput; - usage.attachment.name = name; - usage.attachment.load_op = load_op; - usage.attachment.clear = clear; - pass.resources.push_back(std::move(usage)); - return *this; + return add_depth_attachment(name, {}, load_op, clear); +} + +PassBuilder& PassBuilder::add_depth_attachment(std::string_view name, ImageView view, LoadOp load_op, ClearValue clear) { + ResourceUsage usage; + usage.type = ResourceType::Attachment; + usage.access = ResourceAccess::DepthStencilAttachmentOutput; + usage.stage = PipelineStage::AttachmentOutput; + usage.attachment.name = name; + usage.attachment.load_op = load_op; + usage.attachment.clear = clear; + usage.attachment.view = view; + pass.resources.push_back(std::move(usage)); + return *this; } PassBuilder& PassBuilder::sample_attachment(std::string_view name, plib::bit_flag stage) { - ResourceUsage usage{}; - usage.type = ResourceType::Attachment; - usage.access = ResourceAccess::ShaderRead; - usage.stage = stage; - usage.attachment.name = name; - pass.resources.push_back(std::move(usage)); - return *this; + return sample_attachment(name, {}, stage); +} + +PassBuilder& PassBuilder::sample_attachment(std::string_view name, ImageView view, plib::bit_flag stage) { + ResourceUsage usage{}; + usage.type = ResourceType::Attachment; + usage.access = ResourceAccess::ShaderRead; + usage.stage = stage; + usage.attachment.name = name; + usage.attachment.view = view; + pass.resources.push_back(std::move(usage)); + return *this; } PassBuilder& PassBuilder::shader_read_buffer(BufferSlice slice, plib::bit_flag stage) { diff --git a/src/pipeline.cpp b/src/pipeline.cpp index 3c8ee05..8ce0181 100644 --- a/src/pipeline.cpp +++ b/src/pipeline.cpp @@ -134,24 +134,28 @@ DescriptorBuilder& DescriptorBuilder::add_storage_buffer(std::string_view bindin #if PHOBOS_ENABLE_RAY_TRACING -DescriptorBuilder& DescriptorBuilder::add_acceleration_structure(uint32_t binding, AccelerationStructure const& as) { - DescriptorBinding descr{}; - descr.binding = binding; - descr.type = VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_KHR; - auto& descriptor = descr.descriptors.emplace_back(); - descriptor.accel_structure = ph::DescriptorAccelerationStructureInfo{ - .structure = as.top_level.handle - }; - info.bindings.push_back(descr); - return *this; +DescriptorBuilder& DescriptorBuilder::add_acceleration_structure(uint32_t binding, VkAccelerationStructureKHR const& as) { + DescriptorBinding descr{}; + descr.binding = binding; + descr.type = VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_KHR; + auto& descriptor = descr.descriptors.emplace_back(); + descriptor.accel_structure = ph::DescriptorAccelerationStructureInfo{ + .structure = as + }; + info.bindings.push_back(descr); + return *this; } -DescriptorBuilder& DescriptorBuilder::add_acceleration_structure(ShaderMeta::Binding const& binding, AccelerationStructure const& as) { - return add_acceleration_structure(binding.binding, as); +DescriptorBuilder& DescriptorBuilder::add_acceleration_structure(ShaderMeta::Binding const& binding, VkAccelerationStructureKHR const& as) { + return add_acceleration_structure(binding.binding, as); +} + +DescriptorBuilder& DescriptorBuilder::add_acceleration_structure(std::string_view binding, VkAccelerationStructureKHR as) { + return add_acceleration_structure(ctx->get_shader_meta(pipeline)[binding], as); } DescriptorBuilder& DescriptorBuilder::add_acceleration_structure(std::string_view binding, AccelerationStructure const& as) { - return add_acceleration_structure(ctx->get_shader_meta(pipeline)[binding], as); + return add_acceleration_structure(ctx->get_shader_meta(pipeline)[binding], as.top_level.handle); } #endif @@ -231,6 +235,11 @@ PipelineBuilder& PipelineBuilder::set_depth_op(VkCompareOp op) { return *this; } +PipelineBuilder& PipelineBuilder::set_depth_clamp(bool clamp) { + pci.rasterizer.depthClampEnable = clamp; + return *this; +} + PipelineBuilder& PipelineBuilder::add_dynamic_state(VkDynamicState state) { pci.dynamic_states.push_back(state); if (state == VK_DYNAMIC_STATE_VIEWPORT) return add_viewport(VkViewport{}); @@ -258,6 +267,12 @@ PipelineBuilder& PipelineBuilder::set_samples(VkSampleCountFlagBits samples) { return *this; } +PipelineBuilder& PipelineBuilder::set_sample_shading(float value) { + pci.multisample.sampleShadingEnable = true; + pci.multisample.minSampleShading = value; + return *this; +} + PipelineBuilder& PipelineBuilder::add_blend_attachment(bool enable, VkBlendFactor src_color_factor, VkBlendFactor dst_color_factor, VkBlendOp color_op, VkBlendFactor src_alpha_factor, VkBlendFactor dst_alpha_factor, VkBlendOp alpha_op, diff --git a/src/render_graph.cpp b/src/render_graph.cpp index de7f28e..1d014a4 100644 --- a/src/render_graph.cpp +++ b/src/render_graph.cpp @@ -29,10 +29,12 @@ void RenderGraph::build(Context& ctx) { // Skip non-attachment resources if (!is_output_attachment(resource)) continue; - Attachment* attachment = ctx.get_attachment(resource.attachment.name); + Attachment attachment = ctx.get_attachment(resource.attachment.name); + // If a view is set, use that instead of the default ImageView. + ImageView view = resource.attachment.view ? resource.attachment.view : attachment.view; VkAttachmentReference ref{}; ref.attachment = attachment_index; - if (is_depth_format(attachment->view.format)) { + if (is_depth_format(view.format)) { ref.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; depth_ref = ref; } @@ -112,7 +114,9 @@ void RenderGraph::build(Context& ctx) { std::vector attachment_views{}; for (ResourceUsage const& resource : pass->resources) { if (!is_output_attachment(resource)) { continue; } - attachment_views.push_back(ctx.get_attachment(resource.attachment.name)->view.handle); + // If custom view was set, use that instead. + VkImageView view = resource.attachment.view ? resource.attachment.view.handle : ctx.get_attachment(resource.attachment.name).view.handle; + attachment_views.push_back(view); } fbci.attachmentCount = attachments.size(); fbci.pAttachments = attachment_views.data(); @@ -123,9 +127,11 @@ void RenderGraph::build(Context& ctx) { // Figure out width and height of the framebuffer. For this we need to find the largest size of all attachments. for (ResourceUsage const& resource : pass->resources) { if (!is_output_attachment(resource)) { continue; } - Attachment* attachment = ctx.get_attachment(resource.attachment.name); - fbci.width = std::max(attachment->view.size.width, fbci.width); - fbci.height = std::max(attachment->view.size.height, fbci.height); + Attachment attachment = ctx.get_attachment(resource.attachment.name); + // Note that we can safely use the main ImageView here since all sizes for array layers of an image + // must be the same + fbci.width = std::max(attachment.view.size.width, fbci.width); + fbci.height = std::max(attachment.view.size.height, fbci.height); } build.render_area = VkExtent2D{ .width = fbci.width, .height = fbci.height }; build.framebuf = ctx.get_or_create(fbci, "[Framebuffer] " + pass->name); @@ -138,11 +144,11 @@ std::vector RenderGraph::get_attachment_descriptions(Co for (ResourceUsage const& resource : pass->resources) { if (!is_output_attachment(resource)) { continue; } VkAttachmentDescription description{}; - Attachment* attachment = ctx.get_attachment(resource.attachment.name); + Attachment attachment = ctx.get_attachment(resource.attachment.name); assert(attachment && "Invalid attachment name"); - description.format = attachment->view.format; - description.samples = attachment->view.samples; + description.format = attachment.view.format; + description.samples = attachment.view.samples; description.loadOp = static_cast(resource.attachment.load_op); description.storeOp = VK_ATTACHMENT_STORE_OP_STORE; // Stencil operations are currently not supported @@ -171,7 +177,7 @@ VkImageLayout RenderGraph::get_initial_layout(Context& ctx, Pass* pass, Resource if (resource.attachment.load_op == LoadOp::DontCare) return VK_IMAGE_LAYOUT_UNDEFINED; - auto previous_usage_info = find_previous_usage(ctx, pass, ctx.get_attachment(resource.attachment.name)); + auto previous_usage_info = find_previous_usage(ctx, pass, resource.attachment.name); // Not used previously if (!was_used(previous_usage_info)) { return VK_IMAGE_LAYOUT_UNDEFINED; @@ -208,8 +214,8 @@ VkImageLayout RenderGraph::get_final_layout(Context& ctx, Pass* pass, ResourceUs // Note that 'later used' in this case only means the NEXT usage of this attachment. Any further usage can be taken care of by // further renderpasses - Attachment* att = ctx.get_attachment(resource.attachment.name); - auto next_usage_info = find_next_usage(ctx, pass, att); + Attachment att = ctx.get_attachment(resource.attachment.name); + auto next_usage_info = find_next_usage(ctx, pass, resource.attachment.name); if (!was_used(next_usage_info)) { // In this case, the attachments are not used later. We only need to check if this attachment is a swapchain attachment (in which case its used to present) @@ -219,7 +225,7 @@ VkImageLayout RenderGraph::get_final_layout(Context& ctx, Pass* pass, ResourceUs } // Case 3 - return get_output_layout_for_format(att->view.format); + return get_output_layout_for_format(att.view.format); } AttachmentUsage next_usage = get_attachment_usage(next_usage_info); @@ -236,13 +242,13 @@ VkImageLayout RenderGraph::get_final_layout(Context& ctx, Pass* pass, ResourceUs // return VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; // Important note! We do not use the render pass's transition for images that are sampled later, since we will // automatically insert a barrier instead. - return get_output_layout_for_format(att->view.format); + return get_output_layout_for_format(att.view.format); } throw std::runtime_error("Invalid resource access"); } -std::pair RenderGraph::find_previous_usage(Context& ctx, Pass* current_pass, Attachment* attachment) { +std::pair RenderGraph::find_previous_usage(Context& ctx, Pass* current_pass, std::string_view attachment) { // First pass isn't used earlier if (current_pass == &passes.front()) { return {}; @@ -253,7 +259,8 @@ std::pair RenderGraph::find_previous_usage(Context& ctx, P // Look in this pass's resources auto find_resource = [&ctx, attachment](ph::ResourceUsage const& resource) { if (resource.type != ResourceType::Attachment) return false; - return attachment->view.id == ctx.get_attachment(resource.attachment.name)->view.id; + // Since views are no longer always equal we compare by name. + return attachment == resource.attachment.name; }; auto usage = std::find_if(pass->resources.begin(), pass->resources.end(), find_resource); @@ -265,7 +272,7 @@ std::pair RenderGraph::find_previous_usage(Context& ctx, P return {}; } -std::pair RenderGraph::find_next_usage(Context& ctx, Pass* current_pass, Attachment* attachment) { +std::pair RenderGraph::find_next_usage(Context& ctx, Pass* current_pass, std::string_view attachment) { // Last pass isn't used later if (current_pass == &passes.back()) { return {}; @@ -276,7 +283,7 @@ std::pair RenderGraph::find_next_usage(Context& ctx, Pass* auto find_resource = [&ctx, attachment](ph::ResourceUsage const& resource) { if (resource.type != ResourceType::Attachment) return false; - return attachment->view.id == ctx.get_attachment(resource.attachment.name)->view.id; + return attachment == resource.attachment.name; }; auto usage = std::find_if(pass->resources.begin(), pass->resources.end(), find_resource); @@ -342,13 +349,21 @@ std::pair RenderGraph::find_previous_usage(Context& ctx, P // Go over each earlier pass for (Pass* pass = current_pass - 1; pass >= &passes.front(); --pass) { - // Look in this pass's resources auto find_resource = [image, &ctx](ph::ResourceUsage const& resource) { if (resource.type == ResourceType::Attachment) { - return *image == ctx.get_attachment(resource.attachment.name)->view; + // If views are equal, we classify this as found + // Else, if there is an image and image handles are equal, we found one. + // Otherwise, we didn't find anything. + Attachment attachment = ctx.get_attachment(resource.attachment.name); + if (image->handle == attachment.view.handle) return true; + else if (attachment.image && image->image == attachment.image->handle) return true; + else return false; } if (resource.type != ResourceType::Image && resource.type != ResourceType::StorageImage) return false; - return *image == resource.image.view; + // Same explanation as above inside the Attachment case + if (image->handle == resource.image.view.handle) return true; + else if (image->image == resource.image.view.image) return true; + else return false; }; auto usage = std::find_if(pass->resources.begin(), pass->resources.end(), find_resource); @@ -368,13 +383,21 @@ std::pair RenderGraph::find_next_usage(Context& ctx, Pass* // Go over each later pass for (Pass* pass = current_pass + 1; pass <= &passes.back(); ++pass) { - // Look in this pass's resources auto find_resource = [image, &ctx](ph::ResourceUsage const& resource) { if (resource.type == ResourceType::Attachment) { - return *image == ctx.get_attachment(resource.attachment.name)->view; + // If views are equal, we classify this as found + // Else, if there is an image and image handles are equal, we found one. + // Otherwise, we didn't find anything. + Attachment attachment = ctx.get_attachment(resource.attachment.name); + if (image->handle == attachment.view.handle) return true; + else if (attachment.image && image->image == attachment.image->handle) return true; + else return false; } if (resource.type != ResourceType::Image && resource.type != ResourceType::StorageImage) return false; - return *image == resource.image.view; + // Same explanation as above inside the Attachment case + if (image->handle == resource.image.view.handle) return true; + else if (image->image == resource.image.view.image) return true; + else return false; }; auto usage = std::find_if(pass->resources.begin(), pass->resources.end(), find_resource); @@ -440,7 +463,7 @@ void RenderGraph::create_pass_barriers(Context& ctx, Pass& pass, BuiltPass& resu if (next.access & ResourceAccess::ShaderWrite) { barrier.dstAccessMask |= VK_ACCESS_SHADER_WRITE_BIT; } } - // Only actually inser the barrier if we set any values (meaning we need it) + // Only actually insert the barrier if we set any values (meaning we need it) if (barrier.srcAccessMask != VkAccessFlags{} && barrier.dstAccessMask != VkAccessFlags{}) { Barrier final_barrier; final_barrier.buffer = barrier; @@ -481,7 +504,7 @@ void RenderGraph::create_pass_barriers(Context& ctx, Pass& pass, BuiltPass& resu Barrier final_barrier; final_barrier.image = barrier; final_barrier.type = BarrierType::Image; - final_barrier.src_stage = ph::PipelineStage::TopOfPipe; + final_barrier.src_stage = ph::PipelineStage::AllCommands; final_barrier.dst_stage = resource.stage; // Note that this is a PRE barrier! result.pre_barriers.push_back(final_barrier); @@ -507,8 +530,8 @@ void RenderGraph::create_pass_barriers(Context& ctx, Pass& pass, BuiltPass& resu if (resource.type == ResourceType::StorageImage) { barrier.oldLayout = VK_IMAGE_LAYOUT_GENERAL; } else if (resource.type == ResourceType::Image) { barrier.oldLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; } else if (resource.type == ResourceType::Attachment) { - Attachment* att = ctx.get_attachment(resource.attachment.name); - if (is_depth_format(att->view.format)) { + Attachment att = ctx.get_attachment(resource.attachment.name); + if (is_depth_format(att.view.format)) { barrier.oldLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; } else { @@ -543,7 +566,7 @@ void RenderGraph::create_pass_barriers(Context& ctx, Pass& pass, BuiltPass& resu barrier.srcAccessMask |= VK_ACCESS_SHADER_WRITE_BIT; // Write -> Read, add transition to VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL if next is not a storage image if (next.access & ResourceAccess::ShaderRead) { - if (next.type == ResourceType::Image) { barrier.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; } + if (next.type == ResourceType::Image || next.type == ResourceType::Attachment) { barrier.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; } else if (next.type == ResourceType::StorageImage) { barrier.newLayout = VK_IMAGE_LAYOUT_GENERAL; } barrier.dstAccessMask |= VK_ACCESS_SHADER_READ_BIT; } @@ -567,8 +590,8 @@ void RenderGraph::create_pass_barriers(Context& ctx, Pass& pass, BuiltPass& resu } if (resource.type == ResourceType::Attachment) { - Attachment* attachment = ctx.get_attachment(resource.attachment.name); - ph::ImageView view = attachment->view; + Attachment attachment = ctx.get_attachment(resource.attachment.name); + ph::ImageView view = resource.attachment.view ? resource.attachment.view : attachment.view; // If there is no previous usage, we will insert an additional barrier to ensure the proper layout is used at the beginning of the frame auto previous_usage = find_previous_usage(ctx, &pass, &view); @@ -581,8 +604,8 @@ void RenderGraph::create_pass_barriers(Context& ctx, Pass& pass, BuiltPass& resu barrier.subresourceRange.aspectMask = static_cast(view.aspect); barrier.subresourceRange.baseMipLevel = view.base_level; barrier.subresourceRange.levelCount = view.level_count; - barrier.subresourceRange.baseArrayLayer = view.base_layer; - barrier.subresourceRange.layerCount = view.layer_count; + barrier.subresourceRange.baseArrayLayer = attachment.view.base_layer; + barrier.subresourceRange.layerCount = attachment.view.layer_count; barrier.pNext = nullptr; barrier.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED; // We don't know the last layout. @@ -601,7 +624,7 @@ void RenderGraph::create_pass_barriers(Context& ctx, Pass& pass, BuiltPass& resu Barrier final_barrier; final_barrier.image = barrier; final_barrier.type = BarrierType::Image; - final_barrier.src_stage = ph::PipelineStage::TopOfPipe; + final_barrier.src_stage = ph::PipelineStage::AllCommands; if (resource.access == ResourceAccess::ColorAttachmentOutput) { final_barrier.dst_stage = ph::PipelineStage::AttachmentOutput; } @@ -627,8 +650,11 @@ void RenderGraph::create_pass_barriers(Context& ctx, Pass& pass, BuiltPass& resu barrier.subresourceRange.aspectMask = static_cast(view.aspect); barrier.subresourceRange.baseMipLevel = view.base_level; barrier.subresourceRange.levelCount = view.level_count; - barrier.subresourceRange.baseArrayLayer = view.base_layer; - barrier.subresourceRange.layerCount = view.layer_count; + // Always transition entire image. + // This means that if you are using all layers of an image individually and then want to synchronize access + // to the entire image, it is enough to only specify one layer view as a dependency. + barrier.subresourceRange.baseArrayLayer = attachment.view.base_layer; + barrier.subresourceRange.layerCount = attachment.view.layer_count; barrier.pNext = nullptr; if (resource.access & ResourceAccess::ColorAttachmentOutput || @@ -645,7 +671,7 @@ void RenderGraph::create_pass_barriers(Context& ctx, Pass& pass, BuiltPass& resu // Use a cast so we don't need to switch between color/depth barrier.srcAccessMask |= static_cast(resource.access.value()); barrier.oldLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; - if (is_depth_format(attachment->view.format)) { + if (is_depth_format(view.format)) { barrier.oldLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; } diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 0eabc1d..acbafae 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -14,14 +14,19 @@ add_executable(TestApp) target_link_libraries(TestApp PRIVATE Phobos glfw) target_sources(TestApp PRIVATE "main.cpp") +set(GLSLC_DIR "" CACHE STRING "glslc binary directory, or empty if in path") + file(GLOB SHADER_SOURCES "${CMAKE_CURRENT_SOURCE_DIR}/shaders/*.vert" "${CMAKE_CURRENT_SOURCE_DIR}/shaders/*.frag" "${CMAKE_CURRENT_SOURCE_DIR}/shaders/*.comp") +get_filename_component(SHADER_OUTPUT_DIR ${CMAKE_CURRENT_BINARY_DIR}/data/shaders/ REALPATH BASE_DIR ${CMAKE_CURRENT_BINARY_DIR}) +file(MAKE_DIRECTORY ${SHADER_OUTPUT_DIR}) +message(STATUS "Shader output directory: ${SHADER_OUTPUT_DIR}") set(SHADER_OUTPUT_FILES "") foreach(SHADER ${SHADER_SOURCES}) get_filename_component(SHADER_FNAME ${SHADER} NAME) - set(SHADER_OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/data/shaders/${SHADER_FNAME}.spv) + set(SHADER_OUTPUT ${SHADER_OUTPUT_DIR}/${SHADER_FNAME}.spv) list(APPEND SHADER_OUTPUT_FILES ${SHADER_OUTPUT}) add_custom_command(OUTPUT ${SHADER_OUTPUT} - COMMAND glslc ${SHADER} "-o${SHADER_OUTPUT}" + COMMAND ${GLSLC_DIR}/glslc ${SHADER} "-o${SHADER_OUTPUT}" DEPENDS ${SHADER} ) endforeach()