| // 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(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; |
| } |