blob: e97a874afdd95bd9d3511db011552665f879d15e [file] [log] [blame]
// 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 <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;
}