/* Copyright (c) 2019-2025, Sascha Willems * 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 "api_vulkan_sample.h" #include "core/device.h" #include "core/swapchain.h" #include "gltf_loader.h" #include "scene_graph/components/image.h" #include "scene_graph/components/sampler.h" #include "scene_graph/components/sub_mesh.h" #include "scene_graph/components/texture.h" #define VMA_IMPLEMENTATION #include bool ApiVulkanSample::prepare(const vkb::ApplicationOptions &options) { if (!VulkanSample::prepare(options)) { return false; } depth_format = vkb::get_suitable_depth_format(get_device().get_gpu().get_handle()); // Update width and height from surface extent to reflect command line arguments width = get_render_context().get_surface_extent().width; height = get_render_context().get_surface_extent().height; // Create synchronization objects VkSemaphoreCreateInfo semaphore_create_info = vkb::initializers::semaphore_create_info(); // Create a semaphore used to synchronize image presentation // Ensures that the current swapchain render target has completed presentation and has been released by the presentation engine, ready for rendering VK_CHECK(vkCreateSemaphore(get_device().get_handle(), &semaphore_create_info, nullptr, &semaphores.acquired_image_ready)); // Create a semaphore used to synchronize command submission // Ensures that the image is not presented until all commands have been sumbitted and executed VK_CHECK(vkCreateSemaphore(get_device().get_handle(), &semaphore_create_info, nullptr, &semaphores.render_complete)); // Set up submit info structure // Semaphores will stay the same during application lifetime // Command buffer submission info is set by each example submit_info = vkb::initializers::submit_info(); submit_info.pWaitDstStageMask = &submit_pipeline_stages; submit_info.waitSemaphoreCount = 1; submit_info.pWaitSemaphores = &semaphores.acquired_image_ready; submit_info.signalSemaphoreCount = 1; submit_info.pSignalSemaphores = &semaphores.render_complete; queue = get_device().get_queue_by_flags(VK_QUEUE_GRAPHICS_BIT, 0).get_handle(); create_swapchain_buffers(); create_command_pool(); create_command_buffers(); create_synchronization_primitives(); setup_depth_stencil(); setup_render_pass(); create_pipeline_cache(); setup_framebuffer(); prepare_gui(); return true; } void ApiVulkanSample::prepare_gui() { create_gui(*window, nullptr, 15.0f, true); get_gui().prepare(pipeline_cache, render_pass, {load_shader("uioverlay/uioverlay.vert.spv", VK_SHADER_STAGE_VERTEX_BIT), load_shader("uioverlay/uioverlay.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT)}); } void ApiVulkanSample::update(float delta_time) { if (view_updated) { view_updated = false; view_changed(); } assert(has_render_context()); render(delta_time); camera.update(delta_time); if (camera.moving()) { view_updated = true; } } bool ApiVulkanSample::resize(const uint32_t _width, const uint32_t _height) { if (!prepared) { return false; } get_render_context().handle_surface_changes(); // Don't recreate the swapchain if the dimensions haven't changed if (width == get_render_context().get_surface_extent().width && height == get_render_context().get_surface_extent().height) { return false; } width = get_render_context().get_surface_extent().width; height = get_render_context().get_surface_extent().height; prepared = false; // Ensure all operations on the device have been finished before destroying resources get_device().wait_idle(); create_swapchain_buffers(); // Recreate the frame buffers vkDestroyImageView(get_device().get_handle(), depth_stencil.view, nullptr); vkDestroyImage(get_device().get_handle(), depth_stencil.image, nullptr); vkFreeMemory(get_device().get_handle(), depth_stencil.mem, nullptr); setup_depth_stencil(); for (uint32_t i = 0; i < framebuffers.size(); i++) { vkDestroyFramebuffer(get_device().get_handle(), framebuffers[i], nullptr); framebuffers[i] = VK_NULL_HANDLE; } setup_framebuffer(); if ((width > 0.0f) && (height > 0.0f)) { if (has_gui()) { get_gui().resize(width, height); } } rebuild_command_buffers(); get_device().wait_idle(); if ((width > 0.0f) && (height > 0.0f)) { camera.update_aspect_ratio(static_cast(width) / static_cast(height)); } // Notify derived class view_changed(); prepared = true; return true; } void ApiVulkanSample::create_render_context() { // We always want an sRGB surface to match the display. // If we used a UNORM surface, we'd have to do the conversion to sRGB ourselves at the end of our fragment shaders. auto surface_priority_list = std::vector{{VK_FORMAT_B8G8R8A8_SRGB, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR}, {VK_FORMAT_R8G8B8A8_SRGB, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR}}; VulkanSample::create_render_context(surface_priority_list); } void ApiVulkanSample::input_event(const vkb::InputEvent &input_event) { VulkanSample::input_event(input_event); bool gui_captures_event = false; if (has_gui()) { gui_captures_event = get_gui().input_event(input_event); } if (!gui_captures_event) { if (input_event.get_source() == vkb::EventSource::Mouse) { const auto &mouse_button = static_cast(input_event); handle_mouse_move(static_cast(mouse_button.get_pos_x()), static_cast(mouse_button.get_pos_y())); if (mouse_button.get_action() == vkb::MouseAction::Down) { switch (mouse_button.get_button()) { case vkb::MouseButton::Left: mouse_buttons.left = true; break; case vkb::MouseButton::Right: mouse_buttons.right = true; break; case vkb::MouseButton::Middle: mouse_buttons.middle = true; break; default: break; } } else if (mouse_button.get_action() == vkb::MouseAction::Up) { switch (mouse_button.get_button()) { case vkb::MouseButton::Left: mouse_buttons.left = false; break; case vkb::MouseButton::Right: mouse_buttons.right = false; break; case vkb::MouseButton::Middle: mouse_buttons.middle = false; break; default: break; } } } else if (input_event.get_source() == vkb::EventSource::Touchscreen) { const auto &touch_event = static_cast(input_event); if (touch_event.get_action() == vkb::TouchAction::Down) { touch_down = true; touch_pos.x = static_cast(touch_event.get_pos_x()); touch_pos.y = static_cast(touch_event.get_pos_y()); mouse_pos.x = touch_event.get_pos_x(); mouse_pos.y = touch_event.get_pos_y(); mouse_buttons.left = true; } else if (touch_event.get_action() == vkb::TouchAction::Up) { touch_pos.x = static_cast(touch_event.get_pos_x()); touch_pos.y = static_cast(touch_event.get_pos_y()); touch_timer = 0.0; touch_down = false; camera.keys.up = false; mouse_buttons.left = false; } else if (touch_event.get_action() == vkb::TouchAction::Move) { bool handled = false; if (has_gui()) { ImGuiIO &io = ImGui::GetIO(); handled = io.WantCaptureMouse; } if (!handled) { int32_t eventX = static_cast(touch_event.get_pos_x()); int32_t eventY = static_cast(touch_event.get_pos_y()); float deltaX = static_cast(touch_pos.y - eventY) * rotation_speed * 0.5f; float deltaY = static_cast(touch_pos.x - eventX) * rotation_speed * 0.5f; camera.rotate(glm::vec3(deltaX, 0.0f, 0.0f)); camera.rotate(glm::vec3(0.0f, -deltaY, 0.0f)); rotation.x += deltaX; rotation.y -= deltaY; view_changed(); touch_pos.x = eventX; touch_pos.y = eventY; } } } else if (input_event.get_source() == vkb::EventSource::Keyboard) { const auto &key_button = static_cast(input_event); if (key_button.get_action() == vkb::KeyAction::Down) { switch (key_button.get_code()) { case vkb::KeyCode::W: camera.keys.up = true; break; case vkb::KeyCode::S: camera.keys.down = true; break; case vkb::KeyCode::A: camera.keys.left = true; break; case vkb::KeyCode::D: camera.keys.right = true; break; case vkb::KeyCode::P: paused = !paused; break; case vkb::KeyCode::F1: if (has_gui()) { get_gui().visible = !get_gui().visible; } break; default: break; } } else if (key_button.get_action() == vkb::KeyAction::Up) { switch (key_button.get_code()) { case vkb::KeyCode::W: camera.keys.up = false; break; case vkb::KeyCode::S: camera.keys.down = false; break; case vkb::KeyCode::A: camera.keys.left = false; break; case vkb::KeyCode::D: camera.keys.right = false; break; default: break; } } } } } void ApiVulkanSample::handle_mouse_move(int32_t x, int32_t y) { int32_t dx = static_cast(mouse_pos.x) - x; int32_t dy = static_cast(mouse_pos.y) - y; bool handled = false; if (has_gui()) { ImGuiIO &io = ImGui::GetIO(); handled = io.WantCaptureMouse; } mouse_moved(static_cast(x), static_cast(y), handled); if (handled) { mouse_pos = glm::vec2(static_cast(x), static_cast(y)); return; } if (mouse_buttons.left) { rotation.x += dy * 1.25f * rotation_speed; rotation.y -= dx * 1.25f * rotation_speed; camera.rotate(glm::vec3(dy * camera.rotation_speed, -dx * camera.rotation_speed, 0.0f)); view_updated = true; } if (mouse_buttons.right) { zoom += dy * .005f * zoom_speed; camera.translate(glm::vec3(-0.0f, 0.0f, dy * .005f * zoom_speed)); view_updated = true; } if (mouse_buttons.middle) { camera_pos.x -= dx * 0.01f; camera_pos.y -= dy * 0.01f; camera.translate(glm::vec3(-dx * 0.01f, -dy * 0.01f, 0.0f)); view_updated = true; } mouse_pos = glm::vec2(static_cast(x), static_cast(y)); } void ApiVulkanSample::mouse_moved(double x, double y, bool &handled) {} bool ApiVulkanSample::check_command_buffers() { for (auto &command_buffer : draw_cmd_buffers) { if (command_buffer == VK_NULL_HANDLE) { return false; } } return true; } void ApiVulkanSample::create_command_buffers() { // Create one command buffer for each swap chain image and reuse for rendering draw_cmd_buffers.resize(get_render_context().get_render_frames().size()); VkCommandBufferAllocateInfo allocate_info = vkb::initializers::command_buffer_allocate_info( cmd_pool, VK_COMMAND_BUFFER_LEVEL_PRIMARY, static_cast(draw_cmd_buffers.size())); VK_CHECK(vkAllocateCommandBuffers(get_device().get_handle(), &allocate_info, draw_cmd_buffers.data())); } void ApiVulkanSample::destroy_command_buffers() { vkFreeCommandBuffers(get_device().get_handle(), cmd_pool, static_cast(draw_cmd_buffers.size()), draw_cmd_buffers.data()); } void ApiVulkanSample::recreate_current_command_buffer() { auto &cmd = draw_cmd_buffers[current_buffer]; assert(cmd); vkFreeCommandBuffers(get_device().get_handle(), cmd_pool, 1, &cmd); VkCommandBufferAllocateInfo command_buffer_allocate_info{VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO, nullptr, cmd_pool, VK_COMMAND_BUFFER_LEVEL_PRIMARY, 1}; VK_CHECK(vkAllocateCommandBuffers(get_device().get_handle(), &command_buffer_allocate_info, &cmd)); } void ApiVulkanSample::create_pipeline_cache() { VkPipelineCacheCreateInfo pipeline_cache_create_info = {}; pipeline_cache_create_info.sType = VK_STRUCTURE_TYPE_PIPELINE_CACHE_CREATE_INFO; VK_CHECK(vkCreatePipelineCache(get_device().get_handle(), &pipeline_cache_create_info, nullptr, &pipeline_cache)); } VkPipelineShaderStageCreateInfo ApiVulkanSample::load_shader(const std::string &file, VkShaderStageFlagBits stage) { VkPipelineShaderStageCreateInfo shader_stage = {}; shader_stage.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; shader_stage.stage = stage; shader_stage.module = vkb::load_shader(file.c_str(), get_device().get_handle(), stage); shader_stage.pName = "main"; assert(shader_stage.module != VK_NULL_HANDLE); shader_modules.push_back(shader_stage.module); return shader_stage; } VkPipelineShaderStageCreateInfo ApiVulkanSample::load_shader(const std::string &sample_folder_name, const std::string &shader_filename, VkShaderStageFlagBits stage) { std::string full_file_name = sample_folder_name + "/" + get_shader_folder() + "/" + shader_filename; VkPipelineShaderStageCreateInfo shader_stage = {}; shader_stage.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; shader_stage.stage = stage; shader_stage.module = vkb::load_shader(full_file_name, get_device().get_handle(), stage); shader_stage.pName = "main"; assert(shader_stage.module != VK_NULL_HANDLE); shader_modules.push_back(shader_stage.module); return shader_stage; } void ApiVulkanSample::update_overlay(float delta_time, const std::function &additional_ui) { if (has_gui()) { frame_count++; accumulated_time += delta_time; if (0.5f < accumulated_time) { fps = static_cast(frame_count / accumulated_time); frame_count = 0; accumulated_time = 0.0f; } get_gui().show_simple_window(get_name(), fps, [this, additional_ui]() { on_update_ui_overlay(get_gui().get_drawer()); additional_ui(); }); get_gui().update(delta_time); if (get_gui().update_buffers() || get_gui().get_drawer().is_dirty()) { rebuild_command_buffers(); get_gui().get_drawer().clear(); } } } void ApiVulkanSample::draw_ui(const VkCommandBuffer command_buffer) { if (has_gui()) { const VkViewport viewport = vkb::initializers::viewport(static_cast(width), static_cast(height), 0.0f, 1.0f); const VkRect2D scissor = vkb::initializers::rect2D(width, height, 0, 0); vkCmdSetViewport(command_buffer, 0, 1, &viewport); vkCmdSetScissor(command_buffer, 0, 1, &scissor); get_gui().draw(command_buffer); } } void ApiVulkanSample::prepare_frame() { if (get_render_context().has_swapchain()) { handle_surface_changes(); // Acquire the next image from the swap chain VkResult result = get_render_context().get_swapchain().acquire_next_image(current_buffer, semaphores.acquired_image_ready, VK_NULL_HANDLE); // Recreate the swapchain if it's no longer compatible with the surface (OUT_OF_DATE) if (result == VK_ERROR_OUT_OF_DATE_KHR) { resize(width, height); } // VK_SUBOPTIMAL_KHR means that acquire was successful and semaphore is signaled but image is suboptimal // allow rendering frame to suboptimal swapchain as otherwise we would have to manually unsignal semaphore and acquire image again else if (result != VK_SUBOPTIMAL_KHR) { VK_CHECK(result); } } } void ApiVulkanSample::submit_frame() { if (get_render_context().has_swapchain()) { const auto &queue = get_device().get_queue_by_present(0); VkSwapchainKHR sc = get_render_context().get_swapchain().get_handle(); VkPresentInfoKHR present_info = {}; present_info.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR; present_info.pNext = NULL; present_info.swapchainCount = 1; present_info.pSwapchains = ≻ present_info.pImageIndices = ¤t_buffer; VkDisplayPresentInfoKHR disp_present_info{}; if (get_device().get_gpu().is_extension_supported(VK_KHR_DISPLAY_SWAPCHAIN_EXTENSION_NAME) && window->get_display_present_info(&disp_present_info, width, height)) { // Add display present info if supported and wanted present_info.pNext = &disp_present_info; } // Check if a wait semaphore has been specified to wait for before presenting the image if (semaphores.render_complete != VK_NULL_HANDLE) { present_info.pWaitSemaphores = &semaphores.render_complete; present_info.waitSemaphoreCount = 1; } VkResult present_result = queue.present(present_info); if (!((present_result == VK_SUCCESS) || (present_result == VK_SUBOPTIMAL_KHR))) { if (present_result == VK_ERROR_OUT_OF_DATE_KHR) { // Swap chain is no longer compatible with the surface and needs to be recreated resize(width, height); return; } else { VK_CHECK(present_result); } } } // DO NOT USE // vkDeviceWaitIdle and vkQueueWaitIdle are extremely expensive functions, and are used here purely for demonstrating the vulkan API // without having to concern ourselves with proper syncronization. These functions should NEVER be used inside the render loop like this (every frame). VK_CHECK(get_device().get_queue_by_present(0).wait_idle()); } ApiVulkanSample::~ApiVulkanSample() { if (has_device()) { get_device().wait_idle(); // Clean up Vulkan resources if (descriptor_pool != VK_NULL_HANDLE) { vkDestroyDescriptorPool(get_device().get_handle(), descriptor_pool, nullptr); } destroy_command_buffers(); if (render_pass != VK_NULL_HANDLE) { vkDestroyRenderPass(get_device().get_handle(), render_pass, nullptr); } for (uint32_t i = 0; i < framebuffers.size(); i++) { vkDestroyFramebuffer(get_device().get_handle(), framebuffers[i], nullptr); } for (auto &swapchain_buffer : swapchain_buffers) { vkDestroyImageView(get_device().get_handle(), swapchain_buffer.view, nullptr); } for (auto &shader_module : shader_modules) { vkDestroyShaderModule(get_device().get_handle(), shader_module, nullptr); } vkDestroyImageView(get_device().get_handle(), depth_stencil.view, nullptr); vkDestroyImage(get_device().get_handle(), depth_stencil.image, nullptr); vkFreeMemory(get_device().get_handle(), depth_stencil.mem, nullptr); vkDestroyPipelineCache(get_device().get_handle(), pipeline_cache, nullptr); vkDestroyCommandPool(get_device().get_handle(), cmd_pool, nullptr); vkDestroySemaphore(get_device().get_handle(), semaphores.acquired_image_ready, nullptr); vkDestroySemaphore(get_device().get_handle(), semaphores.render_complete, nullptr); for (auto &fence : wait_fences) { vkDestroyFence(get_device().get_handle(), fence, nullptr); } } } void ApiVulkanSample::view_changed() {} void ApiVulkanSample::build_command_buffers() {} void ApiVulkanSample::rebuild_command_buffers() { vkResetCommandPool(get_device().get_handle(), cmd_pool, 0); build_command_buffers(); } void ApiVulkanSample::create_synchronization_primitives() { // Wait fences to sync command buffer access VkFenceCreateInfo fence_create_info = vkb::initializers::fence_create_info(VK_FENCE_CREATE_SIGNALED_BIT); wait_fences.resize(draw_cmd_buffers.size()); for (auto &fence : wait_fences) { VK_CHECK(vkCreateFence(get_device().get_handle(), &fence_create_info, nullptr, &fence)); } } void ApiVulkanSample::create_command_pool() { VkCommandPoolCreateInfo command_pool_info = {}; command_pool_info.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; command_pool_info.queueFamilyIndex = get_device().get_queue_by_flags(VK_QUEUE_GRAPHICS_BIT | VK_QUEUE_COMPUTE_BIT, 0).get_family_index(); VK_CHECK(vkCreateCommandPool(get_device().get_handle(), &command_pool_info, nullptr, &cmd_pool)); } void ApiVulkanSample::setup_depth_stencil() { VkImageCreateInfo image_create_info{}; image_create_info.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; image_create_info.imageType = VK_IMAGE_TYPE_2D; image_create_info.format = depth_format; image_create_info.extent = {get_render_context().get_surface_extent().width, get_render_context().get_surface_extent().height, 1}; image_create_info.mipLevels = 1; image_create_info.arrayLayers = 1; image_create_info.samples = VK_SAMPLE_COUNT_1_BIT; image_create_info.tiling = VK_IMAGE_TILING_OPTIMAL; image_create_info.usage = VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT; VK_CHECK(vkCreateImage(get_device().get_handle(), &image_create_info, nullptr, &depth_stencil.image)); VkMemoryRequirements memReqs{}; vkGetImageMemoryRequirements(get_device().get_handle(), depth_stencil.image, &memReqs); VkMemoryAllocateInfo memory_allocation{}; memory_allocation.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; memory_allocation.allocationSize = memReqs.size; memory_allocation.memoryTypeIndex = get_device().get_gpu().get_memory_type(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); VK_CHECK(vkAllocateMemory(get_device().get_handle(), &memory_allocation, nullptr, &depth_stencil.mem)); VK_CHECK(vkBindImageMemory(get_device().get_handle(), depth_stencil.image, depth_stencil.mem, 0)); VkImageViewCreateInfo image_view_create_info{}; image_view_create_info.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; image_view_create_info.viewType = VK_IMAGE_VIEW_TYPE_2D; image_view_create_info.image = depth_stencil.image; image_view_create_info.format = depth_format; image_view_create_info.subresourceRange.baseMipLevel = 0; image_view_create_info.subresourceRange.levelCount = 1; image_view_create_info.subresourceRange.baseArrayLayer = 0; image_view_create_info.subresourceRange.layerCount = 1; image_view_create_info.subresourceRange.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT; // Stencil aspect should only be set on depth + stencil formats (VK_FORMAT_D16_UNORM_S8_UINT..VK_FORMAT_D32_SFLOAT_S8_UINT if (depth_format >= VK_FORMAT_D16_UNORM_S8_UINT) { image_view_create_info.subresourceRange.aspectMask |= VK_IMAGE_ASPECT_STENCIL_BIT; } VK_CHECK(vkCreateImageView(get_device().get_handle(), &image_view_create_info, nullptr, &depth_stencil.view)); } void ApiVulkanSample::setup_framebuffer() { VkImageView attachments[2]{}; // Depth/Stencil attachment is the same for all frame buffers attachments[1] = depth_stencil.view; VkFramebufferCreateInfo framebuffer_create_info = {}; framebuffer_create_info.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO; framebuffer_create_info.pNext = NULL; framebuffer_create_info.renderPass = render_pass; framebuffer_create_info.attachmentCount = 2; framebuffer_create_info.pAttachments = attachments; framebuffer_create_info.width = get_render_context().get_surface_extent().width; framebuffer_create_info.height = get_render_context().get_surface_extent().height; framebuffer_create_info.layers = 1; // Delete existing frame buffers if (framebuffers.size() > 0) { for (uint32_t i = 0; i < framebuffers.size(); i++) { if (framebuffers[i] != VK_NULL_HANDLE) { vkDestroyFramebuffer(get_device().get_handle(), framebuffers[i], nullptr); } } } // Create frame buffers for every swap chain image framebuffers.resize(get_render_context().get_render_frames().size()); for (uint32_t i = 0; i < framebuffers.size(); i++) { attachments[0] = swapchain_buffers[i].view; VK_CHECK(vkCreateFramebuffer(get_device().get_handle(), &framebuffer_create_info, nullptr, &framebuffers[i])); } } void ApiVulkanSample::setup_render_pass() { std::array attachments = {}; // Color attachment attachments[0].format = get_render_context().get_format(); attachments[0].samples = VK_SAMPLE_COUNT_1_BIT; attachments[0].loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; attachments[0].storeOp = VK_ATTACHMENT_STORE_OP_STORE; attachments[0].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; attachments[0].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; attachments[0].initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; attachments[0].finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; // Depth attachment attachments[1].format = depth_format; attachments[1].samples = VK_SAMPLE_COUNT_1_BIT; attachments[1].loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; attachments[1].storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; attachments[1].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; attachments[1].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; attachments[1].initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; attachments[1].finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; VkAttachmentReference color_reference = {}; color_reference.attachment = 0; color_reference.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; VkAttachmentReference depth_reference = {}; depth_reference.attachment = 1; depth_reference.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; VkSubpassDescription subpass_description = {}; subpass_description.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; subpass_description.colorAttachmentCount = 1; subpass_description.pColorAttachments = &color_reference; subpass_description.pDepthStencilAttachment = &depth_reference; subpass_description.inputAttachmentCount = 0; subpass_description.pInputAttachments = nullptr; subpass_description.preserveAttachmentCount = 0; subpass_description.pPreserveAttachments = nullptr; subpass_description.pResolveAttachments = nullptr; // Subpass dependencies for layout transitions std::array dependencies{}; dependencies[0].srcSubpass = VK_SUBPASS_EXTERNAL; dependencies[0].dstSubpass = 0; dependencies[0].srcStageMask = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT; dependencies[0].dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT; dependencies[0].srcAccessMask = VK_ACCESS_NONE_KHR; dependencies[0].dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT; dependencies[0].dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT; dependencies[1].srcSubpass = 0; dependencies[1].dstSubpass = VK_SUBPASS_EXTERNAL; dependencies[1].srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT; dependencies[1].dstStageMask = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT; dependencies[1].srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT; dependencies[1].dstAccessMask = VK_ACCESS_MEMORY_READ_BIT; dependencies[1].dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT; VkRenderPassCreateInfo render_pass_create_info = {}; render_pass_create_info.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO; render_pass_create_info.attachmentCount = static_cast(attachments.size()); render_pass_create_info.pAttachments = attachments.data(); render_pass_create_info.subpassCount = 1; render_pass_create_info.pSubpasses = &subpass_description; render_pass_create_info.dependencyCount = static_cast(dependencies.size()); render_pass_create_info.pDependencies = dependencies.data(); VK_CHECK(vkCreateRenderPass(get_device().get_handle(), &render_pass_create_info, nullptr, &render_pass)); } void ApiVulkanSample::update_render_pass_flags(uint32_t flags) { vkDestroyRenderPass(get_device().get_handle(), render_pass, nullptr); VkAttachmentLoadOp color_attachment_load_op = VK_ATTACHMENT_LOAD_OP_CLEAR; VkAttachmentStoreOp color_attachment_store_op = VK_ATTACHMENT_STORE_OP_STORE; VkImageLayout color_attachment_image_layout = VK_IMAGE_LAYOUT_UNDEFINED; // Samples can keep the color attachment contents, e.g. if they have previously written to the swap chain images if (flags & RenderPassCreateFlags::ColorAttachmentLoad) { color_attachment_load_op = VK_ATTACHMENT_LOAD_OP_LOAD; color_attachment_image_layout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; } std::array attachments = {}; // Color attachment attachments[0].format = get_render_context().get_format(); attachments[0].samples = VK_SAMPLE_COUNT_1_BIT; attachments[0].loadOp = color_attachment_load_op; attachments[0].storeOp = color_attachment_store_op; attachments[0].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; attachments[0].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; attachments[0].initialLayout = color_attachment_image_layout; attachments[0].finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; // Depth attachment attachments[1].format = depth_format; attachments[1].samples = VK_SAMPLE_COUNT_1_BIT; attachments[1].loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; attachments[1].storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; attachments[1].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; attachments[1].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; attachments[1].initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; attachments[1].finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; VkAttachmentReference color_reference = {}; color_reference.attachment = 0; color_reference.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; VkAttachmentReference depth_reference = {}; depth_reference.attachment = 1; depth_reference.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; VkSubpassDescription subpass_description = {}; subpass_description.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; subpass_description.colorAttachmentCount = 1; subpass_description.pColorAttachments = &color_reference; subpass_description.pDepthStencilAttachment = &depth_reference; subpass_description.inputAttachmentCount = 0; subpass_description.pInputAttachments = nullptr; subpass_description.preserveAttachmentCount = 0; subpass_description.pPreserveAttachments = nullptr; subpass_description.pResolveAttachments = nullptr; // Subpass dependencies for layout transitions std::array dependencies{}; dependencies[0].srcSubpass = VK_SUBPASS_EXTERNAL; dependencies[0].dstSubpass = 0; dependencies[0].srcStageMask = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT; dependencies[0].dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT; dependencies[0].srcAccessMask = VK_ACCESS_NONE_KHR; dependencies[0].dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT; dependencies[0].dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT; dependencies[1].srcSubpass = 0; dependencies[1].dstSubpass = VK_SUBPASS_EXTERNAL; dependencies[1].srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT; dependencies[1].dstStageMask = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT; dependencies[1].srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT; dependencies[1].dstAccessMask = VK_ACCESS_MEMORY_READ_BIT; dependencies[1].dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT; VkRenderPassCreateInfo render_pass_create_info = {}; render_pass_create_info.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO; render_pass_create_info.attachmentCount = static_cast(attachments.size()); render_pass_create_info.pAttachments = attachments.data(); render_pass_create_info.subpassCount = 1; render_pass_create_info.pSubpasses = &subpass_description; render_pass_create_info.dependencyCount = static_cast(dependencies.size()); render_pass_create_info.pDependencies = dependencies.data(); VK_CHECK(vkCreateRenderPass(get_device().get_handle(), &render_pass_create_info, nullptr, &render_pass)); } void ApiVulkanSample::on_update_ui_overlay(vkb::Drawer &drawer) {} void ApiVulkanSample::create_swapchain_buffers() { if (get_render_context().has_swapchain()) { auto &images = get_render_context().get_swapchain().get_images(); // Get the swap chain buffers containing the image and imageview for (auto &swapchain_buffer : swapchain_buffers) { vkDestroyImageView(get_device().get_handle(), swapchain_buffer.view, nullptr); } swapchain_buffers.clear(); swapchain_buffers.resize(images.size()); for (uint32_t i = 0; i < images.size(); i++) { VkImageViewCreateInfo color_attachment_view = {}; color_attachment_view.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; color_attachment_view.pNext = NULL; color_attachment_view.format = get_render_context().get_swapchain().get_format(); color_attachment_view.components = { VK_COMPONENT_SWIZZLE_R, VK_COMPONENT_SWIZZLE_G, VK_COMPONENT_SWIZZLE_B, VK_COMPONENT_SWIZZLE_A}; color_attachment_view.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; color_attachment_view.subresourceRange.baseMipLevel = 0; color_attachment_view.subresourceRange.levelCount = 1; color_attachment_view.subresourceRange.baseArrayLayer = 0; color_attachment_view.subresourceRange.layerCount = 1; color_attachment_view.viewType = VK_IMAGE_VIEW_TYPE_2D; color_attachment_view.flags = 0; swapchain_buffers[i].image = images[i]; color_attachment_view.image = swapchain_buffers[i].image; VK_CHECK(vkCreateImageView(get_device().get_handle(), &color_attachment_view, nullptr, &swapchain_buffers[i].view)); } } else { auto &frames = get_render_context().get_render_frames(); // Get the swap chain buffers containing the image and imageview swapchain_buffers.clear(); swapchain_buffers.resize(frames.size()); for (uint32_t i = 0; i < frames.size(); i++) { auto &image_view = *frames[i]->get_render_target().get_views().begin(); swapchain_buffers[i].image = image_view.get_image().get_handle(); swapchain_buffers[i].view = image_view.get_handle(); } } } void ApiVulkanSample::update_swapchain_image_usage_flags(std::set image_usage_flags) { get_render_context().update_swapchain(image_usage_flags); create_swapchain_buffers(); setup_framebuffer(); } void ApiVulkanSample::handle_surface_changes() { VkSurfaceCapabilitiesKHR surface_properties; VK_CHECK(vkGetPhysicalDeviceSurfaceCapabilitiesKHR(get_device().get_gpu().get_handle(), get_render_context().get_swapchain().get_surface(), &surface_properties)); if ((surface_properties.currentExtent.width != get_render_context().get_surface_extent().width || surface_properties.currentExtent.height != get_render_context().get_surface_extent().height) && (surface_properties.currentExtent.width != 0xFFFFFFFF && surface_properties.currentExtent.height != 0xFFFFFFFF)) { resize(surface_properties.currentExtent.width, surface_properties.currentExtent.height); } } VkDescriptorBufferInfo ApiVulkanSample::create_descriptor(vkb::core::BufferC &buffer, VkDeviceSize size, VkDeviceSize offset) { VkDescriptorBufferInfo descriptor{}; descriptor.buffer = buffer.get_handle(); descriptor.range = size; descriptor.offset = offset; return descriptor; } VkDescriptorImageInfo ApiVulkanSample::create_descriptor(Texture &texture, VkDescriptorType descriptor_type) { VkDescriptorImageInfo descriptor{}; descriptor.sampler = texture.sampler; descriptor.imageView = texture.image->get_vk_image_view().get_handle(); // Add image layout info based on descriptor type switch (descriptor_type) { case VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER: case VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT: if (vkb::is_depth_stencil_format(texture.image->get_vk_image_view().get_format())) { descriptor.imageLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL; } else { assert(!vkb::is_depth_format(texture.image->get_vk_image_view().get_format())); descriptor.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; } break; case VK_DESCRIPTOR_TYPE_STORAGE_IMAGE: descriptor.imageLayout = VK_IMAGE_LAYOUT_GENERAL; break; default: descriptor.imageLayout = VK_IMAGE_LAYOUT_UNDEFINED; break; } return descriptor; } Texture ApiVulkanSample::load_texture(const std::string &file, vkb::sg::Image::ContentType content_type) { Texture texture{}; texture.image = vkb::sg::Image::load(file, file, content_type); texture.image->create_vk_image(get_device()); const auto &queue = get_device().get_queue_by_flags(VK_QUEUE_GRAPHICS_BIT, 0); VkCommandBuffer command_buffer = get_device().create_command_buffer(VK_COMMAND_BUFFER_LEVEL_PRIMARY, true); vkb::core::BufferC stage_buffer = vkb::core::BufferC::create_staging_buffer(get_device(), texture.image->get_data()); // Setup buffer copy regions for each mip level std::vector bufferCopyRegions; auto &mipmaps = texture.image->get_mipmaps(); for (size_t i = 0; i < mipmaps.size(); i++) { VkBufferImageCopy buffer_copy_region = {}; buffer_copy_region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; buffer_copy_region.imageSubresource.mipLevel = vkb::to_u32(i); buffer_copy_region.imageSubresource.baseArrayLayer = 0; buffer_copy_region.imageSubresource.layerCount = 1; buffer_copy_region.imageExtent.width = texture.image->get_extent().width >> i; buffer_copy_region.imageExtent.height = texture.image->get_extent().height >> i; buffer_copy_region.imageExtent.depth = 1; buffer_copy_region.bufferOffset = mipmaps[i].offset; bufferCopyRegions.push_back(buffer_copy_region); } VkImageSubresourceRange subresource_range = {}; subresource_range.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; subresource_range.baseMipLevel = 0; subresource_range.levelCount = vkb::to_u32(mipmaps.size()); subresource_range.layerCount = 1; // Image barrier for optimal image (target) // Optimal image will be used as destination for the copy vkb::image_layout_transition(command_buffer, texture.image->get_vk_image().get_handle(), VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, subresource_range); // Copy mip levels from staging buffer vkCmdCopyBufferToImage( command_buffer, stage_buffer.get_handle(), texture.image->get_vk_image().get_handle(), VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, static_cast(bufferCopyRegions.size()), bufferCopyRegions.data()); // Change texture image layout to shader read after all mip levels have been copied vkb::image_layout_transition(command_buffer, texture.image->get_vk_image().get_handle(), VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, subresource_range); get_device().flush_command_buffer(command_buffer, queue.get_handle()); // Calculate valid filter and mipmap modes VkFilter filter = VK_FILTER_LINEAR; VkSamplerMipmapMode mipmap_mode = VK_SAMPLER_MIPMAP_MODE_LINEAR; vkb::make_filters_valid(get_device().get_gpu().get_handle(), texture.image->get_format(), &filter, &mipmap_mode); // Create a defaultsampler VkSamplerCreateInfo sampler_create_info = {}; sampler_create_info.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO; sampler_create_info.magFilter = filter; sampler_create_info.minFilter = filter; sampler_create_info.mipmapMode = mipmap_mode; sampler_create_info.addressModeU = VK_SAMPLER_ADDRESS_MODE_REPEAT; sampler_create_info.addressModeV = VK_SAMPLER_ADDRESS_MODE_REPEAT; sampler_create_info.addressModeW = VK_SAMPLER_ADDRESS_MODE_REPEAT; sampler_create_info.mipLodBias = 0.0f; sampler_create_info.compareOp = VK_COMPARE_OP_NEVER; sampler_create_info.minLod = 0.0f; // Max level-of-detail should match mip level count sampler_create_info.maxLod = static_cast(mipmaps.size()); // Only enable anisotropic filtering if enabled on the device // Note that for simplicity, we will always be using max. available anisotropy level for the current device // This may have an impact on performance, esp. on lower-specced devices // In a real-world scenario the level of anisotropy should be a user setting or e.g. lowered for mobile devices by default sampler_create_info.maxAnisotropy = get_device().get_gpu().get_requested_features().samplerAnisotropy ? (get_device().get_gpu().get_properties().limits.maxSamplerAnisotropy) : 1.0f; sampler_create_info.anisotropyEnable = get_device().get_gpu().get_requested_features().samplerAnisotropy; sampler_create_info.borderColor = VK_BORDER_COLOR_FLOAT_OPAQUE_WHITE; VK_CHECK(vkCreateSampler(get_device().get_handle(), &sampler_create_info, nullptr, &texture.sampler)); return texture; } Texture ApiVulkanSample::load_texture_array(const std::string &file, vkb::sg::Image::ContentType content_type) { Texture texture{}; texture.image = vkb::sg::Image::load(file, file, content_type); texture.image->create_vk_image(get_device(), VK_IMAGE_VIEW_TYPE_2D_ARRAY); const auto &queue = get_device().get_queue_by_flags(VK_QUEUE_GRAPHICS_BIT, 0); VkCommandBuffer command_buffer = get_device().create_command_buffer(VK_COMMAND_BUFFER_LEVEL_PRIMARY, true); vkb::core::BufferC stage_buffer = vkb::core::BufferC::create_staging_buffer(get_device(), texture.image->get_data()); // Setup buffer copy regions for each mip level std::vector buffer_copy_regions; auto &mipmaps = texture.image->get_mipmaps(); const auto &layers = texture.image->get_layers(); auto &offsets = texture.image->get_offsets(); for (uint32_t layer = 0; layer < layers; layer++) { for (size_t i = 0; i < mipmaps.size(); i++) { VkBufferImageCopy buffer_copy_region = {}; buffer_copy_region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; buffer_copy_region.imageSubresource.mipLevel = vkb::to_u32(i); buffer_copy_region.imageSubresource.baseArrayLayer = layer; buffer_copy_region.imageSubresource.layerCount = 1; buffer_copy_region.imageExtent.width = texture.image->get_extent().width >> i; buffer_copy_region.imageExtent.height = texture.image->get_extent().height >> i; buffer_copy_region.imageExtent.depth = 1; buffer_copy_region.bufferOffset = offsets[layer][i]; buffer_copy_regions.push_back(buffer_copy_region); } } VkImageSubresourceRange subresource_range = {}; subresource_range.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; subresource_range.baseMipLevel = 0; subresource_range.levelCount = vkb::to_u32(mipmaps.size()); subresource_range.layerCount = layers; // Image barrier for optimal image (target) // Optimal image will be used as destination for the copy vkb::image_layout_transition(command_buffer, texture.image->get_vk_image().get_handle(), VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, subresource_range); // Copy mip levels from staging buffer vkCmdCopyBufferToImage( command_buffer, stage_buffer.get_handle(), texture.image->get_vk_image().get_handle(), VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, static_cast(buffer_copy_regions.size()), buffer_copy_regions.data()); // Change texture image layout to shader read after all mip levels have been copied vkb::image_layout_transition(command_buffer, texture.image->get_vk_image().get_handle(), VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, subresource_range); get_device().flush_command_buffer(command_buffer, queue.get_handle()); // Calculate valid filter and mipmap modes VkFilter filter = VK_FILTER_LINEAR; VkSamplerMipmapMode mipmap_mode = VK_SAMPLER_MIPMAP_MODE_LINEAR; vkb::make_filters_valid(get_device().get_gpu().get_handle(), texture.image->get_format(), &filter, &mipmap_mode); // Create a defaultsampler VkSamplerCreateInfo sampler_create_info = {}; sampler_create_info.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO; sampler_create_info.magFilter = filter; sampler_create_info.minFilter = filter; sampler_create_info.mipmapMode = mipmap_mode; sampler_create_info.addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; sampler_create_info.addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; sampler_create_info.addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; sampler_create_info.mipLodBias = 0.0f; sampler_create_info.compareOp = VK_COMPARE_OP_NEVER; sampler_create_info.minLod = 0.0f; // Max level-of-detail should match mip level count sampler_create_info.maxLod = static_cast(mipmaps.size()); // Only enable anisotropic filtering if enabled on the devicec sampler_create_info.maxAnisotropy = get_device().get_gpu().get_features().samplerAnisotropy ? get_device().get_gpu().get_properties().limits.maxSamplerAnisotropy : 1.0f; sampler_create_info.anisotropyEnable = get_device().get_gpu().get_features().samplerAnisotropy; sampler_create_info.borderColor = VK_BORDER_COLOR_FLOAT_OPAQUE_WHITE; VK_CHECK(vkCreateSampler(get_device().get_handle(), &sampler_create_info, nullptr, &texture.sampler)); return texture; } Texture ApiVulkanSample::load_texture_cubemap(const std::string &file, vkb::sg::Image::ContentType content_type) { Texture texture{}; texture.image = vkb::sg::Image::load(file, file, content_type); texture.image->create_vk_image(get_device(), VK_IMAGE_VIEW_TYPE_CUBE, VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT); const auto &queue = get_device().get_queue_by_flags(VK_QUEUE_GRAPHICS_BIT, 0); VkCommandBuffer command_buffer = get_device().create_command_buffer(VK_COMMAND_BUFFER_LEVEL_PRIMARY, true); vkb::core::BufferC stage_buffer = vkb::core::BufferC::create_staging_buffer(get_device(), texture.image->get_data()); // Setup buffer copy regions for each mip level std::vector buffer_copy_regions; auto &mipmaps = texture.image->get_mipmaps(); const auto &layers = texture.image->get_layers(); auto &offsets = texture.image->get_offsets(); for (uint32_t layer = 0; layer < layers; layer++) { for (size_t i = 0; i < mipmaps.size(); i++) { VkBufferImageCopy buffer_copy_region = {}; buffer_copy_region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; buffer_copy_region.imageSubresource.mipLevel = vkb::to_u32(i); buffer_copy_region.imageSubresource.baseArrayLayer = layer; buffer_copy_region.imageSubresource.layerCount = 1; buffer_copy_region.imageExtent.width = texture.image->get_extent().width >> i; buffer_copy_region.imageExtent.height = texture.image->get_extent().height >> i; buffer_copy_region.imageExtent.depth = 1; buffer_copy_region.bufferOffset = offsets[layer][i]; buffer_copy_regions.push_back(buffer_copy_region); } } VkImageSubresourceRange subresource_range = {}; subresource_range.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; subresource_range.baseMipLevel = 0; subresource_range.levelCount = vkb::to_u32(mipmaps.size()); subresource_range.layerCount = layers; // Image barrier for optimal image (target) // Optimal image will be used as destination for the copy vkb::image_layout_transition(command_buffer, texture.image->get_vk_image().get_handle(), VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, subresource_range); // Copy mip levels from staging buffer vkCmdCopyBufferToImage( command_buffer, stage_buffer.get_handle(), texture.image->get_vk_image().get_handle(), VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, static_cast(buffer_copy_regions.size()), buffer_copy_regions.data()); // Change texture image layout to shader read after all mip levels have been copied vkb::image_layout_transition(command_buffer, texture.image->get_vk_image().get_handle(), VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, subresource_range); get_device().flush_command_buffer(command_buffer, queue.get_handle()); // Calculate valid filter and mipmap modes VkFilter filter = VK_FILTER_LINEAR; VkSamplerMipmapMode mipmap_mode = VK_SAMPLER_MIPMAP_MODE_LINEAR; vkb::make_filters_valid(get_device().get_gpu().get_handle(), texture.image->get_format(), &filter, &mipmap_mode); // Create a defaultsampler VkSamplerCreateInfo sampler_create_info = {}; sampler_create_info.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO; sampler_create_info.magFilter = filter; sampler_create_info.minFilter = filter; sampler_create_info.mipmapMode = mipmap_mode; sampler_create_info.addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; sampler_create_info.addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; sampler_create_info.addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; sampler_create_info.mipLodBias = 0.0f; sampler_create_info.compareOp = VK_COMPARE_OP_NEVER; sampler_create_info.minLod = 0.0f; // Max level-of-detail should match mip level count sampler_create_info.maxLod = static_cast(mipmaps.size()); // Only enable anisotropic filtering if enabled on the devicec sampler_create_info.maxAnisotropy = get_device().get_gpu().get_features().samplerAnisotropy ? get_device().get_gpu().get_properties().limits.maxSamplerAnisotropy : 1.0f; sampler_create_info.anisotropyEnable = get_device().get_gpu().get_features().samplerAnisotropy; sampler_create_info.borderColor = VK_BORDER_COLOR_FLOAT_OPAQUE_WHITE; VK_CHECK(vkCreateSampler(get_device().get_handle(), &sampler_create_info, nullptr, &texture.sampler)); return texture; } std::unique_ptr ApiVulkanSample::load_model(const std::string &file, uint32_t index, bool storage_buffer, VkBufferUsageFlags additional_buffer_usage_flags) { vkb::GLTFLoader loader{get_device()}; std::unique_ptr model = loader.read_model_from_file(file, index, storage_buffer, additional_buffer_usage_flags); if (!model) { LOGE("Cannot load model from file: {}", file.c_str()); throw std::runtime_error("Cannot load model from: " + file); } return model; } void ApiVulkanSample::draw_model(std::unique_ptr &model, VkCommandBuffer command_buffer, uint32_t instance_count) { VkDeviceSize offsets[1] = {0}; const auto &vertex_buffer = model->vertex_buffers.at("vertex_buffer"); auto &index_buffer = model->index_buffer; vkCmdBindVertexBuffers(command_buffer, 0, 1, vertex_buffer.get(), offsets); vkCmdBindIndexBuffer(command_buffer, index_buffer->get_handle(), 0, model->index_type); vkCmdDrawIndexed(command_buffer, model->vertex_indices, instance_count, 0, 0, 0); } void ApiVulkanSample::with_command_buffer(const std::function &f, VkSemaphore signalSemaphore) { VkCommandBuffer command_buffer = get_device().create_command_buffer(VK_COMMAND_BUFFER_LEVEL_PRIMARY, true); f(command_buffer); get_device().flush_command_buffer(command_buffer, queue, true, signalSemaphore); } void ApiVulkanSample::with_vkb_command_buffer(const std::function &f) { auto cmd = get_device().get_command_pool().request_command_buffer(); cmd->begin(VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT, VK_NULL_HANDLE); f(*cmd); cmd->end(); auto &queue = get_device().get_queue_by_flags(VK_QUEUE_GRAPHICS_BIT, 0); queue.submit(*cmd, get_device().get_fence_pool().request_fence()); get_device().get_fence_pool().wait(); }