/* Copyright (c) 2021-2025, NVIDIA CORPORATION. All rights reserved. * Copyright (c) 2024-2025, Arm Limited and Contributors * * 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 "hpp_hello_triangle.h" #include #include #include #include #include // Note: the default dispatcher is instantiated in hpp_api_vulkan_sample.cpp. // Even though, that file is not part of this sample, it's part of the sample-project! #if defined(VKB_DEBUG) || defined(VKB_VALIDATION_LAYERS) /// @brief A debug callback called from Vulkan validation layers. VKAPI_ATTR vk::Bool32 VKAPI_CALL debug_utils_messenger_callback(vk::DebugUtilsMessageSeverityFlagBitsEXT message_severity, vk::DebugUtilsMessageTypeFlagsEXT message_type, const vk::DebugUtilsMessengerCallbackDataEXT *callback_data, void *user_data) { // Log debug message if (message_severity & vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning) { LOGW("{} - {}: {}", callback_data->messageIdNumber, callback_data->pMessageIdName, callback_data->pMessage); } else if (message_severity & vk::DebugUtilsMessageSeverityFlagBitsEXT::eError) { LOGE("{} - {}: {}", 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 vk::ExtensionProperties object containing available extensions. * @return true if all required extensions are available * @return false otherwise */ bool validate_extensions(const std::vector &required, const std::vector &available) { // inner find_if gives true if the extension was not found // outer find_if gives true if none of the extensions were not found, that is if all extensions were found return std::ranges::find_if(required, [&available](auto extension) { return std::ranges::find_if(available, [&extension](auto const &ep) { return strcmp(ep.extensionName, extension) == 0; }) == available.end(); }) == required.end(); } HPPHelloTriangle::HPPHelloTriangle() { } HPPHelloTriangle::~HPPHelloTriangle() { // Don't release anything until the GPU is completely idle. device.waitIdle(); teardown_framebuffers(); for (auto &pfd : per_frame_data) { teardown_per_frame(pfd); } per_frame_data.clear(); for (auto semaphore : recycled_semaphores) { device.destroySemaphore(semaphore); } if (pipeline) { device.destroyPipeline(pipeline); } if (pipeline_layout) { device.destroyPipelineLayout(pipeline_layout); } if (render_pass) { device.destroyRenderPass(render_pass); } for (auto image_view : swapchain_data.image_views) { device.destroyImageView(image_view); } if (swapchain_data.swapchain) { device.destroySwapchainKHR(swapchain_data.swapchain); } if (surface) { instance.destroySurfaceKHR(surface); } if (vertex_buffer_allocation != VK_NULL_HANDLE) { vmaDestroyBuffer(vma_allocator, vertex_buffer, vertex_buffer_allocation); } if (vma_allocator != VK_NULL_HANDLE) { vmaDestroyAllocator(vma_allocator); } if (device) { device.destroy(); } if (debug_utils_messenger) { instance.destroyDebugUtilsMessengerEXT(debug_utils_messenger); } instance.destroy(); } bool HPPHelloTriangle::prepare(const vkb::ApplicationOptions &options) { // Headless is not supported to keep this sample as simple as possible assert(options.window != nullptr); assert(options.window->get_window_mode() != vkb::Window::Mode::Headless); if (Application::prepare(options)) { instance = create_instance({VK_KHR_SURFACE_EXTENSION_NAME}, {}); #if defined(VKB_DEBUG) || defined(VKB_VALIDATION_LAYERS) debug_utils_messenger = instance.createDebugUtilsMessengerEXT(debug_utils_create_info); #endif select_physical_device_and_surface(); const vkb::Window::Extent &extent = options.window->get_extent(); swapchain_data.extent.width = extent.width; swapchain_data.extent.height = extent.height; // create a device device = create_device({VK_KHR_SWAPCHAIN_EXTENSION_NAME}); // get the (graphics) queue queue = device.getQueue(graphics_queue_index, 0); vma_allocator = create_vma_allocator(); std::tie(vertex_buffer, vertex_buffer_allocation) = create_vertex_buffer(); init_swapchain(); // Create the necessary objects for rendering. render_pass = create_render_pass(); // Create a blank pipeline layout. // We are not binding any resources to the pipeline in this first sample. pipeline_layout = device.createPipelineLayout({}); pipeline = create_graphics_pipeline(); init_framebuffers(); } return true; } void HPPHelloTriangle::update(float delta_time) { vk::Result res; uint32_t index; std::tie(res, index) = acquire_next_image(); // Handle outdated error in acquire. if (res == vk::Result::eSuboptimalKHR || res == vk::Result::eErrorOutOfDateKHR) { resize(swapchain_data.extent.width, swapchain_data.extent.height); std::tie(res, index) = acquire_next_image(); } if (res != vk::Result::eSuccess) { queue.waitIdle(); return; } render_triangle(index); // Present swapchain image vk::PresentInfoKHR present_info{.waitSemaphoreCount = 1, .pWaitSemaphores = &per_frame_data[index].swapchain_release_semaphore, .swapchainCount = 1, .pSwapchains = &swapchain_data.swapchain, .pImageIndices = &index}; res = queue.presentKHR(present_info); // Handle Outdated error in present. if (res == vk::Result::eSuboptimalKHR || res == vk::Result::eErrorOutOfDateKHR) { resize(swapchain_data.extent.width, swapchain_data.extent.height); } else if (res != vk::Result::eSuccess) { LOGE("Failed to present swapchain image."); } } bool HPPHelloTriangle::resize(const uint32_t, const uint32_t) { if (!device) { return false; } vk::SurfaceCapabilitiesKHR surface_properties = gpu.getSurfaceCapabilitiesKHR(surface); // Only rebuild the swapchain if the dimensions have changed if (surface_properties.currentExtent == swapchain_data.extent) { return false; } device.waitIdle(); teardown_framebuffers(); init_swapchain(); init_framebuffers(); return true; } /** * @brief Acquires an image from the swapchain. * @param[out] image The swapchain index for the acquired image. * @returns Vulkan result code */ std::pair HPPHelloTriangle::acquire_next_image() { vk::Semaphore acquire_semaphore; if (recycled_semaphores.empty()) { acquire_semaphore = device.createSemaphore({}); } else { acquire_semaphore = recycled_semaphores.back(); recycled_semaphores.pop_back(); } vk::Result res; uint32_t image; std::tie(res, image) = device.acquireNextImageKHR(swapchain_data.swapchain, UINT64_MAX, acquire_semaphore); if (res != vk::Result::eSuccess) { recycled_semaphores.push_back(acquire_semaphore); return {res, image}; } // 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 (per_frame_data[image].queue_submit_fence) { (void) device.waitForFences(per_frame_data[image].queue_submit_fence, true, UINT64_MAX); device.resetFences(per_frame_data[image].queue_submit_fence); } if (per_frame_data[image].primary_command_pool) { device.resetCommandPool(per_frame_data[image].primary_command_pool); } // Recycle the old semaphore back into the semaphore manager. vk::Semaphore old_semaphore = per_frame_data[image].swapchain_acquire_semaphore; if (old_semaphore) { recycled_semaphores.push_back(old_semaphore); } per_frame_data[image].swapchain_acquire_semaphore = acquire_semaphore; return {vk::Result::eSuccess, image}; } vk::Device HPPHelloTriangle::create_device(const std::vector &required_device_extensions) { std::vector device_extensions = gpu.enumerateDeviceExtensionProperties(); if (!validate_extensions(required_device_extensions, device_extensions)) { throw std::runtime_error("Required device extensions are missing, will try without."); } std::vector active_device_extensions(required_device_extensions); #if (defined(VKB_ENABLE_PORTABILITY)) // VK_KHR_portability_subset must be enabled if present in the implementation (e.g on macOS/iOS with beta extensions enabled) if (std::ranges::any_of(device_extensions, [](vk::ExtensionProperties const &extension) { return strcmp(extension.extensionName, VK_KHR_PORTABILITY_SUBSET_EXTENSION_NAME) == 0; })) { active_device_extensions.push_back(VK_KHR_PORTABILITY_SUBSET_EXTENSION_NAME); } #endif // Create a device with one queue float queue_priority = 1.0f; vk::DeviceQueueCreateInfo queue_info{.queueFamilyIndex = graphics_queue_index, .queueCount = 1, .pQueuePriorities = &queue_priority}; vk::DeviceCreateInfo device_info{.queueCreateInfoCount = 1, .pQueueCreateInfos = &queue_info, .enabledExtensionCount = static_cast(active_device_extensions.size()), .ppEnabledExtensionNames = active_device_extensions.data()}; vk::Device device = gpu.createDevice(device_info); // initialize function pointers for device VULKAN_HPP_DEFAULT_DISPATCHER.init(device); return device; } vk::Pipeline HPPHelloTriangle::create_graphics_pipeline() { // Load our SPIR-V shaders. // Samples support different shading languages, all of which are offline compiled to SPIR-V, the shader format that Vulkan uses. // The shading language to load for can be selected via command line std::string shader_folder{""}; switch (get_shading_language()) { case vkb::ShadingLanguage::HLSL: shader_folder = "hlsl"; break; case vkb::ShadingLanguage::SLANG: shader_folder = "slang"; break; default: shader_folder = "glsl"; } std::vector shader_stages{ {.stage = vk::ShaderStageFlagBits::eVertex, .module = create_shader_module("hello_triangle/" + shader_folder + "/triangle.vert.spv"), .pName = "main"}, {.stage = vk::ShaderStageFlagBits::eFragment, .module = create_shader_module("hello_triangle/" + shader_folder + "/triangle.frag.spv"), .pName = "main"}}; // Define the vertex input binding. vk::VertexInputBindingDescription binding_description{.binding = 0, .stride = sizeof(Vertex), .inputRate = vk::VertexInputRate::eVertex}; // Define the vertex input attribute. std::array attribute_descriptions{ {{.location = 0, .binding = 0, .format = vk::Format::eR32G32B32Sfloat, .offset = offsetof(Vertex, position)}, {.location = 1, .binding = 0, .format = vk::Format::eR32G32B32Sfloat, .offset = offsetof(Vertex, color)}}}; // Define the pipeline vertex input. vk::PipelineVertexInputStateCreateInfo vertex_input{ .vertexBindingDescriptionCount = 1, .pVertexBindingDescriptions = &binding_description, .vertexAttributeDescriptionCount = static_cast(attribute_descriptions.size()), .pVertexAttributeDescriptions = attribute_descriptions.data()}; // Our attachment will write to all color channels, but no blending is enabled. vk::PipelineColorBlendAttachmentState blend_attachment{.colorWriteMask = vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG | vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA}; // Disable all depth testing. vk::PipelineDepthStencilStateCreateInfo depth_stencil; vk::Pipeline pipeline = vkb::common::create_graphics_pipeline(device, nullptr, shader_stages, vertex_input, vk::PrimitiveTopology::eTriangleList, // We will use triangle lists to draw geometry. 0, vk::PolygonMode::eFill, vk::CullModeFlagBits::eBack, vk::FrontFace::eClockwise, {blend_attachment}, depth_stencil, pipeline_layout, // We need to specify the pipeline layout render_pass); // and the render pass up front as well // Pipeline is baked, we can delete the shader modules now. device.destroyShaderModule(shader_stages[0].module); device.destroyShaderModule(shader_stages[1].module); return pipeline; } vk::ImageView HPPHelloTriangle::create_image_view(vk::Image image) { vk::ImageViewCreateInfo image_view_create_info{ .image = image, .viewType = vk::ImageViewType::e2D, .format = swapchain_data.format, .components = {.r = vk::ComponentSwizzle::eR, .g = vk::ComponentSwizzle::eG, .b = vk::ComponentSwizzle::eB, .a = vk::ComponentSwizzle::eA}, .subresourceRange = {.aspectMask = vk::ImageAspectFlagBits::eColor, .baseMipLevel = 0, .levelCount = 1, .baseArrayLayer = 0, .layerCount = 1}}; return device.createImageView(image_view_create_info); } vk::Instance HPPHelloTriangle::create_instance(std::vector const &required_instance_extensions, std::vector const &required_validation_layers) { #if defined(_HPP_VULKAN_LIBRARY) static vk::detail::DynamicLoader dl(_HPP_VULKAN_LIBRARY); #else static vk::detail::DynamicLoader dl; #endif PFN_vkGetInstanceProcAddr vkGetInstanceProcAddr = dl.getProcAddress("vkGetInstanceProcAddr"); VULKAN_HPP_DEFAULT_DISPATCHER.init(vkGetInstanceProcAddr); std::vector available_instance_extensions = vk::enumerateInstanceExtensionProperties(); std::vector active_instance_extensions(required_instance_extensions); #if defined(VKB_DEBUG) || defined(VKB_VALIDATION_LAYERS) active_instance_extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); #endif #if (defined(VKB_ENABLE_PORTABILITY)) active_instance_extensions.push_back(VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME); bool portability_enumeration_available = false; if (std::ranges::any_of(available_instance_extensions, [](vk::ExtensionProperties const &extension) { return strcmp(extension.extensionName, VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME) == 0; })) { active_instance_extensions.push_back(VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME); portability_enumeration_available = true; } #endif #if defined(VK_USE_PLATFORM_ANDROID_KHR) active_instance_extensions.push_back(VK_KHR_ANDROID_SURFACE_EXTENSION_NAME); #elif defined(VK_USE_PLATFORM_WIN32_KHR) active_instance_extensions.push_back(VK_KHR_WIN32_SURFACE_EXTENSION_NAME); #elif defined(VK_USE_PLATFORM_METAL_EXT) active_instance_extensions.push_back(VK_EXT_METAL_SURFACE_EXTENSION_NAME); #elif defined(VK_USE_PLATFORM_XCB_KHR) active_instance_extensions.push_back(VK_KHR_XCB_SURFACE_EXTENSION_NAME); #elif defined(VK_USE_PLATFORM_XLIB_KHR) active_instance_extensions.push_back(VK_KHR_XLIB_SURFACE_EXTENSION_NAME); #elif defined(VK_USE_PLATFORM_WAYLAND_KHR) active_instance_extensions.push_back(VK_KHR_WAYLAND_SURFACE_EXTENSION_NAME); #elif defined(VK_USE_PLATFORM_DISPLAY_KHR) active_instance_extensions.push_back(VK_KHR_DISPLAY_EXTENSION_NAME); #else # pragma error Platform not supported #endif if (!validate_extensions(active_instance_extensions, available_instance_extensions)) { throw std::runtime_error("Required instance extensions are missing."); } std::vector requested_instance_layers(required_validation_layers); #if defined(VKB_DEBUG) || defined(VKB_VALIDATION_LAYERS) char const *validationLayer = "VK_LAYER_KHRONOS_validation"; std::vector supported_instance_layers = vk::enumerateInstanceLayerProperties(); if (std::ranges::any_of(supported_instance_layers, [&validationLayer](auto const &lp) { return strcmp(lp.layerName, validationLayer) == 0; })) { requested_instance_layers.push_back(validationLayer); LOGI("Enabled Validation Layer {}", validationLayer); } else { LOGW("Validation Layer {} is not available", validationLayer); } #endif vk::ApplicationInfo app{.pApplicationName = "HPP Hello Triangle", .pEngineName = "Vulkan Samples", .apiVersion = VK_API_VERSION_1_1}; vk::InstanceCreateInfo instance_info{.pApplicationInfo = &app, .enabledLayerCount = static_cast(requested_instance_layers.size()), .ppEnabledLayerNames = requested_instance_layers.data(), .enabledExtensionCount = static_cast(active_instance_extensions.size()), .ppEnabledExtensionNames = active_instance_extensions.data()}; #if defined(VKB_DEBUG) || defined(VKB_VALIDATION_LAYERS) debug_utils_create_info = vk::DebugUtilsMessengerCreateInfoEXT{.messageSeverity = vk::DebugUtilsMessageSeverityFlagBitsEXT::eError | vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning, .messageType = vk::DebugUtilsMessageTypeFlagBitsEXT::eValidation | vk::DebugUtilsMessageTypeFlagBitsEXT::ePerformance, .pfnUserCallback = debug_utils_messenger_callback}; instance_info.pNext = &debug_utils_create_info; #endif #if (defined(VKB_ENABLE_PORTABILITY)) if (portability_enumeration_available) { instance_info.flags |= vk::InstanceCreateFlagBits::eEnumeratePortabilityKHR; } #endif // Create the Vulkan instance vk::Instance instance = vk::createInstance(instance_info); // initialize function pointers for instance VULKAN_HPP_DEFAULT_DISPATCHER.init(instance); #if defined(VK_USE_PLATFORM_DISPLAY_KHR) || defined(VK_USE_PLATFORM_ANDROID_KHR) || defined(VK_USE_PLATFORM_METAL_EXT) // we need some additional initializing for this platform! if (volkInitialize()) { throw std::runtime_error("Failed to initialize volk."); } volkLoadInstance(instance); #endif return instance; } vk::RenderPass HPPHelloTriangle::create_render_pass() { vk::AttachmentDescription attachment{ .format = swapchain_data.format, // Backbuffer format. .samples = vk::SampleCountFlagBits::e1, // Not multisampled. .loadOp = vk::AttachmentLoadOp::eClear, // When starting the frame, we want tiles to be cleared. .storeOp = vk::AttachmentStoreOp::eStore, // When ending the frame, we want tiles to be written out. .stencilLoadOp = vk::AttachmentLoadOp::eDontCare, // Don't care about stencil since we're not using it. .stencilStoreOp = vk::AttachmentStoreOp::eDontCare, // Don't care about stencil since we're not using it. .initialLayout = vk::ImageLayout::eUndefined, // The image layout will be undefined when the render pass begins. .finalLayout = vk::ImageLayout::ePresentSrcKHR // After the render pass is complete, we will transition to PRESENT_SRC_KHR layout. }; // We have one subpass. This subpass has one color attachment. // While executing this subpass, the attachment will be in attachment optimal layout. vk::AttachmentReference color_ref{.attachment = 0, .layout = vk::ImageLayout::eColorAttachmentOptimal}; // We will end up with two transitions. // The first one happens right before we start subpass #0, where // eUndefined is transitioned into eColorAttachmentOptimal. // The final layout in the render pass attachment states ePresentSrcKHR, so we // will get a final transition from eColorAttachmentOptimal to ePresetSrcKHR. vk::SubpassDescription subpass{.pipelineBindPoint = vk::PipelineBindPoint::eGraphics, .colorAttachmentCount = 1, .pColorAttachments = &color_ref}; // Create a dependency to external events. // We need to wait for the WSI semaphore to signal. // Only pipeline stages which depend on eColorAttachmentOutput will // actually wait for the semaphore, so we must also wait for that pipeline stage. vk::SubpassDependency dependency{.srcSubpass = vk::SubpassExternal, .dstSubpass = 0, .srcStageMask = vk::PipelineStageFlagBits::eColorAttachmentOutput, .dstStageMask = vk::PipelineStageFlagBits::eColorAttachmentOutput, // Since we changed the image layout, we need to make the memory visible to color attachment to modify. .srcAccessMask = {}, .dstAccessMask = vk::AccessFlagBits::eColorAttachmentRead | vk::AccessFlagBits::eColorAttachmentWrite}; // Finally, create the renderpass. vk::RenderPassCreateInfo rp_info{ .attachmentCount = 1, .pAttachments = &attachment, .subpassCount = 1, .pSubpasses = &subpass, .dependencyCount = 1, .pDependencies = &dependency}; return device.createRenderPass(rp_info); } /** * @brief Helper function to load a shader module from an offline-compiled SPIR-V file. * @param path The path for the shader (relative to the assets directory). * @returns A vk::ShaderModule handle. Aborts execution if shader creation fails. */ vk::ShaderModule HPPHelloTriangle::create_shader_module(std::string const &path) { auto spirv = vkb::fs::read_shader_binary_u32(path); vk::ShaderModuleCreateInfo shader_module_create_info{.codeSize = spirv.size() * sizeof(uint32_t), .pCode = spirv.data()}; return device.createShaderModule(shader_module_create_info); } vk::SwapchainKHR HPPHelloTriangle::create_swapchain(vk::Extent2D const &swapchain_extent, vk::SurfaceFormatKHR surface_format, vk::SwapchainKHR old_swapchain) { vk::SurfaceCapabilitiesKHR surface_properties = gpu.getSurfaceCapabilitiesKHR(surface); // Determine the number of vk::Image'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. vk::SurfaceTransformFlagBitsKHR pre_transform = (surface_properties.supportedTransforms & vk::SurfaceTransformFlagBitsKHR::eIdentity) ? vk::SurfaceTransformFlagBitsKHR::eIdentity : surface_properties.currentTransform; // Find a supported composite type. vk::CompositeAlphaFlagBitsKHR composite = vk::CompositeAlphaFlagBitsKHR::eOpaque; if (surface_properties.supportedCompositeAlpha & vk::CompositeAlphaFlagBitsKHR::eOpaque) { composite = vk::CompositeAlphaFlagBitsKHR::eOpaque; } else if (surface_properties.supportedCompositeAlpha & vk::CompositeAlphaFlagBitsKHR::eInherit) { composite = vk::CompositeAlphaFlagBitsKHR::eInherit; } else if (surface_properties.supportedCompositeAlpha & vk::CompositeAlphaFlagBitsKHR::ePreMultiplied) { composite = vk::CompositeAlphaFlagBitsKHR::ePreMultiplied; } else if (surface_properties.supportedCompositeAlpha & vk::CompositeAlphaFlagBitsKHR::ePostMultiplied) { composite = vk::CompositeAlphaFlagBitsKHR::ePostMultiplied; } // FIFO must be supported by all implementations. vk::PresentModeKHR swapchain_present_mode = vk::PresentModeKHR::eFifo; vk::SwapchainCreateInfoKHR swapchain_create_info{ .surface = surface, .minImageCount = desired_swapchain_images, .imageFormat = surface_format.format, .imageColorSpace = surface_format.colorSpace, .imageExtent = swapchain_extent, .imageArrayLayers = 1, .imageUsage = vk::ImageUsageFlagBits::eColorAttachment, .imageSharingMode = vk::SharingMode::eExclusive, .preTransform = pre_transform, .compositeAlpha = composite, .presentMode = swapchain_present_mode, .clipped = true, .oldSwapchain = old_swapchain}; return device.createSwapchainKHR(swapchain_create_info); } std::pair HPPHelloTriangle::create_vertex_buffer() { // Vertex data for a single colored triangle const std::vector vertices = { {{0.5f, -0.5f, 0.5f}, {1.0f, 0.0f, 0.0f}}, {{0.5f, 0.5f, 0.5f}, {0.0f, 1.0f, 0.0f}}, {{-0.5f, 0.5f, 0.5f}, {0.0f, 0.0f, 1.0f}}}; const vk::DeviceSize buffer_size = sizeof(vertices[0]) * vertices.size(); // Copy Vertex data to a buffer accessible by the device vk::BufferCreateInfo buffer_create_info{.size = buffer_size, .usage = vk::BufferUsageFlagBits::eVertexBuffer}; // We use the Vulkan Memory Allocator to find a memory type that can be written and mapped from the host // On most setups this will return a memory type that resides in VRAM and is accessible from the host VmaAllocationCreateInfo allocation_create_info{ .flags = VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT | VMA_ALLOCATION_CREATE_MAPPED_BIT, .usage = VMA_MEMORY_USAGE_AUTO, .requiredFlags = VK_MEMORY_PROPERTY_HOST_COHERENT_BIT}; vk::Buffer vertex_buffer; VmaAllocation vertex_buffer_allocation; VmaAllocationInfo allocation_info{}; vmaCreateBuffer(vma_allocator, reinterpret_cast(&buffer_create_info), &allocation_create_info, reinterpret_cast(&vertex_buffer), &vertex_buffer_allocation, &allocation_info); if (allocation_info.pMappedData) { memcpy(allocation_info.pMappedData, vertices.data(), buffer_size); } else { throw std::runtime_error("Could not map vertex buffer."); } return {vertex_buffer, vertex_buffer_allocation}; } VmaAllocator HPPHelloTriangle::create_vma_allocator() { // This sample uses the Vulkan Memory Alloctor (VMA), which needs to be set up VmaVulkanFunctions vma_vulkan_functions{ .vkGetInstanceProcAddr = VULKAN_HPP_DEFAULT_DISPATCHER.vkGetInstanceProcAddr, .vkGetDeviceProcAddr = VULKAN_HPP_DEFAULT_DISPATCHER.vkGetDeviceProcAddr}; VmaAllocatorCreateInfo allocator_info{.physicalDevice = gpu, .device = device, .pVulkanFunctions = &vma_vulkan_functions, .instance = instance}; VmaAllocator allocator; VkResult result = vmaCreateAllocator(&allocator_info, &allocator); if (result != VK_SUCCESS) { throw std::runtime_error("Could not create allocator for VMA allocator"); } return allocator; } /** * @brief Initializes the Vulkan framebuffers. */ void HPPHelloTriangle::init_framebuffers() { assert(swapchain_data.framebuffers.empty()); // Create framebuffer for each swapchain image view for (auto &image_view : swapchain_data.image_views) { // create the framebuffer. swapchain_data.framebuffers.push_back(vkb::common::create_framebuffer(device, render_pass, {image_view}, swapchain_data.extent)); } } /** * @brief Initializes the Vulkan swapchain. */ void HPPHelloTriangle::init_swapchain() { vk::SurfaceCapabilitiesKHR surface_properties = gpu.getSurfaceCapabilitiesKHR(surface); vk::Extent2D swapchain_extent = (surface_properties.currentExtent.width == 0xFFFFFFFF) ? swapchain_data.extent : surface_properties.currentExtent; vk::SurfaceFormatKHR surface_format = vkb::common::select_surface_format(gpu, surface); vk::SwapchainKHR old_swapchain = swapchain_data.swapchain; swapchain_data.swapchain = create_swapchain(swapchain_extent, surface_format, old_swapchain); if (old_swapchain) { for (vk::ImageView image_view : swapchain_data.image_views) { device.destroyImageView(image_view); } size_t image_count = device.getSwapchainImagesKHR(old_swapchain).size(); for (size_t i = 0; i < image_count; i++) { teardown_per_frame(per_frame_data[i]); } swapchain_data.image_views.clear(); device.destroySwapchainKHR(old_swapchain); } swapchain_data.extent = swapchain_extent; swapchain_data.format = surface_format.format; /// The swapchain images. std::vector swapchain_images = device.getSwapchainImagesKHR(swapchain_data.swapchain); size_t image_count = swapchain_images.size(); // 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. per_frame_data.clear(); per_frame_data.resize(image_count); for (size_t frame = 0; frame < image_count; frame++) { auto &pfd = per_frame_data[frame]; pfd.queue_submit_fence = device.createFence({.flags = vk::FenceCreateFlagBits::eSignaled}); pfd.primary_command_pool = device.createCommandPool({.flags = vk::CommandPoolCreateFlagBits::eTransient, .queueFamilyIndex = graphics_queue_index}); pfd.primary_command_buffer = vkb::common::allocate_command_buffer(device, pfd.primary_command_pool); } for (size_t i = 0; i < image_count; i++) { // Create an image view which we can render into. swapchain_data.image_views.push_back(create_image_view(swapchain_images[i])); } } /** * @brief Renders a triangle to the specified swapchain image. * @param swapchain_index The swapchain index for the image being rendered. */ void HPPHelloTriangle::render_triangle(uint32_t swapchain_index) { // Render to this framebuffer. vk::Framebuffer framebuffer = swapchain_data.framebuffers[swapchain_index]; // Allocate or re-use a primary command buffer. vk::CommandBuffer cmd = per_frame_data[swapchain_index].primary_command_buffer; // We will only submit this once before it's recycled. vk::CommandBufferBeginInfo begin_info{.flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit}; // Begin command recording cmd.begin(begin_info); // Set clear color values. vk::ClearValue clear_value; clear_value.color = vk::ClearColorValue(std::array({{0.01f, 0.01f, 0.033f, 1.0f}})); // Begin the render pass. vk::RenderPassBeginInfo rp_begin{.renderPass = render_pass, .framebuffer = framebuffer, .renderArea = {{0, 0}, {swapchain_data.extent.width, swapchain_data.extent.height}}, .clearValueCount = 1, .pClearValues = &clear_value}; // We will add draw commands in the same command buffer. cmd.beginRenderPass(rp_begin, vk::SubpassContents::eInline); // Bind the graphics pipeline. cmd.bindPipeline(vk::PipelineBindPoint::eGraphics, pipeline); vk::Viewport vp{0.0f, 0.0f, static_cast(swapchain_data.extent.width), static_cast(swapchain_data.extent.height), 0.0f, 1.0f}; // Set viewport dynamically cmd.setViewport(0, vp); vk::Rect2D scissor{{0, 0}, {swapchain_data.extent.width, swapchain_data.extent.height}}; // Set scissor dynamically cmd.setScissor(0, scissor); // Bind the vertex buffer to source the draw calls from. vk::DeviceSize offset = {0}; cmd.bindVertexBuffers(0, vertex_buffer, offset); // Draw three vertices with one instance. cmd.draw(3, 1, 0, 0); // Complete render pass. cmd.endRenderPass(); // Complete the command buffer. cmd.end(); // Submit it to the queue with a release semaphore. if (!per_frame_data[swapchain_index].swapchain_release_semaphore) { per_frame_data[swapchain_index].swapchain_release_semaphore = device.createSemaphore({}); } vk::PipelineStageFlags wait_stage{VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT}; vk::SubmitInfo info{.waitSemaphoreCount = 1, .pWaitSemaphores = &per_frame_data[swapchain_index].swapchain_acquire_semaphore, .pWaitDstStageMask = &wait_stage, .commandBufferCount = 1, .pCommandBuffers = &cmd, .signalSemaphoreCount = 1, .pSignalSemaphores = &per_frame_data[swapchain_index].swapchain_release_semaphore}; // Submit command buffer to graphics queue queue.submit(info, per_frame_data[swapchain_index].queue_submit_fence); } /** * @brief Select a physical device. */ void HPPHelloTriangle::select_physical_device_and_surface() { std::vector gpus = instance.enumeratePhysicalDevices(); bool found_graphics_queue_index = false; for (size_t i = 0; i < gpus.size() && !found_graphics_queue_index; i++) { gpu = gpus[i]; std::vector queue_family_properties = gpu.getQueueFamilyProperties(); if (queue_family_properties.empty()) { throw std::runtime_error("No queue family found."); } if (surface) { instance.destroySurfaceKHR(surface); } surface = static_cast(window->create_surface(static_cast(instance), static_cast(gpu))); if (!surface) { throw std::runtime_error("Failed to create window surface."); } for (uint32_t j = 0; j < vkb::to_u32(queue_family_properties.size()); j++) { vk::Bool32 supports_present = gpu.getSurfaceSupportKHR(j, surface); // Find a queue family which supports graphics and presentation. if ((queue_family_properties[j].queueFlags & vk::QueueFlagBits::eGraphics) && supports_present) { graphics_queue_index = j; found_graphics_queue_index = true; break; } } } if (!found_graphics_queue_index) { LOGE("Did not find suitable queue which supports graphics and presentation."); } } /** * @brief Tears down the framebuffers. If our swapchain changes, we will call this, and create a new swapchain. */ void HPPHelloTriangle::teardown_framebuffers() { // Wait until device is idle before teardown. queue.waitIdle(); for (auto &framebuffer : swapchain_data.framebuffers) { device.destroyFramebuffer(framebuffer); } swapchain_data.framebuffers.clear(); } /** * @brief Tears down the frame data. * @param per_frame_data The data of a frame. */ void HPPHelloTriangle::teardown_per_frame(FrameData &per_frame_data) { if (per_frame_data.queue_submit_fence) { device.destroyFence(per_frame_data.queue_submit_fence); per_frame_data.queue_submit_fence = nullptr; } if (per_frame_data.primary_command_buffer) { device.freeCommandBuffers(per_frame_data.primary_command_pool, per_frame_data.primary_command_buffer); per_frame_data.primary_command_buffer = nullptr; } if (per_frame_data.primary_command_pool) { device.destroyCommandPool(per_frame_data.primary_command_pool); per_frame_data.primary_command_pool = nullptr; } if (per_frame_data.swapchain_acquire_semaphore) { device.destroySemaphore(per_frame_data.swapchain_acquire_semaphore); per_frame_data.swapchain_acquire_semaphore = nullptr; } if (per_frame_data.swapchain_release_semaphore) { device.destroySemaphore(per_frame_data.swapchain_release_semaphore); per_frame_data.swapchain_release_semaphore = nullptr; } } std::unique_ptr create_hpp_hello_triangle() { return std::make_unique(); }