|  | // Copyright 2013 The Flutter Authors. All rights reserved. | 
|  | // Use of this source code is governed by a BSD-style license that can be | 
|  | // found in the LICENSE file. | 
|  |  | 
|  | #include <chrono> | 
|  | #include <cstdlib> | 
|  | #include <iostream> | 
|  | #include <optional> | 
|  | #include <tuple> | 
|  | #include <vector> | 
|  |  | 
|  | // Use vulkan.hpp's convenient proc table and resolver. | 
|  | #define VULKAN_HPP_NO_EXCEPTIONS 1 | 
|  | #define VULKAN_HPP_DISPATCH_LOADER_DYNAMIC 1 | 
|  | #include "vulkan/vulkan.hpp" | 
|  | VULKAN_HPP_DEFAULT_DISPATCH_LOADER_DYNAMIC_STORAGE | 
|  |  | 
|  | // Convenient reference to vulkan.hpp's global proc table. | 
|  | auto& d = vk::defaultDispatchLoaderDynamic; | 
|  |  | 
|  | // GLFW needs to be included after Vulkan. | 
|  | #include "GLFW/glfw3.h" | 
|  |  | 
|  | #include "embedder.h"  // Flutter's Embedder ABI. | 
|  |  | 
|  | static const bool g_enable_validation_layers = true; | 
|  | // This value is calculated after the window is created. | 
|  | static double g_pixelRatio = 1.0; | 
|  | static const size_t kInitialWindowWidth = 800; | 
|  | static const size_t kInitialWindowHeight = 600; | 
|  | // Use `VK_PRESENT_MODE_FIFO_KHR` for full vsync (one swap per screen refresh), | 
|  | // `VK_PRESENT_MODE_MAILBOX_KHR` for continual swap without horizontal tearing, | 
|  | // or `VK_PRESENT_MODE_IMMEDIATE_KHR` for no vsync. | 
|  | static const VkPresentModeKHR kPreferredPresentMode = VK_PRESENT_MODE_FIFO_KHR; | 
|  |  | 
|  | static_assert(FLUTTER_ENGINE_VERSION == 1, | 
|  | "This Flutter Embedder was authored against the stable Flutter " | 
|  | "API at version 1. There has been a serious breakage in the " | 
|  | "API. Please read the ChangeLog and take appropriate action " | 
|  | "before updating this assertion"); | 
|  |  | 
|  | /// Global struct for holding the Window+Vulkan state. | 
|  | struct { | 
|  | GLFWwindow* window; | 
|  |  | 
|  | std::vector<const char*> enabled_instance_extensions; | 
|  | VkInstance instance; | 
|  | VkSurfaceKHR surface; | 
|  |  | 
|  | VkPhysicalDevice physical_device; | 
|  | std::vector<const char*> enabled_device_extensions; | 
|  | VkDevice device; | 
|  | uint32_t queue_family_index; | 
|  | VkQueue queue; | 
|  |  | 
|  | VkCommandPool swapchain_command_pool; | 
|  | std::vector<VkCommandBuffer> present_transition_buffers; | 
|  |  | 
|  | VkFence image_ready_fence; | 
|  | VkSemaphore present_transition_semaphore; | 
|  |  | 
|  | VkSurfaceFormatKHR surface_format; | 
|  | VkSwapchainKHR swapchain; | 
|  | std::vector<VkImage> swapchain_images; | 
|  | uint32_t last_image_index; | 
|  |  | 
|  | FlutterEngine engine; | 
|  |  | 
|  | bool resize_pending = false; | 
|  | } g_state; | 
|  |  | 
|  | void GLFW_ErrorCallback(int error, const char* description) { | 
|  | std::cerr << "GLFW Error: (" << error << ") " << description << std::endl; | 
|  | } | 
|  |  | 
|  | void GLFWcursorPositionCallbackAtPhase(GLFWwindow* window, | 
|  | FlutterPointerPhase phase, | 
|  | double x, | 
|  | double y) { | 
|  | FlutterPointerEvent event = {}; | 
|  | event.struct_size = sizeof(event); | 
|  | event.phase = phase; | 
|  | event.x = x * g_pixelRatio; | 
|  | event.y = y * g_pixelRatio; | 
|  | event.timestamp = | 
|  | std::chrono::duration_cast<std::chrono::microseconds>( | 
|  | std::chrono::high_resolution_clock::now().time_since_epoch()) | 
|  | .count(); | 
|  | FlutterEngineSendPointerEvent(g_state.engine, &event, 1); | 
|  | } | 
|  |  | 
|  | void GLFWcursorPositionCallback(GLFWwindow* window, double x, double y) { | 
|  | GLFWcursorPositionCallbackAtPhase(window, FlutterPointerPhase::kMove, x, y); | 
|  | } | 
|  |  | 
|  | void GLFWmouseButtonCallback(GLFWwindow* window, | 
|  | int key, | 
|  | int action, | 
|  | int mods) { | 
|  | if (key == GLFW_MOUSE_BUTTON_1 && action == GLFW_PRESS) { | 
|  | double x, y; | 
|  | glfwGetCursorPos(window, &x, &y); | 
|  | GLFWcursorPositionCallbackAtPhase(window, FlutterPointerPhase::kDown, x, y); | 
|  | glfwSetCursorPosCallback(window, GLFWcursorPositionCallback); | 
|  | } | 
|  |  | 
|  | if (key == GLFW_MOUSE_BUTTON_1 && action == GLFW_RELEASE) { | 
|  | double x, y; | 
|  | glfwGetCursorPos(window, &x, &y); | 
|  | GLFWcursorPositionCallbackAtPhase(window, FlutterPointerPhase::kUp, x, y); | 
|  | glfwSetCursorPosCallback(window, nullptr); | 
|  | } | 
|  | } | 
|  |  | 
|  | void GLFWKeyCallback(GLFWwindow* window, | 
|  | int key, | 
|  | int scancode, | 
|  | int action, | 
|  | int mods) { | 
|  | if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS) { | 
|  | glfwSetWindowShouldClose(window, GLFW_TRUE); | 
|  | } | 
|  | } | 
|  |  | 
|  | void GLFWframebufferSizeCallback(GLFWwindow* window, int width, int height) { | 
|  | g_state.resize_pending = true; | 
|  |  | 
|  | FlutterWindowMetricsEvent event = {}; | 
|  | event.struct_size = sizeof(event); | 
|  | event.width = width; | 
|  | event.height = height; | 
|  | event.pixel_ratio = g_pixelRatio; | 
|  | FlutterEngineSendWindowMetricsEvent(g_state.engine, &event); | 
|  | } | 
|  |  | 
|  | void PrintUsage() { | 
|  | std::cerr | 
|  | << "usage: embedder_example_vulkan <path to project> <path to icudtl.dat>" | 
|  | << std::endl; | 
|  | } | 
|  |  | 
|  | bool InitializeSwapchain() { | 
|  | if (g_state.resize_pending) { | 
|  | g_state.resize_pending = false; | 
|  | d.vkDestroySwapchainKHR(g_state.device, g_state.swapchain, nullptr); | 
|  |  | 
|  | d.vkQueueWaitIdle(g_state.queue); | 
|  | d.vkResetCommandPool(g_state.device, g_state.swapchain_command_pool, | 
|  | VK_COMMAND_POOL_RESET_RELEASE_RESOURCES_BIT); | 
|  | } | 
|  |  | 
|  | /// -------------------------------------------------------------------------- | 
|  | /// Choose an image format that can be presented to the surface, preferring | 
|  | /// the common BGRA+sRGB if available. | 
|  | /// -------------------------------------------------------------------------- | 
|  |  | 
|  | uint32_t format_count; | 
|  | d.vkGetPhysicalDeviceSurfaceFormatsKHR( | 
|  | g_state.physical_device, g_state.surface, &format_count, nullptr); | 
|  | std::vector<VkSurfaceFormatKHR> formats(format_count); | 
|  | d.vkGetPhysicalDeviceSurfaceFormatsKHR( | 
|  | g_state.physical_device, g_state.surface, &format_count, formats.data()); | 
|  | assert(!formats.empty());  // Shouldn't be possible. | 
|  |  | 
|  | g_state.surface_format = formats[0]; | 
|  | for (const auto& format : formats) { | 
|  | if (format.format == VK_FORMAT_B8G8R8A8_UNORM && | 
|  | format.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) { | 
|  | g_state.surface_format = format; | 
|  | } | 
|  | } | 
|  |  | 
|  | /// -------------------------------------------------------------------------- | 
|  | /// Choose the presentable image size that's as close as possible to the | 
|  | /// window size. | 
|  | /// -------------------------------------------------------------------------- | 
|  |  | 
|  | VkExtent2D extent; | 
|  |  | 
|  | VkSurfaceCapabilitiesKHR surface_capabilities; | 
|  | d.vkGetPhysicalDeviceSurfaceCapabilitiesKHR( | 
|  | g_state.physical_device, g_state.surface, &surface_capabilities); | 
|  |  | 
|  | if (surface_capabilities.currentExtent.width != UINT32_MAX) { | 
|  | // If the surface reports a specific extent, we must use it. | 
|  | extent = surface_capabilities.currentExtent; | 
|  | } else { | 
|  | // `glfwGetWindowSize` returns the window size in screen coordinates, so we | 
|  | // instead use `glfwGetFramebufferSize` to get the size in pixels in order | 
|  | // to properly support high DPI displays. | 
|  | int width, height; | 
|  | glfwGetFramebufferSize(g_state.window, &width, &height); | 
|  |  | 
|  | VkExtent2D actual_extent = { | 
|  | .width = static_cast<uint32_t>(width), | 
|  | .height = static_cast<uint32_t>(height), | 
|  | }; | 
|  | actual_extent.width = | 
|  | std::max(surface_capabilities.minImageExtent.width, | 
|  | std::min(surface_capabilities.maxImageExtent.width, | 
|  | actual_extent.width)); | 
|  | actual_extent.height = | 
|  | std::max(surface_capabilities.minImageExtent.height, | 
|  | std::min(surface_capabilities.maxImageExtent.height, | 
|  | actual_extent.height)); | 
|  | } | 
|  |  | 
|  | /// -------------------------------------------------------------------------- | 
|  | /// Choose the present mode. | 
|  | /// -------------------------------------------------------------------------- | 
|  |  | 
|  | uint32_t mode_count; | 
|  | d.vkGetPhysicalDeviceSurfacePresentModesKHR( | 
|  | g_state.physical_device, g_state.surface, &mode_count, nullptr); | 
|  | std::vector<VkPresentModeKHR> modes(mode_count); | 
|  | d.vkGetPhysicalDeviceSurfacePresentModesKHR( | 
|  | g_state.physical_device, g_state.surface, &mode_count, modes.data()); | 
|  | assert(!formats.empty());  // Shouldn't be possible. | 
|  |  | 
|  | // If the preferred mode isn't available, just choose the first one. | 
|  | VkPresentModeKHR present_mode = modes[0]; | 
|  | for (const auto& mode : modes) { | 
|  | if (mode == kPreferredPresentMode) { | 
|  | present_mode = mode; | 
|  | break; | 
|  | } | 
|  | } | 
|  |  | 
|  | /// -------------------------------------------------------------------------- | 
|  | /// Create the swapchain. | 
|  | /// -------------------------------------------------------------------------- | 
|  |  | 
|  | VkSwapchainCreateInfoKHR info = { | 
|  | .sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR, | 
|  | .surface = g_state.surface, | 
|  | .minImageCount = surface_capabilities.minImageCount + 1, | 
|  | .imageFormat = g_state.surface_format.format, | 
|  | .imageColorSpace = g_state.surface_format.colorSpace, | 
|  | .imageExtent = extent, | 
|  | .imageArrayLayers = 1, | 
|  | .imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT, | 
|  | .imageSharingMode = VK_SHARING_MODE_EXCLUSIVE, | 
|  | .queueFamilyIndexCount = 0, | 
|  | .pQueueFamilyIndices = nullptr, | 
|  | .preTransform = surface_capabilities.currentTransform, | 
|  | .compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR, | 
|  | .presentMode = present_mode, | 
|  | .clipped = true, | 
|  | }; | 
|  | if (d.vkCreateSwapchainKHR(g_state.device, &info, nullptr, | 
|  | &g_state.swapchain) != VK_SUCCESS) { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | /// -------------------------------------------------------------------------- | 
|  | /// Fetch swapchain images. | 
|  | /// -------------------------------------------------------------------------- | 
|  |  | 
|  | uint32_t image_count; | 
|  | d.vkGetSwapchainImagesKHR(g_state.device, g_state.swapchain, &image_count, | 
|  | nullptr); | 
|  | g_state.swapchain_images.resize(image_count); | 
|  | d.vkGetSwapchainImagesKHR(g_state.device, g_state.swapchain, &image_count, | 
|  | g_state.swapchain_images.data()); | 
|  |  | 
|  | /// -------------------------------------------------------------------------- | 
|  | /// Record a command buffer for each of the images to be executed prior to | 
|  | /// presenting. | 
|  | /// -------------------------------------------------------------------------- | 
|  |  | 
|  | g_state.present_transition_buffers.resize(g_state.swapchain_images.size()); | 
|  |  | 
|  | VkCommandBufferAllocateInfo buffers_info = { | 
|  | .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO, | 
|  | .commandPool = g_state.swapchain_command_pool, | 
|  | .level = VK_COMMAND_BUFFER_LEVEL_PRIMARY, | 
|  | .commandBufferCount = | 
|  | static_cast<uint32_t>(g_state.present_transition_buffers.size()), | 
|  | }; | 
|  | d.vkAllocateCommandBuffers(g_state.device, &buffers_info, | 
|  | g_state.present_transition_buffers.data()); | 
|  |  | 
|  | for (size_t i = 0; i < g_state.swapchain_images.size(); i++) { | 
|  | auto image = g_state.swapchain_images[i]; | 
|  | auto buffer = g_state.present_transition_buffers[i]; | 
|  |  | 
|  | VkCommandBufferBeginInfo begin_info = { | 
|  | .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO}; | 
|  | d.vkBeginCommandBuffer(buffer, &begin_info); | 
|  |  | 
|  | // Flutter Engine hands back the image after writing to it | 
|  | VkImageMemoryBarrier barrier = { | 
|  | .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, | 
|  | .srcAccessMask = 0, | 
|  | .dstAccessMask = VK_ACCESS_MEMORY_READ_BIT, | 
|  | .oldLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, | 
|  | .newLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, | 
|  | .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, | 
|  | .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, | 
|  | .image = image, | 
|  | .subresourceRange = { | 
|  | .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, | 
|  | .baseMipLevel = 0, | 
|  | .levelCount = 1, | 
|  | .baseArrayLayer = 0, | 
|  | .layerCount = 1, | 
|  | }}; | 
|  | d.vkCmdPipelineBarrier( | 
|  | buffer,                                         // commandBuffer | 
|  | VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,  // srcStageMask | 
|  | VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT,           // dstStageMask | 
|  | 0,                                              // dependencyFlags | 
|  | 0,                                              // memoryBarrierCount | 
|  | nullptr,                                        // pMemoryBarriers | 
|  | 0,        // bufferMemoryBarrierCount | 
|  | nullptr,  // pBufferMemoryBarriers | 
|  | 1,        // imageMemoryBarrierCount | 
|  | &barrier  // pImageMemoryBarriers | 
|  | ); | 
|  |  | 
|  | d.vkEndCommandBuffer(buffer); | 
|  | } | 
|  |  | 
|  | return true;  // \o/ | 
|  | } | 
|  |  | 
|  | FlutterVulkanImage FlutterGetNextImageCallback( | 
|  | void* user_data, | 
|  | const FlutterFrameInfo* frame_info) { | 
|  | // If the GLFW framebuffer has been resized, discard the swapchain and create | 
|  | // a new one. | 
|  | if (g_state.resize_pending) { | 
|  | InitializeSwapchain(); | 
|  | } | 
|  |  | 
|  | d.vkAcquireNextImageKHR(g_state.device, g_state.swapchain, UINT64_MAX, | 
|  | nullptr, g_state.image_ready_fence, | 
|  | &g_state.last_image_index); | 
|  |  | 
|  | // Flutter Engine expects the image to be available for transitioning and | 
|  | // attaching immediately, and so we need to force a host sync here before | 
|  | // returning. | 
|  | d.vkWaitForFences(g_state.device, 1, &g_state.image_ready_fence, true, | 
|  | UINT64_MAX); | 
|  | d.vkResetFences(g_state.device, 1, &g_state.image_ready_fence); | 
|  |  | 
|  | return { | 
|  | .struct_size = sizeof(FlutterVulkanImage), | 
|  | .image = reinterpret_cast<uint64_t>( | 
|  | g_state.swapchain_images[g_state.last_image_index]), | 
|  | .format = g_state.surface_format.format, | 
|  | }; | 
|  | } | 
|  |  | 
|  | bool FlutterPresentCallback(void* user_data, const FlutterVulkanImage* image) { | 
|  | VkPipelineStageFlags stage_flags = | 
|  | VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; | 
|  | VkSubmitInfo submit_info = { | 
|  | .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO, | 
|  | .waitSemaphoreCount = 0, | 
|  | .pWaitSemaphores = nullptr, | 
|  | .pWaitDstStageMask = &stage_flags, | 
|  | .commandBufferCount = 1, | 
|  | .pCommandBuffers = | 
|  | &g_state.present_transition_buffers[g_state.last_image_index], | 
|  | .signalSemaphoreCount = 1, | 
|  | .pSignalSemaphores = &g_state.present_transition_semaphore, | 
|  | }; | 
|  | d.vkQueueSubmit(g_state.queue, 1, &submit_info, nullptr); | 
|  |  | 
|  | VkPresentInfoKHR present_info = { | 
|  | .sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR, | 
|  | .waitSemaphoreCount = 1, | 
|  | .pWaitSemaphores = &g_state.present_transition_semaphore, | 
|  | .swapchainCount = 1, | 
|  | .pSwapchains = &g_state.swapchain, | 
|  | .pImageIndices = &g_state.last_image_index, | 
|  | }; | 
|  | VkResult result = d.vkQueuePresentKHR(g_state.queue, &present_info); | 
|  |  | 
|  | // If the swapchain is no longer compatible with the surface, discard the | 
|  | // swapchain and create a new one. | 
|  | if (result == VK_SUBOPTIMAL_KHR || result == VK_ERROR_OUT_OF_DATE_KHR) { | 
|  | InitializeSwapchain(); | 
|  | } | 
|  | d.vkQueueWaitIdle(g_state.queue); | 
|  |  | 
|  | return result == VK_SUCCESS; | 
|  | } | 
|  |  | 
|  | void* FlutterGetInstanceProcAddressCallback( | 
|  | void* user_data, | 
|  | FlutterVulkanInstanceHandle instance, | 
|  | const char* procname) { | 
|  | auto* proc = glfwGetInstanceProcAddress( | 
|  | reinterpret_cast<VkInstance>(instance), procname); | 
|  | return reinterpret_cast<void*>(proc); | 
|  | } | 
|  |  | 
|  | int main(int argc, char** argv) { | 
|  | if (argc != 3) { | 
|  | PrintUsage(); | 
|  | return 1; | 
|  | } | 
|  |  | 
|  | std::string project_path = argv[1]; | 
|  | std::string icudtl_path = argv[2]; | 
|  |  | 
|  | /// -------------------------------------------------------------------------- | 
|  | /// Create a GLFW window. | 
|  | /// -------------------------------------------------------------------------- | 
|  |  | 
|  | { | 
|  | if (!glfwInit()) { | 
|  | std::cerr << "Failed to initialize GLFW." << std::endl; | 
|  | return EXIT_FAILURE; | 
|  | } | 
|  |  | 
|  | glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); | 
|  | g_state.window = glfwCreateWindow(kInitialWindowWidth, kInitialWindowHeight, | 
|  | "Flutter", nullptr, nullptr); | 
|  | if (!g_state.window) { | 
|  | std::cerr << "Failed to create GLFW window." << std::endl; | 
|  | return EXIT_FAILURE; | 
|  | } | 
|  |  | 
|  | int framebuffer_width, framebuffer_height; | 
|  | glfwGetFramebufferSize(g_state.window, &framebuffer_width, | 
|  | &framebuffer_height); | 
|  | g_pixelRatio = framebuffer_width / kInitialWindowWidth; | 
|  |  | 
|  | glfwSetErrorCallback(GLFW_ErrorCallback); | 
|  | } | 
|  |  | 
|  | /// -------------------------------------------------------------------------- | 
|  | /// Dynamically load the Vulkan loader with GLFW and use it to populate GLAD's | 
|  | /// proc table. | 
|  | /// -------------------------------------------------------------------------- | 
|  |  | 
|  | if (!glfwVulkanSupported()) { | 
|  | std::cerr << "GLFW was unable to resolve either a Vulkan loader or a " | 
|  | "compatible physical device!" | 
|  | << std::endl; | 
|  | #if defined(__APPLE__) | 
|  | std::cerr | 
|  | << "NOTE: Apple platforms don't ship with a Vulkan loader or any " | 
|  | "Vulkan drivers. Follow this guide to set up a Vulkan loader on " | 
|  | "macOS and use the MoltenVK ICD: " | 
|  | "https://vulkan.lunarg.com/doc/sdk/latest/mac/getting_started.html" | 
|  | << std::endl; | 
|  | #endif | 
|  | return EXIT_FAILURE; | 
|  | } | 
|  |  | 
|  | VULKAN_HPP_DEFAULT_DISPATCHER.init(glfwGetInstanceProcAddress); | 
|  |  | 
|  | /// -------------------------------------------------------------------------- | 
|  | /// Create a Vulkan instance. | 
|  | /// -------------------------------------------------------------------------- | 
|  |  | 
|  | { | 
|  | uint32_t extension_count; | 
|  | const char** glfw_extensions = | 
|  | glfwGetRequiredInstanceExtensions(&extension_count); | 
|  | g_state.enabled_instance_extensions.resize(extension_count); | 
|  | memcpy(g_state.enabled_instance_extensions.data(), glfw_extensions, | 
|  | extension_count * sizeof(char*)); | 
|  |  | 
|  | if (g_enable_validation_layers) { | 
|  | g_state.enabled_instance_extensions.push_back( | 
|  | VK_EXT_DEBUG_REPORT_EXTENSION_NAME); | 
|  | } | 
|  |  | 
|  | std::cout << "Enabling " << g_state.enabled_instance_extensions.size() | 
|  | << " instance extensions:" << std::endl; | 
|  | for (const auto& extension : g_state.enabled_instance_extensions) { | 
|  | std::cout << "  - " << extension << std::endl; | 
|  | } | 
|  |  | 
|  | VkApplicationInfo app_info = { | 
|  | .sType = VK_STRUCTURE_TYPE_APPLICATION_INFO, | 
|  | .pNext = nullptr, | 
|  | .pApplicationName = "Flutter", | 
|  | .applicationVersion = VK_MAKE_VERSION(1, 0, 0), | 
|  | .pEngineName = "No Engine", | 
|  | .engineVersion = VK_MAKE_VERSION(1, 0, 0), | 
|  | .apiVersion = VK_MAKE_VERSION(1, 1, 0), | 
|  | }; | 
|  | VkInstanceCreateInfo info = {}; | 
|  | info.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; | 
|  | info.flags = 0; | 
|  | info.pApplicationInfo = &app_info; | 
|  | info.enabledExtensionCount = g_state.enabled_instance_extensions.size(); | 
|  | info.ppEnabledExtensionNames = g_state.enabled_instance_extensions.data(); | 
|  | if (g_enable_validation_layers) { | 
|  | auto available_layers = vk::enumerateInstanceLayerProperties(); | 
|  |  | 
|  | const char* layer = "VK_LAYER_KHRONOS_validation"; | 
|  | for (const auto& l : available_layers.value) { | 
|  | if (strcmp(l.layerName, layer) == 0) { | 
|  | info.enabledLayerCount = 1; | 
|  | info.ppEnabledLayerNames = &layer; | 
|  | break; | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | if (d.vkCreateInstance(&info, nullptr, &g_state.instance) != VK_SUCCESS) { | 
|  | std::cerr << "Failed to create Vulkan instance." << std::endl; | 
|  | return EXIT_FAILURE; | 
|  | } | 
|  | } | 
|  |  | 
|  | // Load instance procs. | 
|  | VULKAN_HPP_DEFAULT_DISPATCHER.init(vk::Instance(g_state.instance)); | 
|  |  | 
|  | /// -------------------------------------------------------------------------- | 
|  | /// Create the window surface. | 
|  | /// -------------------------------------------------------------------------- | 
|  |  | 
|  | if (glfwCreateWindowSurface(g_state.instance, g_state.window, NULL, | 
|  | &g_state.surface) != VK_SUCCESS) { | 
|  | std::cerr << "Failed to create window surface." << std::endl; | 
|  | return EXIT_FAILURE; | 
|  | } | 
|  |  | 
|  | /// -------------------------------------------------------------------------- | 
|  | /// Select a compatible physical device. | 
|  | /// -------------------------------------------------------------------------- | 
|  |  | 
|  | { | 
|  | uint32_t count; | 
|  | d.vkEnumeratePhysicalDevices(g_state.instance, &count, nullptr); | 
|  | std::vector<VkPhysicalDevice> physical_devices(count); | 
|  | d.vkEnumeratePhysicalDevices(g_state.instance, &count, | 
|  | physical_devices.data()); | 
|  |  | 
|  | std::cout << "Enumerating " << count << " physical device(s)." << std::endl; | 
|  |  | 
|  | uint32_t selected_score = 0; | 
|  | for (const auto& pdevice : physical_devices) { | 
|  | VkPhysicalDeviceProperties properties; | 
|  | VkPhysicalDeviceFeatures features; | 
|  | d.vkGetPhysicalDeviceProperties(pdevice, &properties); | 
|  | d.vkGetPhysicalDeviceFeatures(pdevice, &features); | 
|  |  | 
|  | std::cout << "Checking device: " << properties.deviceName << std::endl; | 
|  |  | 
|  | uint32_t score = 0; | 
|  | std::vector<const char*> supported_extensions; | 
|  |  | 
|  | uint32_t qfp_count; | 
|  | d.vkGetPhysicalDeviceQueueFamilyProperties(pdevice, &qfp_count, nullptr); | 
|  | std::vector<VkQueueFamilyProperties> qfp(qfp_count); | 
|  | d.vkGetPhysicalDeviceQueueFamilyProperties(pdevice, &qfp_count, | 
|  | qfp.data()); | 
|  | std::optional<uint32_t> graphics_queue_family; | 
|  | for (uint32_t i = 0; i < qfp.size(); i++) { | 
|  | // Only pick graphics queues that can also present to the surface. | 
|  | // Graphics queues that can't present are rare if not nonexistent, but | 
|  | // the spec allows for this, so check it anyways. | 
|  | VkBool32 surface_present_supported; | 
|  | d.vkGetPhysicalDeviceSurfaceSupportKHR(pdevice, i, g_state.surface, | 
|  | &surface_present_supported); | 
|  |  | 
|  | if (!graphics_queue_family.has_value() && | 
|  | qfp[i].queueFlags & VK_QUEUE_GRAPHICS_BIT && | 
|  | surface_present_supported) { | 
|  | graphics_queue_family = i; | 
|  | } | 
|  | } | 
|  |  | 
|  | // Skip physical devices that don't have a graphics queue. | 
|  | if (!graphics_queue_family.has_value()) { | 
|  | std::cout << "  - Skipping due to no suitable graphics queues." | 
|  | << std::endl; | 
|  | continue; | 
|  | } | 
|  |  | 
|  | // Prefer discrete GPUs. | 
|  | if (properties.deviceType == VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU) { | 
|  | score += 1 << 30; | 
|  | } | 
|  |  | 
|  | uint32_t extension_count; | 
|  | d.vkEnumerateDeviceExtensionProperties(pdevice, nullptr, &extension_count, | 
|  | nullptr); | 
|  | std::vector<VkExtensionProperties> available_extensions(extension_count); | 
|  | d.vkEnumerateDeviceExtensionProperties(pdevice, nullptr, &extension_count, | 
|  | available_extensions.data()); | 
|  |  | 
|  | bool supports_swapchain = false; | 
|  | for (const auto& available_extension : available_extensions) { | 
|  | if (strcmp(VK_KHR_SWAPCHAIN_EXTENSION_NAME, | 
|  | available_extension.extensionName) == 0) { | 
|  | supports_swapchain = true; | 
|  | supported_extensions.push_back(VK_KHR_SWAPCHAIN_EXTENSION_NAME); | 
|  | } | 
|  | // The spec requires VK_KHR_portability_subset be enabled whenever it's | 
|  | // available on a device. It's present on compatibility ICDs like | 
|  | // MoltenVK. | 
|  | else if (strcmp("VK_KHR_portability_subset", | 
|  | available_extension.extensionName) == 0) { | 
|  | supported_extensions.push_back("VK_KHR_portability_subset"); | 
|  | } | 
|  |  | 
|  | // Prefer GPUs that support VK_KHR_get_memory_requirements2. | 
|  | else if (strcmp(VK_KHR_GET_MEMORY_REQUIREMENTS_2_EXTENSION_NAME, | 
|  | available_extension.extensionName) == 0) { | 
|  | score += 1 << 29; | 
|  | supported_extensions.push_back( | 
|  | VK_KHR_GET_MEMORY_REQUIREMENTS_2_EXTENSION_NAME); | 
|  | } | 
|  | } | 
|  |  | 
|  | // Skip physical devices that don't have swapchain support. | 
|  | if (!supports_swapchain) { | 
|  | std::cout << "  - Skipping due to lack of swapchain support." | 
|  | << std::endl; | 
|  | continue; | 
|  | } | 
|  |  | 
|  | // Prefer GPUs with larger max texture sizes. | 
|  | score += properties.limits.maxImageDimension2D; | 
|  |  | 
|  | if (selected_score < score) { | 
|  | std::cout << "  - This is the best device so far. Score: 0x" << std::hex | 
|  | << score << std::dec << std::endl; | 
|  |  | 
|  | selected_score = score; | 
|  | g_state.physical_device = pdevice; | 
|  | g_state.enabled_device_extensions = supported_extensions; | 
|  | g_state.queue_family_index = graphics_queue_family.value_or( | 
|  | std::numeric_limits<uint32_t>::max()); | 
|  | } | 
|  | } | 
|  |  | 
|  | if (g_state.physical_device == nullptr) { | 
|  | std::cerr << "Failed to find a compatible Vulkan physical device." | 
|  | << std::endl; | 
|  | return EXIT_FAILURE; | 
|  | } | 
|  | } | 
|  |  | 
|  | /// -------------------------------------------------------------------------- | 
|  | /// Create a logical device and a graphics queue handle. | 
|  | /// -------------------------------------------------------------------------- | 
|  |  | 
|  | std::cout << "Enabling " << g_state.enabled_device_extensions.size() | 
|  | << " device extensions:" << std::endl; | 
|  | for (const char* extension : g_state.enabled_device_extensions) { | 
|  | std::cout << "  - " << extension << std::endl; | 
|  | } | 
|  |  | 
|  | { | 
|  | VkPhysicalDeviceFeatures device_features = {}; | 
|  |  | 
|  | VkDeviceQueueCreateInfo graphics_queue = {}; | 
|  | graphics_queue.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; | 
|  | graphics_queue.queueFamilyIndex = g_state.queue_family_index; | 
|  | graphics_queue.queueCount = 1; | 
|  | float priority = 1.0f; | 
|  | graphics_queue.pQueuePriorities = &priority; | 
|  |  | 
|  | VkDeviceCreateInfo device_info = {}; | 
|  | device_info.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; | 
|  | device_info.enabledExtensionCount = | 
|  | g_state.enabled_device_extensions.size(); | 
|  | device_info.ppEnabledExtensionNames = | 
|  | g_state.enabled_device_extensions.data(); | 
|  | device_info.pEnabledFeatures = &device_features; | 
|  | device_info.queueCreateInfoCount = 1; | 
|  | device_info.pQueueCreateInfos = &graphics_queue; | 
|  |  | 
|  | if (d.vkCreateDevice(g_state.physical_device, &device_info, nullptr, | 
|  | &g_state.device) != VK_SUCCESS) { | 
|  | std::cerr << "Failed to create Vulkan logical device." << std::endl; | 
|  | return EXIT_FAILURE; | 
|  | } | 
|  | } | 
|  |  | 
|  | d.vkGetDeviceQueue(g_state.device, g_state.queue_family_index, 0, | 
|  | &g_state.queue); | 
|  |  | 
|  | /// -------------------------------------------------------------------------- | 
|  | /// Create sync primitives and command pool to use in the render loop | 
|  | /// callbacks. | 
|  | /// -------------------------------------------------------------------------- | 
|  |  | 
|  | { | 
|  | VkFenceCreateInfo f_info = {.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO}; | 
|  | d.vkCreateFence(g_state.device, &f_info, nullptr, | 
|  | &g_state.image_ready_fence); | 
|  |  | 
|  | VkSemaphoreCreateInfo s_info = { | 
|  | .sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO}; | 
|  | d.vkCreateSemaphore(g_state.device, &s_info, nullptr, | 
|  | &g_state.present_transition_semaphore); | 
|  |  | 
|  | VkCommandPoolCreateInfo pool_info = { | 
|  | .sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO, | 
|  | .queueFamilyIndex = g_state.queue_family_index, | 
|  | }; | 
|  | d.vkCreateCommandPool(g_state.device, &pool_info, nullptr, | 
|  | &g_state.swapchain_command_pool); | 
|  | } | 
|  |  | 
|  | /// -------------------------------------------------------------------------- | 
|  | /// Create swapchain. | 
|  | /// -------------------------------------------------------------------------- | 
|  |  | 
|  | if (!InitializeSwapchain()) { | 
|  | std::cerr << "Failed to create swapchain." << std::endl; | 
|  | return EXIT_FAILURE; | 
|  | } | 
|  |  | 
|  | /// -------------------------------------------------------------------------- | 
|  | /// Start Flutter Engine. | 
|  | /// -------------------------------------------------------------------------- | 
|  |  | 
|  | { | 
|  | FlutterRendererConfig config = {}; | 
|  | config.type = kVulkan; | 
|  | config.vulkan.struct_size = sizeof(config.vulkan); | 
|  | config.vulkan.version = VK_MAKE_VERSION(1, 1, 0); | 
|  | config.vulkan.instance = g_state.instance; | 
|  | config.vulkan.physical_device = g_state.physical_device; | 
|  | config.vulkan.device = g_state.device; | 
|  | config.vulkan.queue_family_index = g_state.queue_family_index; | 
|  | config.vulkan.queue = g_state.queue; | 
|  | config.vulkan.enabled_instance_extension_count = | 
|  | g_state.enabled_instance_extensions.size(); | 
|  | config.vulkan.enabled_instance_extensions = | 
|  | g_state.enabled_instance_extensions.data(); | 
|  | config.vulkan.enabled_device_extension_count = | 
|  | g_state.enabled_device_extensions.size(); | 
|  | config.vulkan.enabled_device_extensions = | 
|  | g_state.enabled_device_extensions.data(); | 
|  | config.vulkan.get_instance_proc_address_callback = | 
|  | FlutterGetInstanceProcAddressCallback; | 
|  | config.vulkan.get_next_image_callback = FlutterGetNextImageCallback; | 
|  | config.vulkan.present_image_callback = FlutterPresentCallback; | 
|  |  | 
|  | // This directory is generated by `flutter build bundle`. | 
|  | std::string assets_path = project_path + "/build/flutter_assets"; | 
|  | FlutterProjectArgs args = { | 
|  | .struct_size = sizeof(FlutterProjectArgs), | 
|  | .assets_path = assets_path.c_str(), | 
|  | .icu_data_path = | 
|  | icudtl_path.c_str(),  // Find this in your bin/cache directory. | 
|  | }; | 
|  | FlutterEngineResult result = | 
|  | FlutterEngineRun(FLUTTER_ENGINE_VERSION, &config, &args, g_state.window, | 
|  | &g_state.engine); | 
|  | if (result != kSuccess || g_state.engine == nullptr) { | 
|  | std::cerr << "Failed to start Flutter Engine." << std::endl; | 
|  | return EXIT_FAILURE; | 
|  | } | 
|  |  | 
|  | // Trigger a FlutterEngineSendWindowMetricsEvent to communicate the initial | 
|  | // size of the window. | 
|  | int width, height; | 
|  | glfwGetFramebufferSize(g_state.window, &width, &height); | 
|  | GLFWframebufferSizeCallback(g_state.window, width, height); | 
|  | g_state.resize_pending = false; | 
|  | } | 
|  |  | 
|  | /// -------------------------------------------------------------------------- | 
|  | /// GLFW render loop. | 
|  | /// -------------------------------------------------------------------------- | 
|  |  | 
|  | glfwSetKeyCallback(g_state.window, GLFWKeyCallback); | 
|  | glfwSetFramebufferSizeCallback(g_state.window, GLFWframebufferSizeCallback); | 
|  | glfwSetMouseButtonCallback(g_state.window, GLFWmouseButtonCallback); | 
|  |  | 
|  | while (!glfwWindowShouldClose(g_state.window)) { | 
|  | glfwWaitEvents(); | 
|  | } | 
|  |  | 
|  | /// -------------------------------------------------------------------------- | 
|  | /// Cleanup. | 
|  | /// -------------------------------------------------------------------------- | 
|  |  | 
|  | if (FlutterEngineShutdown(g_state.engine) != kSuccess) { | 
|  | std::cerr << "Flutter Engine shutdown failed." << std::endl; | 
|  | } | 
|  |  | 
|  | d.vkDestroyCommandPool(g_state.device, g_state.swapchain_command_pool, | 
|  | nullptr); | 
|  | d.vkDestroySemaphore(g_state.device, g_state.present_transition_semaphore, | 
|  | nullptr); | 
|  | d.vkDestroyFence(g_state.device, g_state.image_ready_fence, nullptr); | 
|  |  | 
|  | d.vkDestroyDevice(g_state.device, nullptr); | 
|  | d.vkDestroySurfaceKHR(g_state.instance, g_state.surface, nullptr); | 
|  | d.vkDestroyInstance(g_state.instance, nullptr); | 
|  |  | 
|  | glfwDestroyWindow(g_state.window); | 
|  | glfwTerminate(); | 
|  |  | 
|  | return 0; | 
|  | } |