diff --git a/antora/modules/ROOT/nav.adoc b/antora/modules/ROOT/nav.adoc index 4e2eba447..693f45943 100644 --- a/antora/modules/ROOT/nav.adoc +++ b/antora/modules/ROOT/nav.adoc @@ -30,6 +30,7 @@ ** xref:samples/api/hdr/README.adoc[HDR] *** xref:samples/api/hpp_hdr/README.adoc[HDR (Vulkan-Hpp)] ** xref:samples/api/hello_triangle/README.adoc[Hello Triangle] +** xref:samples/api/hello_triangle_1_3/README.adoc[Hello Triangle 1.3] *** xref:samples/api/hpp_hello_triangle/README.adoc[Hello Triangle (Vulkan-Hpp)] ** xref:samples/api/hlsl_shaders/README.adoc[HLSL Shaders] *** xref:samples/api/hpp_hlsl_shaders/README.adoc[HLSL Shaders (Vulkan-Hpp)] diff --git a/samples/api/README.adoc b/samples/api/README.adoc index 0cdd8b0ad..b91f7db51 100644 --- a/samples/api/README.adoc +++ b/samples/api/README.adoc @@ -38,6 +38,10 @@ Implements a high dynamic range rendering pipeline using 16/32 bit floating poin A self-contained (minimal use of framework) sample that illustrates the rendering of a triangle. +=== xref:./{api_samplespath}hello_triangle/README.adoc[Hello Triangle 1.3] + +A self-contained (minimal use of framework) sample that illustrates the rendering of a triangle using Vulkan 1.3 features. + === xref:./{api_samplespath}hpp_compute_nbody/README.adoc[HPP Compute shader N-Body simulation] A transcoded version of the API sample <> that illustrates the usage of the C{pp} bindings of Vulkan provided by vulkan.hpp. diff --git a/samples/api/hello_triangle_1_3/CMakeLists.txt b/samples/api/hello_triangle_1_3/CMakeLists.txt new file mode 100644 index 000000000..10651d625 --- /dev/null +++ b/samples/api/hello_triangle_1_3/CMakeLists.txt @@ -0,0 +1,33 @@ +# Copyright (c) 2024, Huawei Technologies Co., Ltd. +# +# SPDX-License-Identifier: Apache-2.0 +# +# Licensed under the Apache License, Version 2.0 the "License"; +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +set(CMAKE_CXX_STANDARD 20) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_EXTENSIONS OFF) + +get_filename_component(FOLDER_NAME ${CMAKE_CURRENT_LIST_DIR} NAME) +get_filename_component(PARENT_DIR ${CMAKE_CURRENT_LIST_DIR} PATH) +get_filename_component(CATEGORY_NAME ${PARENT_DIR} NAME) + +add_sample( + ID ${FOLDER_NAME} + CATEGORY ${CATEGORY_NAME} + AUTHOR "Huawei Technologies Co., Ltd." + NAME "Vulkan 1.3 Hello Triangle" + DESCRIPTION "An introduction into Vulkan using Vulkan 1.3" + SHADER_FILES_GLSL + "hello_triangle_1_3/triangle.vert" + "hello_triangle_1_3/triangle.frag") \ No newline at end of file diff --git a/samples/api/hello_triangle_1_3/README.adoc b/samples/api/hello_triangle_1_3/README.adoc new file mode 100644 index 000000000..efca4a9fd --- /dev/null +++ b/samples/api/hello_triangle_1_3/README.adoc @@ -0,0 +1,25 @@ +/* Copyright (c) 2024, Huawei Technologies Co., Ltd. + * + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 the "License"; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + += Vulkan 1.3 Hello Triangle + +ifdef::site-gen-antora[] +TIP: The source for this sample can be found in the https://github.com/KhronosGroup/Vulkan-Samples/tree/main/samples/api/hello_triangle_1_3[Khronos Vulkan samples github repository]. +endif::[] + + +A self-contained sample that illustrates the rendering of a triangle using Vulkan 1.3 using features like dynamic rendering, synchronization2 and pipeline dynamic state . diff --git a/samples/api/hello_triangle_1_3/hello_triangle_1_3.cpp b/samples/api/hello_triangle_1_3/hello_triangle_1_3.cpp new file mode 100644 index 000000000..de43a164b --- /dev/null +++ b/samples/api/hello_triangle_1_3/hello_triangle_1_3.cpp @@ -0,0 +1,1340 @@ +/* Copyright (c) 2024, Huawei Technologies Co., Ltd. + * + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 the "License"; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "hello_triangle_1_3.h" + +#include "common/vk_common.h" +#include "core/util/logging.hpp" +#include "filesystem/legacy.h" +#include "glsl_compiler.h" +#include "platform/window.h" + +#if defined(VKB_DEBUG) || defined(VKB_VALIDATION_LAYERS) +/// @brief A debug callback called from Vulkan validation layers. +static VKAPI_ATTR VkBool32 VKAPI_CALL debug_callback(VkDebugUtilsMessageSeverityFlagBitsEXT message_severity, + VkDebugUtilsMessageTypeFlagsEXT message_types, + const VkDebugUtilsMessengerCallbackDataEXT *callback_data, + void *user_data) +{ + if (message_severity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT) + { + LOGE("{} Validation Layer: Error: {}: {}", callback_data->messageIdNumber, callback_data->pMessageIdName, callback_data->pMessage); + } + else if (message_severity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT) + { + LOGW("{} Validation Layer: Warning: {}: {}", callback_data->messageIdNumber, callback_data->pMessageIdName, callback_data->pMessage); + } + else if (message_severity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT) + { + LOGI("{} Validation Layer: Information: {}: {}", callback_data->messageIdNumber, callback_data->pMessageIdName, callback_data->pMessage); + } + else if (message_severity & VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT) + { + LOGI("{} Validation Layer: Performance warning: {}: {}", callback_data->messageIdNumber, callback_data->pMessageIdName, callback_data->pMessage); + } + else if (message_severity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT) + { + LOGD("{} Validation Layer: Verbose: {}: {}", callback_data->messageIdNumber, callback_data->pMessageIdName, callback_data->pMessage); + } + return VK_FALSE; +} +#endif + +/** + * @brief Validates a list of required extensions, comparing it with the available ones. + * + * @param required A vector containing required extension names. + * @param available A VkExtensionProperties object containing available extensions. + * @return true if all required extensions are available + * @return false otherwise + */ +bool HelloTriangleV13::validate_extensions(const std::vector &required, + const std::vector &available) +{ + bool all_found = true; + + for (const auto *extension_name : required) + { + bool found = false; + for (const auto &available_extension : available) + { + if (strcmp(available_extension.extensionName, extension_name) == 0) + { + found = true; + break; + } + } + + if (!found) + { + // Output an error message for the missing extension + LOGE("Error: Required extension not found: {}", extension_name); + all_found = false; + } + } + + return all_found; +} + +/** + * @brief Validates a list of required layers, comparing it with the available ones. + * + * @param required A vector containing required layer names. + * @param available A VkLayerProperties object containing available layers. + * @return true if all required extensions are available + * @return false otherwise + */ +bool HelloTriangleV13::validate_layers(const std::vector &required, + const std::vector &available) +{ + bool all_found = true; + + for (const auto *layer_name : required) + { + bool found = false; + for (const auto &available_layer : available) + { + if (strcmp(available_layer.layerName, layer_name) == 0) + { + found = true; + break; + } + } + + if (!found) + { + LOGE("Error: Required layer not found: {}", layer_name); + all_found = false; + } + } + + return all_found; +} + +/** + * @brief Find the vulkan shader stage for a given a string. + * + * @param ext A string containing the shader stage name. + * @return VkShaderStageFlagBits The shader stage mapping from the given string, VK_SHADER_STAGE_VERTEX_BIT otherwise. + */ +VkShaderStageFlagBits HelloTriangleV13::find_shader_stage(const std::string &ext) +{ + if (ext == "vert") + { + return VK_SHADER_STAGE_VERTEX_BIT; + } + else if (ext == "frag") + { + return VK_SHADER_STAGE_FRAGMENT_BIT; + } + else if (ext == "comp") + { + return VK_SHADER_STAGE_COMPUTE_BIT; + } + else if (ext == "geom") + { + return VK_SHADER_STAGE_GEOMETRY_BIT; + } + else if (ext == "tesc") + { + return VK_SHADER_STAGE_TESSELLATION_CONTROL_BIT; + } + else if (ext == "tese") + { + return VK_SHADER_STAGE_TESSELLATION_EVALUATION_BIT; + } + + throw std::runtime_error("No Vulkan shader stage found for the file extension name."); +}; + +/** + * @brief Initializes the Vulkan instance. + */ +void HelloTriangleV13::init_instance() +{ + LOGI("Initializing Vulkan instance."); + + if (volkInitialize()) + { + throw std::runtime_error("Failed to initialize volk."); + } + + uint32_t instance_extension_count; + VK_CHECK(vkEnumerateInstanceExtensionProperties(nullptr, &instance_extension_count, nullptr)); + + std::vector available_instance_extensions(instance_extension_count); + VK_CHECK(vkEnumerateInstanceExtensionProperties(nullptr, &instance_extension_count, available_instance_extensions.data())); + + std::vector required_instance_extensions{VK_KHR_SURFACE_EXTENSION_NAME}; + +#if defined(VKB_DEBUG) || defined(VKB_VALIDATION_LAYERS) + bool has_debug_utils = false; + for (const auto &ext : available_instance_extensions) + { + if (strncmp(ext.extensionName, VK_EXT_DEBUG_UTILS_EXTENSION_NAME, strlen(VK_EXT_DEBUG_UTILS_EXTENSION_NAME)) == 0) + { + has_debug_utils = true; + break; + } + } + if (has_debug_utils) + { + required_instance_extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); + } + else + { + LOGW("{} is not available; disabling debug utils messenger", VK_EXT_DEBUG_UTILS_EXTENSION_NAME); + } +#endif + +#if defined(VK_USE_PLATFORM_ANDROID_KHR) + required_instance_extensions.push_back(VK_KHR_ANDROID_SURFACE_EXTENSION_NAME); +#elif defined(VK_USE_PLATFORM_WIN32_KHR) + required_instance_extensions.push_back(VK_KHR_WIN32_SURFACE_EXTENSION_NAME); +#elif defined(VK_USE_PLATFORM_METAL_EXT) + required_instance_extensions.push_back(VK_EXT_METAL_SURFACE_EXTENSION_NAME); +#elif defined(VK_USE_PLATFORM_XCB_KHR) + required_instance_extensions.push_back(VK_KHR_XCB_SURFACE_EXTENSION_NAME); +#elif defined(VK_USE_PLATFORM_XLIB_KHR) + required_instance_extensions.push_back(VK_KHR_XLIB_SURFACE_EXTENSION_NAME); +#elif defined(VK_USE_PLATFORM_WAYLAND_KHR) + required_instance_extensions.push_back(VK_KHR_WAYLAND_SURFACE_EXTENSION_NAME); +#elif defined(VK_USE_PLATFORM_DISPLAY_KHR) + required_instance_extensions.push_back(VK_KHR_DISPLAY_EXTENSION_NAME); +#else +# pragma error Platform not supported +#endif + + if (!validate_extensions(required_instance_extensions, available_instance_extensions)) + { + throw std::runtime_error("Required instance extensions are missing."); + } + + uint32_t instance_layer_count; + VK_CHECK(vkEnumerateInstanceLayerProperties(&instance_layer_count, nullptr)); + + std::vector supported_validation_layers(instance_layer_count); + VK_CHECK(vkEnumerateInstanceLayerProperties(&instance_layer_count, supported_validation_layers.data())); + + std::vector requested_validation_layers{}; + +#ifdef VKB_VALIDATION_LAYERS + // Determine the optimal validation layers to enable that are necessary for useful debugging + std::vector optimal_validation_layers = vkb::get_optimal_validation_layers(supported_validation_layers); + requested_validation_layers.insert(requested_validation_layers.end(), optimal_validation_layers.begin(), optimal_validation_layers.end()); +#endif + + if (validate_layers(requested_validation_layers, supported_validation_layers)) + { + LOGI("Enabled Validation Layers:"); + for (const auto &layer : requested_validation_layers) + { + LOGI(" \t{}", layer); + } + } + else + { + throw std::runtime_error("Required validation layers are missing."); + } + + VkApplicationInfo app{ + .sType = VK_STRUCTURE_TYPE_APPLICATION_INFO, + .pApplicationName = "Hello Triangle V1.3", + .pEngineName = "Vulkan Samples", + .apiVersion = VK_MAKE_VERSION(1, 3, 0)}; + + VkInstanceCreateInfo instance_info{ + .sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO, + .pApplicationInfo = &app, + .enabledLayerCount = vkb::to_u32(requested_validation_layers.size()), + .ppEnabledLayerNames = requested_validation_layers.data(), + .enabledExtensionCount = vkb::to_u32(required_instance_extensions.size()), + .ppEnabledExtensionNames = required_instance_extensions.data()}; + +#if defined(VKB_DEBUG) || defined(VKB_VALIDATION_LAYERS) + VkDebugUtilsMessengerCreateInfoEXT debug_messenger_create_info = {VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT}; + if (has_debug_utils) + { + debug_messenger_create_info.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT; + debug_messenger_create_info.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT; + debug_messenger_create_info.pfnUserCallback = debug_callback; + + instance_info.pNext = &debug_messenger_create_info; + } +#endif + +#if (defined(VKB_ENABLE_PORTABILITY)) + if (portability_enumeration_available) + { + instance_info.flags |= VK_INSTANCE_CREATE_ENUMERATE_PORTABILITY_BIT_KHR; + } +#endif + + // Create the Vulkan instance + VK_CHECK(vkCreateInstance(&instance_info, nullptr, &context.instance)); + + volkLoadInstance(context.instance); + +#if defined(VKB_DEBUG) || defined(VKB_VALIDATION_LAYERS) + if (has_debug_utils) + { + VK_CHECK(vkCreateDebugUtilsMessengerEXT(context.instance, &debug_messenger_create_info, nullptr, &context.debug_callback)); + } +#endif +} + +/** + * @brief Initializes the Vulkan physical device and logical device. + */ +void HelloTriangleV13::init_device() +{ + LOGI("Initializing Vulkan device."); + + uint32_t gpu_count = 0; + VK_CHECK(vkEnumeratePhysicalDevices(context.instance, &gpu_count, nullptr)); + + if (gpu_count < 1) + { + throw std::runtime_error("No physical device found."); + } + + std::vector gpus(gpu_count); + VK_CHECK(vkEnumeratePhysicalDevices(context.instance, &gpu_count, gpus.data())); + + for (const auto &physical_device : gpus) + { + // Check if the device supports Vulkan 1.3 + VkPhysicalDeviceProperties device_properties; + vkGetPhysicalDeviceProperties(physical_device, &device_properties); + + uint32_t major_version = VK_API_VERSION_MAJOR(device_properties.apiVersion); + uint32_t minor_version = VK_API_VERSION_MINOR(device_properties.apiVersion); + + if ((major_version < 1) || (major_version == 1 && minor_version < 3)) + { + LOGW("Physical device '{}' does not support Vulkan 1.3, skipping.", device_properties.deviceName); + continue; + } + + // Find a queue family that supports graphics and presentation + uint32_t queue_family_count = 0; + vkGetPhysicalDeviceQueueFamilyProperties(physical_device, &queue_family_count, nullptr); + + std::vector queue_family_properties(queue_family_count); + vkGetPhysicalDeviceQueueFamilyProperties(physical_device, &queue_family_count, queue_family_properties.data()); + + for (uint32_t i = 0; i < queue_family_count; i++) + { + VkBool32 supports_present = VK_FALSE; + vkGetPhysicalDeviceSurfaceSupportKHR(physical_device, i, context.surface, &supports_present); + + if ((queue_family_properties[i].queueFlags & VK_QUEUE_GRAPHICS_BIT) && supports_present) + { + context.graphics_queue_index = i; + break; + } + } + + if (context.graphics_queue_index >= 0) + { + context.gpu = physical_device; + break; + } + } + + if (context.graphics_queue_index < 0) + { + throw std::runtime_error("Failed to find a suitable GPU with Vulkan 1.3 support."); + } + + uint32_t device_extension_count; + + VK_CHECK(vkEnumerateDeviceExtensionProperties(context.gpu, nullptr, &device_extension_count, nullptr)); + + std::vector device_extensions(device_extension_count); + + VK_CHECK(vkEnumerateDeviceExtensionProperties(context.gpu, nullptr, &device_extension_count, device_extensions.data())); + + // Since this sample has visual output, the device needs to support the swapchain extension + std::vector required_device_extensions{VK_KHR_SWAPCHAIN_EXTENSION_NAME}; + + if (!validate_extensions(required_device_extensions, device_extensions)) + { + throw std::runtime_error("Required device extensions are missing"); + } + + // Query for Vulkan 1.3 features + VkPhysicalDeviceFeatures2 device_features2{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2}; + VkPhysicalDeviceVulkan13Features vulkan13_features{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_3_FEATURES}; + VkPhysicalDeviceExtendedDynamicStateFeaturesEXT extended_dynamic_state_features{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTENDED_DYNAMIC_STATE_FEATURES_EXT}; + device_features2.pNext = &vulkan13_features; + vulkan13_features.pNext = &extended_dynamic_state_features; + + vkGetPhysicalDeviceFeatures2(context.gpu, &device_features2); + + // Enable required Vulkan 1.3 features + vulkan13_features.dynamicRendering = VK_TRUE; + vulkan13_features.synchronization2 = VK_TRUE; + extended_dynamic_state_features.extendedDynamicState = VK_TRUE; + + // Create the logical device + + float queue_priority = 1.0f; + + // Create one queue + VkDeviceQueueCreateInfo queue_info{ + .sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO, + .queueFamilyIndex = static_cast(context.graphics_queue_index), + .queueCount = 1, + .pQueuePriorities = &queue_priority}; + + VkDeviceCreateInfo device_info{ + .sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO, + .pNext = &device_features2, + .queueCreateInfoCount = 1, + .pQueueCreateInfos = &queue_info, + .enabledExtensionCount = vkb::to_u32(required_device_extensions.size()), + .ppEnabledExtensionNames = required_device_extensions.data()}; + + VK_CHECK(vkCreateDevice(context.gpu, &device_info, nullptr, &context.device)); + volkLoadDevice(context.device); + + vkGetDeviceQueue(context.device, context.graphics_queue_index, 0, &context.queue); +} + +/** + * @brief Initializes the vertex buffer by creating it, allocating memory, binding the memory, and uploading vertex data. + * @note This function must be called after the Vulkan device has been initialized. + * @throws std::runtime_error if any Vulkan operation fails. + */ +void HelloTriangleV13::init_vertex_buffer() +{ + VkDeviceSize buffer_size = sizeof(vertices[0]) * vertices.size(); + + // Create the vertex buffer + VkBufferCreateInfo vertext_buffer_info{ + .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, + .flags = VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, + .size = buffer_size, + .sharingMode = VK_SHARING_MODE_EXCLUSIVE}; + + VK_CHECK(vkCreateBuffer(context.device, &vertext_buffer_info, nullptr, &context.vertex_buffer)); + + // Get memory requirements + VkMemoryRequirements memory_requirements; + vkGetBufferMemoryRequirements(context.device, context.vertex_buffer, &memory_requirements); + + // Allocate memory for the buffer + VkMemoryAllocateInfo alloc_info{ + .sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, + .allocationSize = memory_requirements.size, + .memoryTypeIndex = find_memory_type(context.gpu, memory_requirements.memoryTypeBits, + VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT)}; + + VK_CHECK(vkAllocateMemory(context.device, &alloc_info, nullptr, &context.vertex_buffer_memory)); + + // Bind the buffer with the allocated memory + VK_CHECK(vkBindBufferMemory(context.device, context.vertex_buffer, context.vertex_buffer_memory, 0)); + + // Map the memory and copy the vertex data + void *data; + VK_CHECK(vkMapMemory(context.device, context.vertex_buffer_memory, 0, buffer_size, 0, &data)); + memcpy(data, vertices.data(), static_cast(buffer_size)); + vkUnmapMemory(context.device, context.vertex_buffer_memory); +} + +/** + * @brief Finds a suitable memory type index for allocating memory. + * + * This function searches through the physical device's memory types to find one that matches + * the requirements specified by `type_filter` and `properties`. It's typically used when allocating + * memory for buffers or images, ensuring that the memory type supports the desired properties. + * + * @param physical_device The Vulkan physical device to query for memory properties. + * @param type_filter A bitmask specifying the acceptable memory types. + * This is usually obtained from `VkMemoryRequirements::memoryTypeBits`. + * @param properties A bitmask specifying the desired memory properties, + * such as `VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT` or `VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT`. + * @return The index of a suitable memory type. + * @throws std::runtime_error if no suitable memory type is found. + */ +uint32_t HelloTriangleV13::find_memory_type(VkPhysicalDevice physical_device, uint32_t type_filter, VkMemoryPropertyFlags properties) +{ + // Structure to hold the physical device's memory properties + VkPhysicalDeviceMemoryProperties mem_properties; + vkGetPhysicalDeviceMemoryProperties(physical_device, &mem_properties); + + // Iterate over all memory types available on the physical device + for (uint32_t i = 0; i < mem_properties.memoryTypeCount; i++) + { + // Check if the current memory type is acceptable based on the type_filter + // The type_filter is a bitmask where each bit represents a memory type that is suitable + if (type_filter & (1 << i)) + { + // Check if the memory type has all the desired property flags + // properties is a bitmask of the required memory properties + if ((mem_properties.memoryTypes[i].propertyFlags & properties) == properties) + { + // Found a suitable memory type; return its index + return i; + } + } + } + + // If no suitable memory type was found, throw an exception + throw std::runtime_error("Failed to find suitable memory type."); +} +/** + * @brief Initializes per frame data. + * @param per_frame The data of a frame. + */ +void HelloTriangleV13::init_per_frame(PerFrame &per_frame) +{ + VkFenceCreateInfo info{ + .sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO, + .flags = VK_FENCE_CREATE_SIGNALED_BIT}; + VK_CHECK(vkCreateFence(context.device, &info, nullptr, &per_frame.queue_submit_fence)); + + VkCommandPoolCreateInfo cmd_pool_info{ + .sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO, + .flags = VK_COMMAND_POOL_CREATE_TRANSIENT_BIT, + .queueFamilyIndex = static_cast(context.graphics_queue_index)}; + VK_CHECK(vkCreateCommandPool(context.device, &cmd_pool_info, nullptr, &per_frame.primary_command_pool)); + + VkCommandBufferAllocateInfo cmd_buf_info{ + .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO, + .commandPool = per_frame.primary_command_pool, + .level = VK_COMMAND_BUFFER_LEVEL_PRIMARY, + .commandBufferCount = 1}; + VK_CHECK(vkAllocateCommandBuffers(context.device, &cmd_buf_info, &per_frame.primary_command_buffer)); +} + +/** + * @brief Tears down the frame data. + * @param per_frame The data of a frame. + */ +void HelloTriangleV13::teardown_per_frame(PerFrame &per_frame) +{ + if (per_frame.queue_submit_fence != VK_NULL_HANDLE) + { + vkDestroyFence(context.device, per_frame.queue_submit_fence, nullptr); + + per_frame.queue_submit_fence = VK_NULL_HANDLE; + } + + if (per_frame.primary_command_buffer != VK_NULL_HANDLE) + { + vkFreeCommandBuffers(context.device, per_frame.primary_command_pool, 1, &per_frame.primary_command_buffer); + + per_frame.primary_command_buffer = VK_NULL_HANDLE; + } + + if (per_frame.primary_command_pool != VK_NULL_HANDLE) + { + vkDestroyCommandPool(context.device, per_frame.primary_command_pool, nullptr); + + per_frame.primary_command_pool = VK_NULL_HANDLE; + } + + if (per_frame.swapchain_acquire_semaphore != VK_NULL_HANDLE) + { + vkDestroySemaphore(context.device, per_frame.swapchain_acquire_semaphore, nullptr); + + per_frame.swapchain_acquire_semaphore = VK_NULL_HANDLE; + } + + if (per_frame.swapchain_release_semaphore != VK_NULL_HANDLE) + { + vkDestroySemaphore(context.device, per_frame.swapchain_release_semaphore, nullptr); + + per_frame.swapchain_release_semaphore = VK_NULL_HANDLE; + } +} + +/** + * @brief Initializes the Vulkan swapchain. + */ +void HelloTriangleV13::init_swapchain() +{ + VkSurfaceCapabilitiesKHR surface_properties; + VK_CHECK(vkGetPhysicalDeviceSurfaceCapabilitiesKHR(context.gpu, context.surface, &surface_properties)); + + VkSurfaceFormatKHR format = vkb::select_surface_format(context.gpu, context.surface); + + VkExtent2D swapchain_size; + if (surface_properties.currentExtent.width == 0xFFFFFFFF) + { + swapchain_size.width = context.swapchain_dimensions.width; + swapchain_size.height = context.swapchain_dimensions.height; + } + else + { + swapchain_size = surface_properties.currentExtent; + } + + // FIFO must be supported by all implementations. + VkPresentModeKHR swapchain_present_mode = VK_PRESENT_MODE_FIFO_KHR; + + // Determine the number of VkImage's to use in the swapchain. + // Ideally, we desire to own 1 image at a time, the rest of the images can + // either be rendered to and/or being queued up for display. + uint32_t desired_swapchain_images = surface_properties.minImageCount + 1; + if ((surface_properties.maxImageCount > 0) && (desired_swapchain_images > surface_properties.maxImageCount)) + { + // Application must settle for fewer images than desired. + desired_swapchain_images = surface_properties.maxImageCount; + } + + // Figure out a suitable surface transform. + VkSurfaceTransformFlagBitsKHR pre_transform; + if (surface_properties.supportedTransforms & VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR) + { + pre_transform = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR; + } + else + { + pre_transform = surface_properties.currentTransform; + } + + VkSwapchainKHR old_swapchain = context.swapchain; + + //one bitmask needs to be set according to the priority of presentation engine + VkCompositeAlphaFlagBitsKHR composite = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR; + if (surface_properties.supportedCompositeAlpha & VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR) + { + composite = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR; + } + else if (surface_properties.supportedCompositeAlpha & VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR) + { + composite = VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR; + } + else if (surface_properties.supportedCompositeAlpha & VK_COMPOSITE_ALPHA_PRE_MULTIPLIED_BIT_KHR) + { + composite = VK_COMPOSITE_ALPHA_PRE_MULTIPLIED_BIT_KHR; + } + else if (surface_properties.supportedCompositeAlpha & VK_COMPOSITE_ALPHA_POST_MULTIPLIED_BIT_KHR) + { + composite = VK_COMPOSITE_ALPHA_POST_MULTIPLIED_BIT_KHR; + } + + VkSwapchainCreateInfoKHR info{ + .sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR, + .surface = context.surface, // The surface onto which images will be presented + .minImageCount = desired_swapchain_images, // Minimum number of images in the swapchain (number of buffers) + .imageFormat = format.format, // Format of the swapchain images (e.g., VK_FORMAT_B8G8R8A8_SRGB) + .imageColorSpace = format.colorSpace, // Color space of the images (e.g., VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) + .imageExtent = swapchain_size, // Resolution of the swapchain images (width and height) + .imageArrayLayers = 1, // Number of layers in each image (usually 1 unless stereoscopic) + .imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT, // How the images will be used (as color attachments) + .imageSharingMode = VK_SHARING_MODE_EXCLUSIVE, // Access mode of the images (exclusive to one queue family) + .preTransform = pre_transform, // Transform to apply to images (e.g., rotation) + .compositeAlpha = composite, // Alpha blending to apply (e.g., opaque, pre-multiplied) + .presentMode = swapchain_present_mode, // Presentation mode (e.g., vsync settings) + .clipped = true, // Whether to clip obscured pixels (improves performance) + .oldSwapchain = old_swapchain}; // Handle to the old swapchain, if replacing an existing one + + VK_CHECK(vkCreateSwapchainKHR(context.device, &info, nullptr, &context.swapchain)); + + if (old_swapchain != VK_NULL_HANDLE) + { + for (VkImageView image_view : context.swapchain_image_views) + { + vkDestroyImageView(context.device, image_view, nullptr); + } + + uint32_t image_count; + VK_CHECK(vkGetSwapchainImagesKHR(context.device, old_swapchain, &image_count, nullptr)); + + for (size_t i = 0; i < image_count; i++) + { + teardown_per_frame(context.per_frame[i]); + } + + context.swapchain_image_views.clear(); + + vkDestroySwapchainKHR(context.device, old_swapchain, nullptr); + } + + context.swapchain_dimensions = {swapchain_size.width, swapchain_size.height, format.format}; + + uint32_t image_count; + VK_CHECK(vkGetSwapchainImagesKHR(context.device, context.swapchain, &image_count, nullptr)); + + /// The swapchain images. + std::vector swapchain_images(image_count); + VK_CHECK(vkGetSwapchainImagesKHR(context.device, context.swapchain, &image_count, swapchain_images.data())); + + // Store swapchain images + context.swapchain_images = swapchain_images; + + // Initialize per-frame resources. + // Every swapchain image has its own command pool and fence manager. + // This makes it very easy to keep track of when we can reset command buffers and such. + context.per_frame.clear(); + context.per_frame.resize(image_count); + + for (size_t i = 0; i < image_count; i++) + { + init_per_frame(context.per_frame[i]); + } + + for (size_t i = 0; i < image_count; i++) + { + // Create an image view which we can render into. + VkImageViewCreateInfo view_info{ + .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + .image = swapchain_images[i], + .viewType = VK_IMAGE_VIEW_TYPE_2D, + .format = context.swapchain_dimensions.format, + .components = { + .r = VK_COMPONENT_SWIZZLE_R, + .g = VK_COMPONENT_SWIZZLE_G, + .b = VK_COMPONENT_SWIZZLE_B, + .a = VK_COMPONENT_SWIZZLE_A}, + .subresourceRange = {.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, .baseMipLevel = 0, .levelCount = 1, .baseArrayLayer = 0, .layerCount = 1}}; + + VkImageView image_view; + VK_CHECK(vkCreateImageView(context.device, &view_info, nullptr, &image_view)); + + context.swapchain_image_views.push_back(image_view); + } +} +/** + * @brief Helper function to load a shader module. + * @param path The path for the shader (relative to the assets directory). + * @returns A VkShaderModule handle. Aborts execution if shader creation fails. + */ +VkShaderModule HelloTriangleV13::load_shader_module(const char *path) +{ + vkb::GLSLCompiler glsl_compiler; + + auto buffer = vkb::fs::read_shader_binary(path); + + std::string file_ext = path; + + // Extract extension name from the glsl shader file + file_ext = file_ext.substr(file_ext.find_last_of(".") + 1); + + std::vector spirv; + std::string info_log; + + // Compile the GLSL source + if (!glsl_compiler.compile_to_spirv(find_shader_stage(file_ext), buffer, "main", {}, spirv, info_log)) + { + LOGE("Failed to compile shader, Error: {}", info_log.c_str()); + return VK_NULL_HANDLE; + } + + VkShaderModuleCreateInfo module_info{ + .sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO, + .codeSize = spirv.size() * sizeof(uint32_t), + .pCode = spirv.data()}; + + VkShaderModule shader_module; + VK_CHECK(vkCreateShaderModule(context.device, &module_info, nullptr, &shader_module)); + + return shader_module; +} + +/** + * @brief Initializes the Vulkan pipeline. + */ +void HelloTriangleV13::init_pipeline() +{ + // Create a blank pipeline layout. + // We are not binding any resources to the pipeline in this first sample. + VkPipelineLayoutCreateInfo layout_info{VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO}; + VK_CHECK(vkCreatePipelineLayout(context.device, &layout_info, nullptr, &context.pipeline_layout)); + + // Define the vertex input binding description + VkVertexInputBindingDescription binding_description{ + .binding = 0, + .stride = sizeof(Vertex), + .inputRate = VK_VERTEX_INPUT_RATE_VERTEX}; + + // Define the vertex input attribute descriptions + std::array attribute_descriptions{}; + + attribute_descriptions[0].binding = 0; + attribute_descriptions[0].location = 0; + attribute_descriptions[0].format = VK_FORMAT_R32G32_SFLOAT; // vec2 for position + attribute_descriptions[0].offset = offsetof(Vertex, position); + + attribute_descriptions[1].binding = 0; + attribute_descriptions[1].location = 1; + attribute_descriptions[1].format = VK_FORMAT_R32G32B32_SFLOAT; // vec3 for color + attribute_descriptions[1].offset = offsetof(Vertex, color); + + // Create the vertex input state + VkPipelineVertexInputStateCreateInfo vertex_input{.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO, + .vertexBindingDescriptionCount = 1, + .pVertexBindingDescriptions = &binding_description, + .vertexAttributeDescriptionCount = static_cast(attribute_descriptions.size()), + .pVertexAttributeDescriptions = attribute_descriptions.data()}; + + // Specify we will use triangle lists to draw geometry. + VkPipelineInputAssemblyStateCreateInfo input_assembly{ + .sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO, + .primitiveRestartEnable = VK_FALSE}; + + // Specify rasterization state. + VkPipelineRasterizationStateCreateInfo raster{ + .sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO, + .depthClampEnable = VK_FALSE, + .rasterizerDiscardEnable = VK_FALSE, + .polygonMode = VK_POLYGON_MODE_FILL, + .depthBiasEnable = VK_FALSE, + .lineWidth = 1.0f}; + + // Specify that these states will be dynamic, i.e. not part of pipeline state object. + std::vector dynamic_states = { + VK_DYNAMIC_STATE_VIEWPORT, + VK_DYNAMIC_STATE_SCISSOR, + VK_DYNAMIC_STATE_CULL_MODE, + VK_DYNAMIC_STATE_FRONT_FACE, + VK_DYNAMIC_STATE_PRIMITIVE_TOPOLOGY}; + + // Our attachment will write to all color channels, but no blending is enabled. + VkPipelineColorBlendAttachmentState blend_attachment{ + .colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT}; + + VkPipelineColorBlendStateCreateInfo blend{ + .sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO, + .attachmentCount = 1, + .pAttachments = &blend_attachment}; + + // We will have one viewport and scissor box. + VkPipelineViewportStateCreateInfo viewport{ + .sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO, + .viewportCount = 1, + .scissorCount = 1}; + + // Disable all depth testing. + VkPipelineDepthStencilStateCreateInfo depth_stencil{ + .sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO, + .depthCompareOp = VK_COMPARE_OP_ALWAYS}; + + // No multisampling. + VkPipelineMultisampleStateCreateInfo multisample{ + .sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO, + .rasterizationSamples = VK_SAMPLE_COUNT_1_BIT}; + + VkPipelineDynamicStateCreateInfo dynamic_state_info{ + .sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO, + .dynamicStateCount = static_cast(dynamic_states.size()), + .pDynamicStates = dynamic_states.data()}; + + // Load our SPIR-V shaders. + std::array shader_stages{}; + + // Vertex stage of the pipeline + shader_stages[0].sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; + shader_stages[0].stage = VK_SHADER_STAGE_VERTEX_BIT; + shader_stages[0].module = load_shader_module("triangle.vert"); + shader_stages[0].pName = "main"; + + // Fragment stage of the pipeline + shader_stages[1].sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; + shader_stages[1].stage = VK_SHADER_STAGE_FRAGMENT_BIT; + shader_stages[1].module = load_shader_module("triangle.frag"); + shader_stages[1].pName = "main"; + + // Pipeline rendering info (for dynamic rendering). + VkPipelineRenderingCreateInfo pipeline_rendering_info{ + .sType = VK_STRUCTURE_TYPE_PIPELINE_RENDERING_CREATE_INFO, + .colorAttachmentCount = 1, + .pColorAttachmentFormats = &context.swapchain_dimensions.format}; + + // Create the graphics pipeline. + VkGraphicsPipelineCreateInfo pipe{ + .sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO, + .pNext = &pipeline_rendering_info, + .stageCount = vkb::to_u32(shader_stages.size()), + .pStages = shader_stages.data(), + .pVertexInputState = &vertex_input, + .pInputAssemblyState = &input_assembly, + .pViewportState = &viewport, + .pRasterizationState = &raster, + .pMultisampleState = &multisample, + .pDepthStencilState = &depth_stencil, + .pColorBlendState = &blend, + .pDynamicState = &dynamic_state_info, + .layout = context.pipeline_layout, // We need to specify the pipeline layout description up front as well. + .renderPass = VK_NULL_HANDLE, // Since we are using dynamic rendering this will set as null + .subpass = 0, + }; + + VK_CHECK(vkCreateGraphicsPipelines(context.device, VK_NULL_HANDLE, 1, &pipe, nullptr, &context.pipeline)); + + // Pipeline is baked, we can delete the shader modules now. + vkDestroyShaderModule(context.device, shader_stages[0].module, nullptr); + vkDestroyShaderModule(context.device, shader_stages[1].module, nullptr); +} + +/** + * @brief Acquires an image from the swapchain. + * @param[out] image The swapchain index for the acquired image. + * @returns Vulkan result code + */ +VkResult HelloTriangleV13::acquire_next_swapchain_image(uint32_t *image) +{ + VkSemaphore acquire_semaphore; + if (context.recycled_semaphores.empty()) + { + VkSemaphoreCreateInfo info = {VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO}; + VK_CHECK(vkCreateSemaphore(context.device, &info, nullptr, &acquire_semaphore)); + } + else + { + acquire_semaphore = context.recycled_semaphores.back(); + context.recycled_semaphores.pop_back(); + } + + VkResult res = vkAcquireNextImageKHR(context.device, context.swapchain, UINT64_MAX, acquire_semaphore, VK_NULL_HANDLE, image); + + if (res != VK_SUCCESS) + { + context.recycled_semaphores.push_back(acquire_semaphore); + return res; + } + + // If we have outstanding fences for this swapchain image, wait for them to complete first. + // After begin frame returns, it is safe to reuse or delete resources which + // were used previously. + // + // We wait for fences which completes N frames earlier, so we do not stall, + // waiting for all GPU work to complete before this returns. + // Normally, this doesn't really block at all, + // since we're waiting for old frames to have been completed, but just in case. + if (context.per_frame[*image].queue_submit_fence != VK_NULL_HANDLE) + { + vkWaitForFences(context.device, 1, &context.per_frame[*image].queue_submit_fence, true, UINT64_MAX); + vkResetFences(context.device, 1, &context.per_frame[*image].queue_submit_fence); + } + + if (context.per_frame[*image].primary_command_pool != VK_NULL_HANDLE) + { + vkResetCommandPool(context.device, context.per_frame[*image].primary_command_pool, 0); + } + + // Recycle the old semaphore back into the semaphore manager. + VkSemaphore old_semaphore = context.per_frame[*image].swapchain_acquire_semaphore; + + if (old_semaphore != VK_NULL_HANDLE) + { + context.recycled_semaphores.push_back(old_semaphore); + } + + context.per_frame[*image].swapchain_acquire_semaphore = acquire_semaphore; + + return VK_SUCCESS; +} + +/** + * @brief Renders a triangle to the specified swapchain image. + * @param swapchain_index The swapchain index for the image being rendered. + */ +void HelloTriangleV13::render_triangle(uint32_t swapchain_index) +{ + // Allocate or re-use a primary command buffer. + VkCommandBuffer cmd = context.per_frame[swapchain_index].primary_command_buffer; + + // We will only submit this once before it's recycled. + VkCommandBufferBeginInfo begin_info{ + .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, + .flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT}; + + // Begin command recording + VK_CHECK(vkBeginCommandBuffer(cmd, &begin_info)); + + // Before starting rendering, transition the swapchain image to COLOR_ATTACHMENT_OPTIMAL + transition_image_layout( + cmd, + context.swapchain_images[swapchain_index], + VK_IMAGE_LAYOUT_UNDEFINED, + VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, + 0, // srcAccessMask (no need to wait for previous operations) + VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT, // dstAccessMask + VK_PIPELINE_STAGE_2_TOP_OF_PIPE_BIT, // srcStage + VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT // dstStage + ); + // Set clear color values. + VkClearValue clear_value; + clear_value.color = {{0.01f, 0.01f, 0.033f, 1.0f}}; + + // Set up the rendering attachment info + VkRenderingAttachmentInfo color_attachment{ + .sType = VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO, + .imageView = context.swapchain_image_views[swapchain_index], + .imageLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, + .loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR, + .storeOp = VK_ATTACHMENT_STORE_OP_STORE, + .clearValue = clear_value}; + + // Begin rendering + VkRenderingInfo rendering_info{ + .sType = VK_STRUCTURE_TYPE_RENDERING_INFO_KHR, + .renderArea = { // Initialize the nested `VkRect2D` structure + .offset = {0, 0}, // Initialize the `VkOffset2D` inside `renderArea` + .extent = { // Initialize the `VkExtent2D` inside `renderArea` + .width = context.swapchain_dimensions.width, + .height = context.swapchain_dimensions.height}}, + .layerCount = 1, + .colorAttachmentCount = 1, + .pColorAttachments = &color_attachment}; + + vkCmdBeginRendering(cmd, &rendering_info); + + // Bind the graphics pipeline. + vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, context.pipeline); + + // Set dynamic states + + // Set viewport dynamically + VkViewport vp{ + .width = static_cast(context.swapchain_dimensions.width), + .height = static_cast(context.swapchain_dimensions.height), + .minDepth = 0.0f, + .maxDepth = 1.0f}; + + vkCmdSetViewport(cmd, 0, 1, &vp); + + // Set scissor dynamically + VkRect2D scissor{}; + scissor.extent.width = context.swapchain_dimensions.width; + scissor.extent.height = context.swapchain_dimensions.height; + vkCmdSetScissor(cmd, 0, 1, &scissor); + + // Since we declared VK_DYNAMIC_STATE_CULL_MODE as dynamic in the pipeline, + // we need to set the cull mode here. VK_CULL_MODE_NONE disables face culling, + // meaning both front and back faces will be rendered. + vkCmdSetCullMode(cmd, VK_CULL_MODE_NONE); + + // Since we declared VK_DYNAMIC_STATE_FRONT_FACE as dynamic, + // we need to specify the winding order considered as the front face. + // VK_FRONT_FACE_CLOCKWISE indicates that vertices defined in clockwise order + // are considered front-facing. + vkCmdSetFrontFace(cmd, VK_FRONT_FACE_CLOCKWISE); + + // Since we declared VK_DYNAMIC_STATE_PRIMITIVE_TOPOLOGY as dynamic, + // we need to set the primitive topology here. VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST + // tells Vulkan that the input vertex data should be interpreted as a list of triangles. + vkCmdSetPrimitiveTopology(cmd, VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST); + + // Bind the vertex buffer + VkDeviceSize offset = {0}; + vkCmdBindVertexBuffers(cmd, 0, 1, &context.vertex_buffer, &offset); + + // Draw three vertices with one instance. + vkCmdDraw(cmd, vertices.size(), 1, 0, 0); + + // Complete rendering. + vkCmdEndRendering(cmd); + + // After rendering , transition the swapchain image to PRESENT_SRC + transition_image_layout( + cmd, + context.swapchain_images[swapchain_index], + VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, + VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, + VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT, // srcAccessMask + 0, // dstAccessMask + VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT, // srcStage + VK_PIPELINE_STAGE_2_BOTTOM_OF_PIPE_BIT // dstStage + ); + + // Complete the command buffer. + VK_CHECK(vkEndCommandBuffer(cmd)); + + // Submit it to the queue with a release semaphore. + if (context.per_frame[swapchain_index].swapchain_release_semaphore == VK_NULL_HANDLE) + { + VkSemaphoreCreateInfo semaphore_info{VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO}; + VK_CHECK(vkCreateSemaphore(context.device, &semaphore_info, nullptr, + &context.per_frame[swapchain_index].swapchain_release_semaphore)); + } + + VkPipelineStageFlags wait_stage{VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT}; + + VkSubmitInfo info{ + .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO, + .waitSemaphoreCount = 1, + .pWaitSemaphores = &context.per_frame[swapchain_index].swapchain_acquire_semaphore, + .pWaitDstStageMask = &wait_stage, + .commandBufferCount = 1, + .pCommandBuffers = &cmd, + .signalSemaphoreCount = 1, + .pSignalSemaphores = &context.per_frame[swapchain_index].swapchain_release_semaphore}; + + // Submit command buffer to graphics queue + VK_CHECK(vkQueueSubmit(context.queue, 1, &info, context.per_frame[swapchain_index].queue_submit_fence)); +} + +/** + * @brief Presents an image to the swapchain. + * @param index The swapchain index previously obtained from @ref acquire_next_swapchain_image. + * @returns Vulkan result code + */ +VkResult HelloTriangleV13::present_image(uint32_t index) +{ + VkPresentInfoKHR present{ + .sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR, + .waitSemaphoreCount = 1, + .pWaitSemaphores = &context.per_frame[index].swapchain_release_semaphore, + .swapchainCount = 1, + .pSwapchains = &context.swapchain, + .pImageIndices = &index, + }; + + // Present swapchain image + return vkQueuePresentKHR(context.queue, &present); +} + +/** + * @brief Transitions an image layout in a Vulkan command buffer. + * @param cmd The command buffer to record the barrier into. + * @param image The Vulkan image to transition. + * @param oldLayout The current layout of the image. + * @param newLayout The desired new layout of the image. + * @param srcAccessMask The source access mask, specifying which access types are being transitioned from. + * @param dstAccessMask The destination access mask, specifying which access types are being transitioned to. + * @param srcStage The pipeline stage that must happen before the transition. + * @param dstStage The pipeline stage that must happen after the transition. + */ +void HelloTriangleV13::transition_image_layout( + VkCommandBuffer cmd, + VkImage image, + VkImageLayout oldLayout, + VkImageLayout newLayout, + VkAccessFlags2 srcAccessMask, + VkAccessFlags2 dstAccessMask, + VkPipelineStageFlags2 srcStage, + VkPipelineStageFlags2 dstStage) +{ + // Initialize the VkImageMemoryBarrier2 structure + VkImageMemoryBarrier2 image_barrier{ + .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2, + + // Specify the pipeline stages and access masks for the barrier + .srcStageMask = srcStage, // Source pipeline stage mask + .srcAccessMask = srcAccessMask, // Source access mask + .dstStageMask = dstStage, // Destination pipeline stage mask + .dstAccessMask = dstAccessMask, // Destination access mask + + // Specify the old and new layouts of the image + .oldLayout = oldLayout, // Current layout of the image + .newLayout = newLayout, // Target layout of the image + + // We are not changing the ownership between queues + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + + // Specify the image to be affected by this barrier + .image = image, + + // Define the subresource range (which parts of the image are affected) + .subresourceRange = { + .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, // Affects the color aspect of the image + .baseMipLevel = 0, // Start at mip level 0 + .levelCount = 1, // Number of mip levels affected + .baseArrayLayer = 0, // Start at array layer 0 + .layerCount = 1 // Number of array layers affected + }}; + + // Initialize the VkDependencyInfo structure + VkDependencyInfo dependency_info{ + .sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO, + .dependencyFlags = 0, // No special dependency flags + .imageMemoryBarrierCount = 1, // Number of image memory barriers + .pImageMemoryBarriers = &image_barrier // Pointer to the image memory barrier(s) + }; + + // Record the pipeline barrier into the command buffer + vkCmdPipelineBarrier2(cmd, &dependency_info); +} + +/** + * @brief Tears down the Vulkan context. + */ +void HelloTriangleV13::teardown() +{ + // Don't release anything until the GPU is completely idle. + vkDeviceWaitIdle(context.device); + + for (auto &per_frame : context.per_frame) + { + teardown_per_frame(per_frame); + } + + context.per_frame.clear(); + + for (auto semaphore : context.recycled_semaphores) + { + vkDestroySemaphore(context.device, semaphore, nullptr); + } + + if (context.pipeline != VK_NULL_HANDLE) + { + vkDestroyPipeline(context.device, context.pipeline, nullptr); + } + + if (context.pipeline_layout != VK_NULL_HANDLE) + { + vkDestroyPipelineLayout(context.device, context.pipeline_layout, nullptr); + } + + for (VkImageView image_view : context.swapchain_image_views) + { + vkDestroyImageView(context.device, image_view, nullptr); + } + + if (context.swapchain != VK_NULL_HANDLE) + { + vkDestroySwapchainKHR(context.device, context.swapchain, nullptr); + context.swapchain = VK_NULL_HANDLE; + } + + if (context.surface != VK_NULL_HANDLE) + { + vkDestroySurfaceKHR(context.instance, context.surface, nullptr); + context.surface = VK_NULL_HANDLE; + } + + if (context.vertex_buffer != VK_NULL_HANDLE) + { + vkDestroyBuffer(context.device, context.vertex_buffer, nullptr); + context.vertex_buffer = VK_NULL_HANDLE; + } + + if (context.vertex_buffer_memory != VK_NULL_HANDLE) + { + vkFreeMemory(context.device, context.vertex_buffer_memory, nullptr); + context.vertex_buffer_memory = VK_NULL_HANDLE; + } + + if (context.device != VK_NULL_HANDLE) + { + vkDestroyDevice(context.device, nullptr); + context.device = VK_NULL_HANDLE; + } + + if (context.debug_callback != VK_NULL_HANDLE) + { + vkDestroyDebugUtilsMessengerEXT(context.instance, context.debug_callback, nullptr); + context.debug_callback = VK_NULL_HANDLE; + } + + vk_instance.reset(); +} + +HelloTriangleV13::~HelloTriangleV13() +{ + teardown(); +} + +bool HelloTriangleV13::prepare(const vkb::ApplicationOptions &options) +{ + assert(options.window != nullptr); + + init_instance(); + + vk_instance = std::make_unique(context.instance); + + context.surface = options.window->create_surface(*vk_instance); + auto &extent = options.window->get_extent(); + context.swapchain_dimensions.width = extent.width; + context.swapchain_dimensions.height = extent.height; + + if (!context.surface) + { + throw std::runtime_error("Failed to create window surface."); + } + + init_device(); + + init_vertex_buffer(); + + init_swapchain(); + + // Create the necessary objects for rendering. + init_pipeline(); + + return true; +} + +void HelloTriangleV13::update(float delta_time) +{ + uint32_t index; + + auto res = acquire_next_swapchain_image(&index); + + // Handle outdated error in acquire. + if (res == VK_SUBOPTIMAL_KHR || res == VK_ERROR_OUT_OF_DATE_KHR) + { + resize(context.swapchain_dimensions.width, context.swapchain_dimensions.height); + res = acquire_next_swapchain_image(&index); + } + + if (res != VK_SUCCESS) + { + vkQueueWaitIdle(context.queue); + return; + } + + render_triangle(index); + res = present_image(index); + + // Handle Outdated error in present. + if (res == VK_SUBOPTIMAL_KHR || res == VK_ERROR_OUT_OF_DATE_KHR) + { + resize(context.swapchain_dimensions.width, context.swapchain_dimensions.height); + } + else if (res != VK_SUCCESS) + { + LOGE("Failed to present swapchain image."); + } +} + +bool HelloTriangleV13::resize(const uint32_t, const uint32_t) +{ + if (context.device == VK_NULL_HANDLE) + { + return false; + } + + VkSurfaceCapabilitiesKHR surface_properties; + VK_CHECK(vkGetPhysicalDeviceSurfaceCapabilitiesKHR(context.gpu, context.surface, &surface_properties)); + + // Only rebuild the swapchain if the dimensions have changed + if (surface_properties.currentExtent.width == context.swapchain_dimensions.width && + surface_properties.currentExtent.height == context.swapchain_dimensions.height) + { + return false; + } + + vkDeviceWaitIdle(context.device); + + init_swapchain(); + return true; +} + +std::unique_ptr create_hello_triangle_1_3() +{ + return std::make_unique(); +} diff --git a/samples/api/hello_triangle_1_3/hello_triangle_1_3.h b/samples/api/hello_triangle_1_3/hello_triangle_1_3.h new file mode 100644 index 000000000..8a87a6ba6 --- /dev/null +++ b/samples/api/hello_triangle_1_3/hello_triangle_1_3.h @@ -0,0 +1,184 @@ +/* Copyright (c) 2024, Huawei Technologies Co., Ltd. + * + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 the "License"; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "common/vk_common.h" +#include "core/instance.h" +#include "platform/application.h" + +/** + * @brief A self-contained (minimal use of framework) sample that illustrates + * the rendering of a triangle + */ +class HelloTriangleV13 : public vkb::Application +{ + // Define the Vertex structure + struct Vertex + { + glm::vec2 position; + glm::vec3 color; + }; + + // Define the vertex data + const std::vector vertices = { + {{0.5f, -0.5f}, {1.0f, 0.0f, 0.0f}}, // Vertex 1: Red + {{0.5f, 0.5f}, {0.0f, 1.0f, 0.0f}}, // Vertex 2: Green + {{-0.5f, 0.5f}, {0.0f, 0.0f, 1.0f}} // Vertex 3: Blue + }; + + /** + * @brief Swapchain state + */ + struct SwapchainDimensions + { + /// Width of the swapchain. + uint32_t width = 0; + + /// Height of the swapchain. + uint32_t height = 0; + + /// Pixel format of the swapchain. + VkFormat format = VK_FORMAT_UNDEFINED; + }; + + /** + * @brief Per-frame data + */ + struct PerFrame + { + VkFence queue_submit_fence = VK_NULL_HANDLE; + VkCommandPool primary_command_pool = VK_NULL_HANDLE; + VkCommandBuffer primary_command_buffer = VK_NULL_HANDLE; + VkSemaphore swapchain_acquire_semaphore = VK_NULL_HANDLE; + VkSemaphore swapchain_release_semaphore = VK_NULL_HANDLE; + }; + + /** + * @brief Vulkan objects and global state + */ + struct Context + { + /// The Vulkan instance. + VkInstance instance = VK_NULL_HANDLE; + + /// The Vulkan physical device. + VkPhysicalDevice gpu = VK_NULL_HANDLE; + + /// The Vulkan device. + VkDevice device = VK_NULL_HANDLE; + + /// The Vulkan device queue. + VkQueue queue = VK_NULL_HANDLE; + + /// The swapchain. + VkSwapchainKHR swapchain = VK_NULL_HANDLE; + + /// The swapchain dimensions. + SwapchainDimensions swapchain_dimensions; + + /// The surface we will render to. + VkSurfaceKHR surface = VK_NULL_HANDLE; + + /// The queue family index where graphics work will be submitted. + int32_t graphics_queue_index = -1; + + /// The image view for each swapchain image. + std::vector swapchain_image_views; + + /// The handles to the images in the swapchain. + std::vector swapchain_images; + + /// The graphics pipeline. + VkPipeline pipeline = VK_NULL_HANDLE; + + /** + * The pipeline layout for resources. + * Not used in this sample, but we still need to provide a dummy one. + */ + VkPipelineLayout pipeline_layout = VK_NULL_HANDLE; + + /// The debug utility messenger callback. + VkDebugUtilsMessengerEXT debug_callback = VK_NULL_HANDLE; + + /// A set of semaphores that can be reused. + std::vector recycled_semaphores; + + /// A set of per-frame data. + std::vector per_frame; + + /// The Vulkan buffer object that holds the vertex data for the triangle. + VkBuffer vertex_buffer = VK_NULL_HANDLE; + + /// The device memory allocated for the vertex buffer. + VkDeviceMemory vertex_buffer_memory = VK_NULL_HANDLE; + }; + + public: + HelloTriangleV13() = default; + + virtual ~HelloTriangleV13(); + + virtual bool prepare(const vkb::ApplicationOptions &options) override; + + virtual void update(float delta_time) override; + + virtual bool resize(const uint32_t width, const uint32_t height) override; + + bool validate_extensions(const std::vector &required, + const std::vector &available); + + bool validate_layers(const std::vector &required, + const std::vector &available); + + VkShaderStageFlagBits find_shader_stage(const std::string &ext); + + void init_instance(); + + void init_device(); + + void init_vertex_buffer(); + + void init_per_frame(PerFrame &per_frame); + + void teardown_per_frame(PerFrame &per_frame); + + void init_swapchain(); + + VkShaderModule load_shader_module(const char *path); + + void init_pipeline(); + + VkResult acquire_next_swapchain_image(uint32_t *image); + + void render_triangle(uint32_t swapchain_index); + + VkResult present_image(uint32_t index); + + void transition_image_layout(VkCommandBuffer cmd, VkImage image, VkImageLayout oldLayout, VkImageLayout newLayout, VkAccessFlags2 srcAccessMask, VkAccessFlags2 dstAccessMask, VkPipelineStageFlags2 srcStage, VkPipelineStageFlags2 dstStage); + + void teardown(); + + uint32_t find_memory_type(VkPhysicalDevice physical_device, uint32_t type_filter, VkMemoryPropertyFlags properties); + + private: + Context context; + + std::unique_ptr vk_instance; +}; + +std::unique_ptr create_hello_triangle_1_3(); diff --git a/shaders/hello_triangle_1_3/triangle.frag b/shaders/hello_triangle_1_3/triangle.frag new file mode 100644 index 000000000..97bbd750c --- /dev/null +++ b/shaders/hello_triangle_1_3/triangle.frag @@ -0,0 +1,26 @@ +#version 450 +/* Copyright (c) 2024, Huawei Technologies Co., Ltd. + * + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 the "License"; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +layout(location = 0) in vec3 in_color; + +layout(location = 0) out vec4 out_color; + +void main() +{ + out_color = vec4(in_color, 1.0); +} \ No newline at end of file diff --git a/shaders/hello_triangle_1_3/triangle.vert b/shaders/hello_triangle_1_3/triangle.vert new file mode 100644 index 000000000..adff45310 --- /dev/null +++ b/shaders/hello_triangle_1_3/triangle.vert @@ -0,0 +1,29 @@ +#version 450 +/* Copyright (c) 2024, Huawei Technologies Co., Ltd. + * + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 the "License"; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +layout(location = 0) in vec2 in_position; +layout(location = 1) in vec3 in_color; + +layout(location = 0) out vec3 out_color; + +void main() +{ + gl_Position = vec4(in_position, 0.5, 1.0); + + out_color = in_color; +} \ No newline at end of file