diff --git a/CMakeLists.txt b/CMakeLists.txt index fee275c..d9655a3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -16,13 +16,11 @@ static_library( glfw3 Vulkan glm - stb LINK_PACKAGES glfw Vulkan::Vulkan glm::glm - stb::stb ) target_compile_features(${PROJECT_NAME} PUBLIC cxx_std_23) @@ -57,6 +55,7 @@ target_sources(${PROJECT_NAME} PUBLIC vulkan-cpp/descriptor_resource.cppm vulkan-cpp/texture.cppm vulkan-cpp/dyn/buffer.cppm + vulkan-cpp/image.cppm ) install( diff --git a/conanfile.py b/conanfile.py index c4fc71e..62eaff1 100644 --- a/conanfile.py +++ b/conanfile.py @@ -27,7 +27,6 @@ def build_requirements(self): def requirements(self): self.requires("glfw/3.4") self.requires("glm/1.0.1") - self.requires("stb/cci.20230920") def layout(self): cmake_layout(self) diff --git a/demos/17-buffer-device-address/CMakeLists.txt b/demos/17-buffer-device-address/CMakeLists.txt index dd8c7ed..b7fefe3 100644 --- a/demos/17-buffer-device-address/CMakeLists.txt +++ b/demos/17-buffer-device-address/CMakeLists.txt @@ -14,6 +14,7 @@ build_application( tinyobjloader LINK_PACKAGES + stb::stb vulkan-cpp tinyobjloader Vulkan::Vulkan diff --git a/demos/17-buffer-device-address/application.cpp b/demos/17-buffer-device-address/application.cpp index e79f527..3da9742 100644 --- a/demos/17-buffer-device-address/application.cpp +++ b/demos/17-buffer-device-address/application.cpp @@ -23,9 +23,15 @@ import vk; #define GLM_ENABLE_EXPERIMENTAL #include #include +#include #include +#ifndef STB_IMAGE_IMPLEMENTATION +#define STB_IMAGE_IMPLEMENTATION +#include +#endif + static VKAPI_ATTR VkBool32 VKAPI_CALL debug_callback( [[maybe_unused]] VkDebugUtilsMessageSeverityFlagBitsEXT p_message_severity, @@ -240,6 +246,80 @@ struct push_constant_data { uint64_t global_ubo_addr = 0; }; +/** + * @brief STBI-specific implementation of the vk::image interface + */ +class stb_image : public vk::image { +public: + stb_image() = delete; + + stb_image(std::string_view p_path, vk::texture_params p_params) { + image_load(p_path, p_params); + } + + ~stb_image() = default; + +protected: + bool image_load(std::string_view p_path, + vk::texture_params p_params) override { + int w = 0; + int h = 0; + int channels = 0; + + stbi_uc* image_pixel_data = + stbi_load(p_path.data(), &w, &h, &channels, STBI_rgb_alpha); + + if (!image_pixel_data) { + return false; + } + + const VkFormat texture_format = + static_cast(vk::format::r8g8b8a8_unorm); + int bytes_per_pixel = vk::bytes_per_texture_format(texture_format); + + m_extent = { + .width = static_cast(w), + .height = static_cast(h), + }; + + // Retrieving total size of bytes of the dimensions of the image and + // accounting for pixels of the image + uint32_t size_bytes = + m_extent.width * m_extent.height * bytes_per_pixel; + + // Retrieving total image size to the count of the image layers + uint32_t size = size_bytes * p_params.layer_count; + + vk::image_params image_options = { + .extent = m_extent, + .format = texture_format, + .memory_mask = p_params.memory_mask, + .usage = static_cast(vk::image_usage::transfer_dst_bit) | + static_cast(vk::image_usage::sampled_bit), + .mip_levels = p_params.mip_levels, + .layer_count = p_params.layer_count, + }; + + m_bytes.reserve(size); + std::span bytes_view = + std::span(image_pixel_data, size); + + m_bytes.assign(bytes_view.begin(), bytes_view.end()); + + stbi_image_free(image_pixel_data); + + return true; + } + + std::span image_read() const override { return m_bytes; } + + vk::image_extent image_extent() const override { return m_extent; } + +private: + vk::image_extent m_extent{}; + std::vector m_bytes{}; +}; + int main() { //! @note Just added the some test code to test the conan-starter setup code @@ -590,14 +670,13 @@ main() { vk::dyn::buffer(logical_device, sizeof(global_uniform), uniform_params); vk::texture_params config_texture = { - .memory_mask = - physical_device.memory_properties(static_cast( - vk::memory_property::host_visible_bit | - vk::memory_property::host_cached_bit)), + .memory_mask = physical_device.memory_properties( + vk::memory_property::host_visible_bit | + vk::memory_property::host_cached_bit), }; - vk::texture texture1(logical_device, - std::filesystem::path("asset_samples/viking_room.png"), - config_texture); + + stb_image img = stb_image("asset_samples/viking_room.png", config_texture); + vk::texture texture1(logical_device, &img, config_texture); // Setting the texture sampler/image view to descriptor resource std::array samplers = { diff --git a/vulkan-cpp/image.cppm b/vulkan-cpp/image.cppm new file mode 100644 index 0000000..b417657 --- /dev/null +++ b/vulkan-cpp/image.cppm @@ -0,0 +1,52 @@ +module; + +#include +#include + +export module vk:image; + +import :types; + +export namespace vk { + inline namespace v1 { + + /** + * @brief interface for the purpose of acting as an interface to + * different implementations for support variety of approaches in + * loading images. + */ + class image { + public: + virtual ~image() = default; + + /** + * @brief Perform loading operations specific to the implementation + * of this interface + */ + bool load(std::string_view p_path, texture_params p_params) { + return image_load(p_path, p_params); + } + + /** + * @brief Read you the bytes read from the specific image loading + * implementations + */ + [[nodiscard]] std::span read() const { + return image_read(); + } + + /** + * @return the extent that contains properties about the image + * (dimensions, depth, etc) + */ + [[nodiscard]] image_extent extent() const { return image_extent(); } + + protected: + virtual bool image_load(std::string_view, texture_params) = 0; + + virtual std::span image_read() const = 0; + + virtual image_extent image_extent() const = 0; + }; + }; +}; \ No newline at end of file diff --git a/vulkan-cpp/texture.cppm b/vulkan-cpp/texture.cppm index d906f44..d7a332b 100644 --- a/vulkan-cpp/texture.cppm +++ b/vulkan-cpp/texture.cppm @@ -5,199 +5,152 @@ module; #include #include -#ifndef STB_IMAGE_IMPLEMENTATION -#define STB_IMAGE_IMPLEMENTATION -#include -#endif - export module vk:texture; -export import :types; -export import :utilities; -export import :buffer_streams; -export import :sample_image; -export import :command_buffer; +import :types; +import :utilities; +import :buffer_streams; +import :sample_image; +import :command_buffer; +import :image; export namespace vk { inline namespace v1 { - sample_image create_texture_with_data(const VkDevice& p_device, - const image_params& p_config, - std::span p_data) { - - // 1. loading texture - sample_image texture_image = sample_image(p_device, p_config); - - // 2. transfer data from staging buffer - uint32_t property_flag = memory_property::host_visible_bit | - memory_property::host_cached_bit; - - buffer_parameters staging_buffer_config = { - .memory_mask = p_config.memory_mask, - .property_flags = static_cast(property_flag), - .usage = static_cast(buffer_usage::transfer_src_bit), - }; - buffer_stream staging( - p_device, p_data.size(), staging_buffer_config); - - // 3. write data to the staging buffer with specific size specified - staging.transfer(p_data); - - // 4. start recording to this command buffer - VkImageLayout old_layout = VK_IMAGE_LAYOUT_UNDEFINED; - VkImageLayout new_layout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; - VkFormat texture_format = p_config.format; - - // 5. Creating temporary command buffer for texture - command_params copy_command_params = { - .levels = command_levels::primary, - .queue_index = 0, - .flags = command_pool_flags::reset, - }; - command_buffer temp_command_buffer = - command_buffer(p_device, copy_command_params); - - temp_command_buffer.begin(command_usage::one_time_submit); - - // 6. transition image layout - // Ensure that we are transferring our image data and correcting the - // format to ensure we do not lose any data in the process - texture_image.memory_barrier( - temp_command_buffer, texture_format, old_layout, new_layout); - - std::array region_copies = { - vk::buffer_image_copy{ - .image_offset = { .width = 0, .height = 0, .depth = 0, }, - .image_extent = { .width = p_config.extent.width, .height = p_config.extent.height, .depth = 1, }, - } - }; - - // staging.copy_to_image(temp_command_buffer, texture_image, - // p_config.extent); - staging.copy_to_image( - temp_command_buffer, texture_image, region_copies); - old_layout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; - new_layout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; - texture_image.memory_barrier( - temp_command_buffer, texture_format, old_layout, new_layout); - - temp_command_buffer.end(); - - // 8. Getting graphics queue to store the texture data for GPU - // access - // TODO: Extend vk::device_queue to enable perform command - // submission to the GPU - uint32_t queue_family_index = 0; - uint32_t queue_index = 0; - VkQueue temp_graphics_queue; - vkGetDeviceQueue( - p_device, queue_family_index, queue_index, &temp_graphics_queue); - - // 8. now submit that texture data to be stored in GPU memory - const VkCommandBuffer handle = temp_command_buffer; - VkSubmitInfo submit_info = { - .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO, - .commandBufferCount = 1, - .pCommandBuffers = &handle, - }; - - vkQueueSubmit(temp_graphics_queue, 1, &submit_info, nullptr); - vkQueueWaitIdle(temp_graphics_queue); - - temp_command_buffer.destroy(); - staging.destroy(); - - return texture_image; - } - - // TODO: Remove redundant struct and replace with vk::image_params - struct texture_params { - uint32_t memory_mask = 0; - uint32_t mip_levels = 1; - uint32_t layer_count = 1; - }; - class texture { public: - texture() = default; + texture() = delete; - // TODO: Replace these current parameters to using vk::image_params - // to make the API's consistent. texture(const VkDevice& p_device, const image_extent& p_extent, - uint32_t p_memory_mask) + std::span p_color, + uint32_t p_memory_mask, + uint32_t p_mip_levels = 1, + uint32_t p_layer_count = 1) : m_device(p_device) , m_extent(p_extent) { - // 1.) Load in extent dimensions - // White pixels for storing texture. - std::array white_color = { 0xFF, 0xFF, 0xFF, 0xFF }; - - image_params config_image = { - .extent = m_extent, - .format = static_cast(format::r8g8b8a8_unorm), - .memory_mask = p_memory_mask, - .usage = - static_cast(image_usage::transfer_dst_bit) | - static_cast(image_usage::sampled_bit), - }; - int bytes_per_pixel = - bytes_per_texture_format(config_image.format); - - // Ensuring we get pass in the correct image size with bytes per - // pixel - uint32_t layer_size_with_bytes = config_image.extent.width * - config_image.extent.height * - bytes_per_pixel; - uint32_t layer_count = 1; - uint32_t image_size = layer_size_with_bytes * layer_count; - - m_image = create_texture_with_data( - m_device, - config_image, - std::span(white_color.data(), image_size)); + construct(p_extent, + p_color, + p_memory_mask, + p_mip_levels, + p_layer_count); m_texture_loaded = true; } - // TODO: Replace these current parameters to using vk::image_params - // to make the API's consistent. texture(const VkDevice& p_device, - const std::filesystem::path& p_filename, + image* p_image, const texture_params& p_texture_params) : m_device(p_device) { - // 1. load from file - int w, h; - int channels; - // TODO: Make passing in the filepath an explicit parameter for - // loading in a texture - stbi_uc* image_pixel_data = - stbi_load(p_filename.string().c_str(), - &w, - &h, - &channels, - STBI_rgb_alpha); - m_extent = { - .width = static_cast(w), - .height = static_cast(h), - }; + construct(p_image, p_texture_params); + } + + void construct(image_extent p_extent, + std::span p_data, + uint32_t p_memory_mask, + uint32_t p_mip_levels = 1, + uint32_t p_layer_count = 1) { + m_extent = p_extent; const VkFormat texture_format = static_cast(format::r8g8b8a8_unorm); - int bytes_per_pixel = bytes_per_texture_format(texture_format); - // Ensuring we get pass in the correct image size - // with bytes per pixel - // uint32_t layer_size_with_bytes = - uint32_t image_layer_sizes_with_bytes = - m_extent.width * m_extent.height * bytes_per_pixel; + image_params img_options = { + .extent = p_extent, + .format = texture_format, + .memory_mask = p_memory_mask, + .usage = + static_cast(image_usage::transfer_dst_bit) | + static_cast(image_usage::sampled_bit), + .mip_levels = p_mip_levels, + .layer_count = p_layer_count, + }; - // uint32_t layer_count = 1; - uint32_t layer_count = p_texture_params.layer_count; - uint32_t image_size = - image_layer_sizes_with_bytes * layer_count; + m_image = sample_image(m_device, img_options); - image_params config_image = { - .extent = m_extent, + // Setup staging buffer + uint32_t property_flag = memory_property::host_visible_bit | + memory_property::host_cached_bit; + + buffer_parameters staging_options = { + .memory_mask = img_options.memory_mask, + .property_flags = + static_cast(property_flag), + .usage = + static_cast(buffer_usage::transfer_src_bit), + }; + buffer_stream staging(m_device, p_data.size(), staging_options); + + staging.transfer(p_data); + + // 5. Creating temporary command buffer for texture + command_params copy_command_params = { + .levels = command_levels::primary, + .queue_index = 0, + .flags = command_pool_flags::reset, + }; + command_buffer temp_command_buffer = + command_buffer(m_device, copy_command_params); + + temp_command_buffer.begin(command_usage::one_time_submit); + + // Performing image layouts + VkImageLayout old_layout = VK_IMAGE_LAYOUT_UNDEFINED; + VkImageLayout new_layout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; + m_image.memory_barrier(temp_command_buffer, + img_options.format, + old_layout, + new_layout); + + std::array region_copies = { + vk::buffer_image_copy{ + .image_offset = { .width = 0, .height = 0, .depth = 0, }, + .image_extent = { .width = img_options.extent.width, .height = img_options.extent.height, .depth = 1, }, + } + }; + + staging.copy_to_image( + temp_command_buffer, m_image, region_copies); + + old_layout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; + new_layout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; + m_image.memory_barrier(temp_command_buffer, + img_options.format, + old_layout, + new_layout); + + temp_command_buffer.end(); + + uint32_t queue_family = 0; + uint32_t queue_index = 0; + VkQueue temp_graphics_queue = nullptr; + vkGetDeviceQueue( + m_device, queue_family, queue_index, &temp_graphics_queue); + + const VkCommandBuffer handle = temp_command_buffer; + VkSubmitInfo submit_info = { + .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO, + .commandBufferCount = 1, + .pCommandBuffers = &handle, + }; + + vkQueueSubmit(temp_graphics_queue, 1, &submit_info, nullptr); + vkQueueWaitIdle(temp_graphics_queue); + + temp_command_buffer.destroy(); + staging.destroy(); + } + + void construct(image* p_image, + const texture_params& p_texture_params) { + m_extent = p_image->extent(); + + const VkFormat texture_format = + static_cast(format::r8g8b8a8_unorm); + // NOTE To Self: Essentially passed to p_config parameter in + // create_texture_with_data + image_params img_options = { + .extent = p_image->extent(), .format = texture_format, .memory_mask = p_texture_params.memory_mask, .usage = @@ -207,22 +160,80 @@ export namespace vk { .layer_count = p_texture_params.layer_count, }; - // Ensures the image data is valid before continuing - // We can check the state of the image if it loaded succesfully - if (!image_pixel_data) { - m_texture_loaded = false; - return; - } - - // Creating Image Handle + Loading Image Pixel Data - m_image = create_texture_with_data( - p_device, - config_image, - std::span(image_pixel_data, image_size)); - - // Proper cleanup of the image data - stbi_image_free(image_pixel_data); - m_texture_loaded = true; + m_image = sample_image(m_device, img_options); + + // Setup staging buffer + uint32_t property_flag = memory_property::host_visible_bit | + memory_property::host_cached_bit; + + buffer_parameters staging_options = { + .memory_mask = img_options.memory_mask, + .property_flags = + static_cast(property_flag), + .usage = + static_cast(buffer_usage::transfer_src_bit), + }; + buffer_stream staging( + m_device, p_image->read().size(), staging_options); + + staging.transfer(p_image->read()); + + // 5. Creating temporary command buffer for texture + command_params copy_command_params = { + .levels = command_levels::primary, + .queue_index = 0, + .flags = command_pool_flags::reset, + }; + command_buffer temp_command_buffer = + command_buffer(m_device, copy_command_params); + + temp_command_buffer.begin(command_usage::one_time_submit); + + // Performing image layouts + VkImageLayout old_layout = VK_IMAGE_LAYOUT_UNDEFINED; + VkImageLayout new_layout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; + m_image.memory_barrier(temp_command_buffer, + img_options.format, + old_layout, + new_layout); + + std::array region_copies = { + vk::buffer_image_copy{ + .image_offset = { .width = 0, .height = 0, .depth = 0, }, + .image_extent = { .width = img_options.extent.width, .height = img_options.extent.height, .depth = 1, }, + } + }; + + staging.copy_to_image( + temp_command_buffer, m_image, region_copies); + + old_layout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; + new_layout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; + m_image.memory_barrier(temp_command_buffer, + img_options.format, + old_layout, + new_layout); + + temp_command_buffer.end(); + + uint32_t queue_family = 0; + uint32_t queue_index = 0; + VkQueue temp_graphics_queue = nullptr; + vkGetDeviceQueue( + m_device, queue_family, queue_index, &temp_graphics_queue); + + const VkCommandBuffer handle = temp_command_buffer; + VkSubmitInfo submit_info = { + .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO, + .commandBufferCount = 1, + .pCommandBuffers = &handle, + }; + + vkQueueSubmit(temp_graphics_queue, 1, &submit_info, nullptr); + vkQueueWaitIdle(temp_graphics_queue); + + temp_command_buffer.destroy(); + staging.destroy(); } //! @return true if image loaded, false if texture did not load @@ -239,7 +250,8 @@ export namespace vk { VkDevice m_device = nullptr; bool m_texture_loaded = false; sample_image m_image{}; - image_extent m_extent{}; + image_extent m_extent; + class image* m_image_loader = nullptr; }; }; }; \ No newline at end of file diff --git a/vulkan-cpp/types.cppm b/vulkan-cpp/types.cppm index 1083d53..316123f 100644 --- a/vulkan-cpp/types.cppm +++ b/vulkan-cpp/types.cppm @@ -1604,6 +1604,13 @@ export namespace vk { uint32_t addrses_mode_w = sampler_address_mode::repeat; }; + // TODO: Remove redundant struct and replace with vk::image_params + struct texture_params { + uint32_t memory_mask = 0; + uint32_t mip_levels = 1; + uint32_t layer_count = 1; + }; + struct buffer_image_copy { uint32_t offset = 0; uint32_t row_length = 0; diff --git a/vulkan-cpp/vk.cppm b/vulkan-cpp/vk.cppm index 5a5e7b9..ac285de 100644 --- a/vulkan-cpp/vk.cppm +++ b/vulkan-cpp/vk.cppm @@ -23,6 +23,7 @@ export import :uniform_buffer; export import :descriptor_resource; export import :texture; export import :buffer_device_address; +export import :image; namespace vk { inline namespace v1 {};