diff --git a/CMakeLists.txt b/CMakeLists.txt index 3f9d817..065406f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -90,7 +90,7 @@ target_link_libraries(vku PUBLIC $<$:Vulkan::shaderc_combined> ) target_compile_definitions(vku PUBLIC - $<$:VKU_USE_STD_MODULE> + $<$:VKU_USE_STD_MODULE VULKAN_HPP_ENABLE_STD_MODULE> $<$:VKU_USE_SHADERC> $<$:VULKAN_HPP_NO_SMART_HANDLE VK_NO_PROTOTYPES> # See https://github.com/KhronosGroup/Vulkan-Hpp/blob/main/README.md#c20-named-module for the details. $<$:VK_ENABLE_BETA_EXTENSIONS> # For VK_KHR_portability_subset availability. diff --git a/interface/descriptors/DescriptorSet.cppm b/interface/descriptors/DescriptorSet.cppm index e0b69f5..84a9f39 100644 --- a/interface/descriptors/DescriptorSet.cppm +++ b/interface/descriptors/DescriptorSet.cppm @@ -59,6 +59,27 @@ namespace vku { return attachInfo(VULKAN_HPP_NAMESPACE::WriteDescriptorSet { *this, Binding, 0, {}, get(Layout::bindingTypes) }, descriptorInfos); } + /** + * Convenience method for calling getWrite with single lifetime-bounded descriptor info object. + * @tparam Binding Binding index to get the write descriptor. + * @param descriptorInfo Descriptor info to write. This is either vk::DescriptorBufferInfo, + * vk::DescriptorImageInfo or vk::BufferView, based on your descriptor type predefined by DescriptorSetLayout. + * @return vk::WriteDescriptorSet with given info. + * @example + * @code + * struct Layout : vku::DescriptorSetLayout { ... } layout; + * + * auto [descriptorSet] = vku::allocateDescriptorSets(*device, *descriptorPool, std::tie(layout)); + * device.updateDescriptorSets({ + * descriptorSet.getWriteOne<0>({ buffer, 0, vk::WholeSize }), // argument type infered as vk::DescriptorBufferInfo at the compile time. + * }, {}); + * @endcode + */ + template + [[nodiscard]] auto getWriteOne(const WriteDescriptorInfo_t(Layout::bindingTypes)> &descriptorInfo [[clang::lifetimebound]]) const noexcept -> VULKAN_HPP_NAMESPACE::WriteDescriptorSet { + return getWrite(descriptorInfo); + } + template ... Layouts> friend auto allocateDescriptorSets(VULKAN_HPP_NAMESPACE::Device, VULKAN_HPP_NAMESPACE::DescriptorPool, std::tuple) -> std::tuple...>; diff --git a/interface/images/Image.cppm b/interface/images/Image.cppm index 4facbcc..b451d02 100644 --- a/interface/images/Image.cppm +++ b/interface/images/Image.cppm @@ -3,8 +3,10 @@ module; #include #ifndef VKU_USE_STD_MODULE #include -#include #include +#include +#include +#include #endif #include @@ -70,6 +72,30 @@ namespace vku { */ [[nodiscard]] auto getViewCreateInfo(const VULKAN_HPP_NAMESPACE::ImageSubresourceRange &subresourceRange, VULKAN_HPP_NAMESPACE::ImageViewType type = VULKAN_HPP_NAMESPACE::ImageViewType::e2D) const noexcept -> VULKAN_HPP_NAMESPACE::ImageViewCreateInfo; + /** + * Get vk::ImageViewCreateInfo structs for all mip levels with the specified \p type. Aspect flags are + * inferred from the image format. + * @param type Image view type (default=vk::ImageViewType::e2D). + * @return Vector of vk::ImageViewCreateInfo structs for all mip levels. + * @note + * See inferAspectFlags(vk::Format) for aspect flags inference rule.
+ * It internally calls inferAspectFlags(vk::Format) and getViewCreateInfo(const vk::ImageSubresourceRange&, vk::ImageViewType). + */ + [[nodiscard]] auto getMipViewCreateInfos(VULKAN_HPP_NAMESPACE::ImageViewType type = VULKAN_HPP_NAMESPACE::ImageViewType::e2D) const NOEXCEPT_IF_RELEASE -> std::vector { + return std::views::iota(0U, mipLevels) + | std::views::transform([&, aspectFlags = inferAspectFlags(format)](std::uint32_t level) { + return VULKAN_HPP_NAMESPACE::ImageViewCreateInfo { + {}, + image, + type, + format, + {}, + { aspectFlags, level, 1, 0, VULKAN_HPP_NAMESPACE::RemainingArrayLayers } + }; + }) + | std::ranges::to(); + } + /** * Get the extent of the specified mip level. * @param mipLevel Mip level, starts from zero, which must be less than maxMipLevels(extent). diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index c83526f..8a32121 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -2,5 +2,12 @@ add_executable(execute_hierarchical_commands execute_hierarchical_commands.cpp) target_link_libraries(execute_hierarchical_commands PRIVATE vku::vku) add_test(NAME execute_hierarchical_commands COMMAND execute_hierarchical_commands) +add_executable(get_mip_view_create_infos get_mip_view_create_infos.cpp) +target_link_libraries(get_mip_view_create_infos PRIVATE vku::vku) +target_compile_definitions(get_mip_view_create_infos PRIVATE + COMPILED_SHADER_DIR="${CMAKE_CURRENT_SOURCE_DIR}/shaders/get_mip_view_create_infos" +) +add_test(NAME get_mip_view_create_infos COMMAND get_mip_view_create_infos) + add_subdirectory(triangle) add_subdirectory(msaa-triangle) \ No newline at end of file diff --git a/test/execute_hierarchical_commands.cpp b/test/execute_hierarchical_commands.cpp index 37e0d5f..be3c171 100644 --- a/test/execute_hierarchical_commands.cpp +++ b/test/execute_hierarchical_commands.cpp @@ -3,13 +3,15 @@ #include #include #include -#include #include #include #include #include #include #include +#ifdef _MSC_VER +#include +#endif #endif #include diff --git a/test/get_mip_view_create_infos.cpp b/test/get_mip_view_create_infos.cpp new file mode 100644 index 0000000..59756ab --- /dev/null +++ b/test/get_mip_view_create_infos.cpp @@ -0,0 +1,258 @@ +#include +#ifndef VKU_USE_STD_MODULE +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef _MSC_VER +#include +#endif +#endif + +#include + +#ifdef VKU_USE_STD_MODULE +import std; +#endif +import vku; + +#if VULKAN_HPP_DISPATCH_LOADER_DYNAMIC == 1 +VULKAN_HPP_DEFAULT_DISPATCH_LOADER_DYNAMIC_STORAGE +#endif + +struct QueueFamilies { + std::uint32_t compute; + + explicit QueueFamilies(vk::PhysicalDevice physicalDevice) + : compute { vku::getComputeQueueFamily(physicalDevice.getQueueFamilyProperties()).value() } { } +}; + +struct Queues { + vk::Queue compute; + + Queues(vk::Device device, const QueueFamilies &queueFamilies) + : compute { device.getQueue(queueFamilies.compute, 0) } { } + + [[nodiscard]] static auto getCreateInfos(vk::PhysicalDevice, const QueueFamilies &queueFamilies) noexcept +#ifdef _MSC_VER + -> vku::RefHolder, std::array> +#endif + { + return vku::RefHolder { + [&](std::span priorities) { + return std::array { + vk::DeviceQueueCreateInfo { + {}, + queueFamilies.compute, + priorities, + }, + }; + }, + std::array { 1.f }, + }; + } +}; + +struct Gpu : vku::Gpu { + explicit Gpu(const vk::raii::Instance &instance [[clang::lifetimebound]]) + : vku::Gpu { instance, vku::Gpu::Config { + .verbose = true, + .deviceExtensions = { + vk::EXTDescriptorIndexingExtensionName, +#if __APPLE__ + vk::KHRPortabilitySubsetExtensionName, +#endif + }, + .devicePNexts = std::tuple { + vk::PhysicalDeviceDescriptorIndexingFeatures{} + .setRuntimeDescriptorArray(true), + }, + .apiVersion = vk::makeApiVersion(0, 1, 1, 0), + } } { } +}; + +struct ColorCheckComputer { + struct DescriptorSetLayout : vku::DescriptorSetLayout { + explicit DescriptorSetLayout(const vk::raii::Device &device [[clang::lifetimebound]], std::uint32_t mipLevels) + : vku::DescriptorSetLayout { + device, vk::DescriptorSetLayoutCreateInfo { + {}, + vku::unsafeProxy({ + vk::DescriptorSetLayoutBinding { 0, vk::DescriptorType::eSampledImage, mipLevels, vk::ShaderStageFlagBits::eCompute }, + vk::DescriptorSetLayoutBinding { 1, vk::DescriptorType::eStorageBuffer, 1, vk::ShaderStageFlagBits::eCompute }, + }), + } + } { } + }; + + struct PushConstant { + std::array color; + std::uint32_t mipLevel; + }; + + DescriptorSetLayout descriptorSetLayout; + vk::raii::PipelineLayout pipelineLayout; + vk::raii::Pipeline pipeline; + + explicit ColorCheckComputer(const vk::raii::Device &device [[clang::lifetimebound]], std::uint32_t mipLevels) + : descriptorSetLayout { device, mipLevels } + , pipelineLayout { device, vk::PipelineLayoutCreateInfo { + {}, + *descriptorSetLayout, + vku::unsafeProxy(vk::PushConstantRange { + vk::ShaderStageFlagBits::eCompute, + 0, sizeof(PushConstant), + }), + } } + , pipeline { device, nullptr, vk::ComputePipelineCreateInfo { + {}, + createPipelineStages(device, vku::Shader { COMPILED_SHADER_DIR "/color_check.comp.spv", vk::ShaderStageFlagBits::eCompute }).get()[0], + *pipelineLayout, + } } { } +}; + +int main() { +#if VULKAN_HPP_DISPATCH_LOADER_DYNAMIC == 1 + VULKAN_HPP_DEFAULT_DISPATCHER.init(); +#endif + + const vk::raii::Context context; + const vk::raii::Instance instance { context, vk::InstanceCreateInfo { +#if __APPLE__ + vk::InstanceCreateFlagBits::eEnumeratePortabilityKHR, +#else + {}, +#endif + vku::unsafeAddress(vk::ApplicationInfo { + "vku_test_get_mip_view_create_infos", 0, + {}, 0, + vk::makeApiVersion(0, 1, 1, 0), + }), + {}, +#if __APPLE__ + vku::unsafeProxy({ + vk::KHRPortabilityEnumerationExtensionName, + }), +#endif + } }; + const Gpu gpu { instance }; + + constexpr std::array colors { + vk::ClearColorValue { 0.f, 0.f, 0.f, 1.f }, // Black + vk::ClearColorValue { 1.f, 0.f, 0.f, 1.f }, // Red + vk::ClearColorValue { 0.f, 1.f, 0.f, 1.f }, // Green + vk::ClearColorValue { 0.f, 0.f, 1.f, 1.f }, // Blue + vk::ClearColorValue { 1.f, 1.f, 0.f, 1.f }, // Yellow + vk::ClearColorValue { 0.f, 1.f, 1.f, 1.f }, // Cyan + vk::ClearColorValue { 1.f, 0.f, 1.f, 1.f }, // Magenta + vk::ClearColorValue { 1.f, 1.f, 1.f, 1.f }, // White + }; + + const vku::AllocatedImage image { gpu.allocator, vk::ImageCreateInfo { + {}, + vk::ImageType::e2D, + vk::Format::eR8G8B8A8Unorm, + vk::Extent3D { 128, 128, 1 }, + colors.size(), 1, + vk::SampleCountFlagBits::e1, + vk::ImageTiling::eOptimal, + vk::ImageUsageFlagBits::eTransferDst | vk::ImageUsageFlagBits::eSampled, + } }; + const std::vector imageViews + // -------------------- + // MAIN CODE TO TEST! + // -------------------- + = image.getMipViewCreateInfos() + | std::views::transform([&](const vk::ImageViewCreateInfo &createInfo) { + return vk::raii::ImageView { gpu.device, createInfo }; + }) + | std::ranges::to(); + + const vku::MappedBuffer buffer { + gpu.allocator, + std::from_range, std::vector(image.mipLevels, vk::True), + vk::BufferUsageFlagBits::eStorageBuffer, + }; + + const ColorCheckComputer colorCheckComputer { gpu.device, image.mipLevels }; + + const vk::raii::DescriptorPool descriptorPool { gpu.device, vk::DescriptorPoolCreateInfo { + {}, + 1, + vku::unsafeProxy({ + vk::DescriptorPoolSize { vk::DescriptorType::eSampledImage, image.mipLevels }, + vk::DescriptorPoolSize { vk::DescriptorType::eStorageBuffer, 1 }, + }), + } }; + const auto [descriptorSet] = vku::allocateDescriptorSets(*gpu.device, *descriptorPool, std::tie(colorCheckComputer.descriptorSetLayout)); + gpu.device.updateDescriptorSets({ + descriptorSet.getWrite<0>(vku::unsafeProxy(imageViews | std::views::transform([](const auto &imageView) { + return vk::DescriptorImageInfo { {}, *imageView, vk::ImageLayout::eShaderReadOnlyOptimal }; + }) | std::ranges::to())), + descriptorSet.getWriteOne<1>({ buffer, 0, vk::WholeSize }), + }, {}); + + const vk::raii::CommandPool computeCommandPool { gpu.device, vk::CommandPoolCreateInfo { + {}, + gpu.queueFamilies.compute, + } }; + + vku::executeSingleCommand(*gpu.device, *computeCommandPool, gpu.queues.compute, [&](vk::CommandBuffer cb) { + // Change image layout to TransferDstOptimal. + cb.pipelineBarrier( + vk::PipelineStageFlagBits::eTopOfPipe, vk::PipelineStageFlagBits::eTransfer, + {}, {}, {}, + vk::ImageMemoryBarrier { + {}, vk::AccessFlagBits::eTransferWrite, + {}, vk::ImageLayout::eTransferDstOptimal, + vk::QueueFamilyIgnored, vk::QueueFamilyIgnored, + image, vku::fullSubresourceRange(), + }); + + // Fill image mipmaps with colors for each level, respectively. + for (std::uint32_t level = 0; const vk::ClearColorValue &color : colors) { + cb.clearColorImage( + image, vk::ImageLayout::eTransferDstOptimal, + color, vk::ImageSubresourceRange { vk::ImageAspectFlagBits::eColor, level, 1, 0, 1 }); + + ++level; + } + + // Change image layout to ShaderReadOnlyOptimal after clearing. + cb.pipelineBarrier( + vk::PipelineStageFlagBits::eTransfer, vk::PipelineStageFlagBits::eComputeShader, + {}, {}, {}, + vk::ImageMemoryBarrier { + vk::AccessFlagBits::eTransferWrite, vk::AccessFlagBits::eShaderRead, + vk::ImageLayout::eTransferDstOptimal, vk::ImageLayout::eShaderReadOnlyOptimal, + vk::QueueFamilyIgnored, vk::QueueFamilyIgnored, + image, vku::fullSubresourceRange(), + }); + + // Execute ColorCheckComputer for every mip levels. + cb.bindPipeline(vk::PipelineBindPoint::eCompute, *colorCheckComputer.pipeline); + cb.bindDescriptorSets(vk::PipelineBindPoint::eCompute, *colorCheckComputer.pipelineLayout, 0, descriptorSet, {}); + for (std::uint32_t level = 0; const vk::ClearColorValue &color : colors) { + cb.pushConstants(*colorCheckComputer.pipelineLayout, vk::ShaderStageFlagBits::eCompute, 0, ColorCheckComputer::PushConstant { + color.float32, + level, + }); + + const vk::Extent2D mipExtent = image.mipExtent(level); + constexpr auto divCeil = [](std::uint32_t num, std::uint32_t denom) noexcept { + return num / denom + (num % denom != 0); + }; + cb.dispatch(divCeil(mipExtent.width, 16U), divCeil(mipExtent.height, 16U), 1); + + ++level; + } + }); + gpu.queues.compute.waitIdle(); + + // Check if all booleans in the buffer remain vk::True. + assert(std::ranges::all_of(buffer.asRange(), std::identity{}) && "Color mismatch!"); +} \ No newline at end of file diff --git a/test/shaders/get_mip_view_create_infos/color_check.comp b/test/shaders/get_mip_view_create_infos/color_check.comp new file mode 100644 index 0000000..15dd8e6 --- /dev/null +++ b/test/shaders/get_mip_view_create_infos/color_check.comp @@ -0,0 +1,38 @@ +/* Check if all texels in the texture's given mip level are same as Push Constant's color. + * If all texels are same, success[mipLevel] will be set to 1. + * If any texel is different, success[mipLevel] will be set to 0. */ + +#version 460 +#extension GL_EXT_samplerless_texture_functions : require +#extension GL_KHR_shader_subgroup_vote : require +#extension GL_EXT_nonuniform_qualifier : require + +layout (set = 0, binding = 0) uniform texture2D textures[]; +layout (set = 0, binding = 1) writeonly buffer OutputBuffer { + uint success[]; // success[i] represents whether i-th mipmap's all texel are same with pc.color or not. +}; + +layout (push_constant) uniform PushConstant { + vec4 color; + int mipLevel; +} pc; + +layout (local_size_x = 16, local_size_y = 16) in; + +void main(){ + uvec2 mipmapSize = textureSize(textures[pc.mipLevel], 0); + + vec4 texel; + if (gl_GlobalInvocationID.x < mipmapSize.x && gl_GlobalInvocationID.y < mipmapSize.y) { + texel = texelFetch(textures[pc.mipLevel], ivec2(gl_GlobalInvocationID.xy), 0); + } + else { + texel = pc.color; + } + + if (subgroupAny(texel != pc.color)) { + if (subgroupElect()){ + atomicAnd(success[pc.mipLevel], 0U); + } + } +} \ No newline at end of file