/* * Copyright 2021-2022 Toyota Connected North America * * 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 "wayland_vulkan.h" #include #include #include #include #include "constants.h" #include "engine.h" WaylandVulkanBackend::WaylandVulkanBackend(struct wl_display* display, uint32_t width, uint32_t height, bool enable_validation_layers) : wl_display_(display), width_(width), height_(height), enable_validation_layers_(enable_validation_layers), resize_pending_(false), Backend(this, Resize, CreateSurface) { if (!bluevk::initialize()) { spdlog::critical("BlueVK is unable to load entry points.\n"); exit(EXIT_FAILURE); } createInstance(); setupDebugMessenger(); } FlutterRendererConfig WaylandVulkanBackend::GetRenderConfig() { return { .type = kVulkan, .vulkan{ .struct_size = sizeof(FlutterRendererConfig), .version = VK_MAKE_VERSION(1, 1, 0), .instance = state_.instance, .physical_device = state_.physical_device, .device = state_.device, .queue_family_index = state_.queue_family_index, .queue = state_.queue, .enabled_instance_extension_count = state_.enabled_instance_extensions.size(), .enabled_instance_extensions = state_.enabled_instance_extensions.data(), .enabled_device_extension_count = state_.enabled_device_extensions.size(), .enabled_device_extensions = state_.enabled_device_extensions.data(), .get_instance_proc_address_callback = GetInstanceProcAddressCallback, .get_next_image_callback = GetNextImageCallback, .present_image_callback = PresentCallback, }}; } FlutterCompositor WaylandVulkanBackend::GetCompositorConfig() { return {.struct_size = sizeof(FlutterCompositor), .user_data = this, .create_backing_store_callback = CreateBackingStore, .collect_backing_store_callback = CollectBackingStore, .present_layers_callback = PresentLayers, .avoid_backing_store_cache = true}; } WaylandVulkanBackend::~WaylandVulkanBackend() { if (state_.device != nullptr) { if (state_.swapchain_command_pool != nullptr) { bluevk::vkDestroyCommandPool(state_.device, state_.swapchain_command_pool, nullptr); } if (state_.present_transition_semaphore != nullptr) { bluevk::vkDestroySemaphore(state_.device, state_.present_transition_semaphore, nullptr); } if (state_.image_ready_fence != nullptr) { bluevk::vkDestroyFence(state_.device, state_.image_ready_fence, nullptr); } bluevk::vkDestroyDevice(state_.device, nullptr); } if (state_.surface != nullptr) { bluevk::vkDestroySurfaceKHR(state_.instance, state_.surface, nullptr); } if (enable_validation_layers_) { if (mDebugCallback) { bluevk::vkDestroyDebugReportCallbackEXT(state_.instance, mDebugCallback, VKALLOC); } if (mDebugMessenger) { bluevk::vkDestroyDebugUtilsMessengerEXT(state_.instance, mDebugMessenger, VKALLOC); } } if (state_.instance != nullptr) { bluevk::vkDestroyInstance(state_.instance, nullptr); } } void WaylandVulkanBackend::createInstance() { auto instance_extensions = enumerateInstanceExtensionProperties(); for (const auto& l : instance_extensions) { if (enable_validation_layers_) { if (strcmp(l.extensionName, VK_EXT_VALIDATION_FEATURES_EXTENSION_NAME) == 0) { state_.validationFeaturesSupported = true; state_.enabled_instance_extensions.push_back(l.extensionName); } if (strcmp(l.extensionName, VK_EXT_DEBUG_UTILS_EXTENSION_NAME) == 0) { state_.debugUtilsSupported = true; state_.enabled_instance_extensions.push_back(l.extensionName); } if (strcmp(l.extensionName, VK_EXT_DEBUG_REPORT_EXTENSION_NAME) == 0) { state_.debugReportExtensionSupported = true; state_.enabled_instance_extensions.push_back(l.extensionName); } } } state_.enabled_instance_extensions.push_back(VK_KHR_SURFACE_EXTENSION_NAME); state_.enabled_instance_extensions.push_back( VK_KHR_WAYLAND_SURFACE_EXTENSION_NAME); std::stringstream ss; ss << "Enabling {} instance extensions: " << state_.enabled_instance_extensions.size(); for (const auto& extension : state_.enabled_instance_extensions) { ss << " - " << extension; } spdlog::info(ss.str()); VkApplicationInfo app_info = { .sType = VK_STRUCTURE_TYPE_APPLICATION_INFO, .pNext = nullptr, .pApplicationName = kApplicationName, .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 = { .sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO, .flags = 0, .pApplicationInfo = &app_info, .enabledExtensionCount = static_cast(state_.enabled_instance_extensions.size()), .ppEnabledExtensionNames = state_.enabled_instance_extensions.data(), }; #if 0 // TODO not currently used VkValidationFeatureEnableEXT enables[] = { VK_VALIDATION_FEATURE_ENABLE_BEST_PRACTICES_EXT, // TODO: Enable synchronization validation. // VK_VALIDATION_FEATURE_ENABLE_SYNCHRONIZATION_VALIDATION_EXT, }; if (state_.validationFeaturesSupported) { VkValidationFeaturesEXT features{}; features.sType = VK_STRUCTURE_TYPE_VALIDATION_FEATURES_EXT; features.enabledValidationFeatureCount = sizeof(enables) / sizeof(enables[0]); features.pEnabledValidationFeatures = enables; info.pNext = &features; } #endif static constexpr char VK_LAYER_KHRONOS_VALIDATION_NAME[] = "VK_LAYER_KHRONOS_validation"; auto available_layers = enumerateInstanceLayerProperties(); for (const auto& l : available_layers) { if (enable_validation_layers_ && strcmp(l.layerName, VK_LAYER_KHRONOS_VALIDATION_NAME) == 0) { state_.enabled_layer_extensions.push_back( VK_LAYER_KHRONOS_VALIDATION_NAME); break; } } info.enabledLayerCount = static_cast(state_.enabled_layer_extensions.size()); info.ppEnabledLayerNames = state_.enabled_layer_extensions.data(); if (bluevk::vkCreateInstance(&info, nullptr, &state_.instance) != VK_SUCCESS) { spdlog::critical("Failed to create Vulkan instance."); exit(EXIT_FAILURE); } bluevk::bindInstance(state_.instance); } void WaylandVulkanBackend::setupDebugMessenger() { if (!enable_validation_layers_) return; if (state_.debugUtilsSupported) { VkDebugUtilsMessengerCreateInfoEXT createInfo{ .sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT, .pNext = nullptr, .flags = 0, .messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT, .messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT, .pfnUserCallback = debugUtilsCallback, .pUserData = nullptr, }; if (bluevk::vkCreateDebugUtilsMessengerEXT(state_.instance, &createInfo, VKALLOC, &mDebugMessenger) != VK_SUCCESS) { spdlog::error("Unable to create Vulkan debug callback"); } } else if (bluevk::vkCreateDebugReportCallbackEXT) { const VkDebugReportCallbackCreateInfoEXT cbinfo = { VK_STRUCTURE_TYPE_DEBUG_REPORT_CALLBACK_CREATE_INFO_EXT, nullptr, VK_DEBUG_REPORT_WARNING_BIT_EXT | VK_DEBUG_REPORT_ERROR_BIT_EXT, debugReportCallback, nullptr}; if (bluevk::vkCreateDebugReportCallbackEXT( state_.instance, &cbinfo, VKALLOC, &mDebugCallback) != VK_SUCCESS) { spdlog::error("Unable to create Vulkan debug callback"); } } } void WaylandVulkanBackend::findPhysicalDevice() { uint32_t count; VkResult result = bluevk::vkEnumeratePhysicalDevices(state_.instance, &count, nullptr); assert(result == VK_SUCCESS && count > 0); std::vector physical_devices(count); result = bluevk::vkEnumeratePhysicalDevices(state_.instance, &count, physical_devices.data()); assert(result == VK_SUCCESS); SPDLOG_DEBUG("Enumerating {} physical device(s).", count); uint32_t selected_score = 0; for (const auto& pdevice : physical_devices) { VkPhysicalDeviceProperties properties; VkPhysicalDeviceFeatures features; bluevk::vkGetPhysicalDeviceProperties(pdevice, &properties); bluevk::vkGetPhysicalDeviceFeatures(pdevice, &features); SPDLOG_DEBUG("Checking device: {}", properties.deviceName); uint32_t score = 0; std::vector supported_extensions; uint32_t qfp_count; bluevk::vkGetPhysicalDeviceQueueFamilyProperties(pdevice, &qfp_count, nullptr); std::vector qfp(qfp_count); bluevk::vkGetPhysicalDeviceQueueFamilyProperties(pdevice, &qfp_count, qfp.data()); std::optional 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 anyhow. VkBool32 surface_present_supported; bluevk::vkGetPhysicalDeviceSurfaceSupportKHR(pdevice, i, 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()) { spdlog::info(" - Skipping due to no suitable graphics queues."); continue; } // Prefer discrete GPUs. if (properties.deviceType == VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU) { score += 1 << 30; } uint32_t extension_count; bluevk::vkEnumerateDeviceExtensionProperties(pdevice, nullptr, &extension_count, nullptr); std::vector available_extensions(extension_count); bluevk::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_EXTENSION_NAME, available_extension.extensionName) == 0) { supported_extensions.push_back( VK_KHR_PORTABILITY_SUBSET_EXTENSION_NAME); } // 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); } else if (strcmp(VK_EXT_DEBUG_MARKER_EXTENSION_NAME, available_extension.extensionName) == 0) { state_.debugMarkersSupported = true; } else if (strcmp(VK_KHR_MAINTENANCE1_EXTENSION_NAME, available_extension.extensionName) == 0) { state_.maintenanceSupported[0] = true; supported_extensions.push_back(VK_KHR_MAINTENANCE1_EXTENSION_NAME); } else if (strcmp(VK_KHR_MAINTENANCE2_EXTENSION_NAME, available_extension.extensionName) == 0) { state_.maintenanceSupported[1] = true; supported_extensions.push_back(VK_KHR_MAINTENANCE2_EXTENSION_NAME); } else if (strcmp(VK_KHR_MAINTENANCE3_EXTENSION_NAME, available_extension.extensionName) == 0) { state_.maintenanceSupported[2] = true; supported_extensions.push_back(VK_KHR_MAINTENANCE3_EXTENSION_NAME); } } // Skip physical devices that don't have swapchain support. if (!supports_swapchain) { SPDLOG_DEBUG(" - Skipping due to lack of swapchain support."); continue; } // Prefer GPUs with larger max texture sizes. score += properties.limits.maxImageDimension2D; if (selected_score < score) { SPDLOG_DEBUG(" - This is the best device so far. Score: 0x{:x}", score); selected_score = score; state_.physical_device = pdevice; state_.enabled_device_extensions = supported_extensions; state_.queue_family_index = graphics_queue_family.value_or(std::numeric_limits::max()); // Bingo, we finally found a physical device that supports everything we // need. bluevk::vkGetPhysicalDeviceFeatures(pdevice, &state_.physical_device_features); bluevk::vkGetPhysicalDeviceMemoryProperties( pdevice, &state_.physical_device_memory_properties); // Print some driver or MoltenVK information if it is available. if (bluevk::vkGetPhysicalDeviceProperties2KHR) { VkPhysicalDeviceDriverProperties driverProperties = { .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DRIVER_PROPERTIES, }; VkPhysicalDeviceProperties2 physicalDeviceProperties2 = { .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2, .pNext = &driverProperties, }; bluevk::vkGetPhysicalDeviceProperties2KHR(state_.physical_device, &physicalDeviceProperties2); spdlog::info("Vulkan device driver: {} {}", driverProperties.driverName, driverProperties.driverInfo); } // Print out some properties of the GPU for diagnostic purposes. // const uint32_t driverVersion = properties.driverVersion; const uint32_t vendorID = properties.vendorID; const uint32_t deviceID = properties.deviceID; const int major = VK_VERSION_MAJOR(properties.apiVersion); const int minor = VK_VERSION_MINOR(properties.apiVersion); spdlog::info("vendor {:x}, device {:x}, driver {:x}, api {}.{}", vendorID, deviceID, driverVersion, major, minor); break; } } if (state_.physical_device == nullptr) { spdlog::critical("Failed to find a compatible Vulkan physical device."); exit(EXIT_FAILURE); } } void WaylandVulkanBackend::createLogicalDevice() { #if !defined(NDEBUG) std::stringstream ss; ss << "Enabling " << state_.enabled_device_extensions.size() << " device extensions:"; for (const char* extension : state_.enabled_device_extensions) { ss << " - " << extension; } SPDLOG_DEBUG(ss.str()); #endif float priority = 1.0f; VkDeviceQueueCreateInfo graphics_queue = { .sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO, .queueFamilyIndex = state_.queue_family_index, .queueCount = 1, .pQueuePriorities = &priority, }; VkPhysicalDeviceFeatures device_features = {}; VkDeviceCreateInfo device_info = { .sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO, .queueCreateInfoCount = 1, .pQueueCreateInfos = &graphics_queue, .enabledExtensionCount = static_cast(state_.enabled_device_extensions.size()), .ppEnabledExtensionNames = state_.enabled_device_extensions.data(), .pEnabledFeatures = &device_features, }; VkResult result = bluevk::vkCreateDevice(state_.physical_device, &device_info, nullptr, &state_.device); if (result != VK_SUCCESS) { state_.device = VK_NULL_HANDLE; spdlog::critical("Failed to create Vulkan logical device: {}", static_cast(result)); exit(EXIT_FAILURE); } bluevk::vkGetDeviceQueue(state_.device, state_.queue_family_index, 0, &state_.queue); } bool WaylandVulkanBackend::InitializeSwapchain() { if (resize_pending_) { resize_pending_ = false; bluevk::vkDestroySwapchainKHR(state_.device, state_.swapchain, nullptr); bluevk::vkQueueWaitIdle(state_.queue); bluevk::vkResetCommandPool(state_.device, 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; bluevk::vkGetPhysicalDeviceSurfaceFormatsKHR( state_.physical_device, state_.surface, &format_count, nullptr); std::vector formats(format_count); bluevk::vkGetPhysicalDeviceSurfaceFormatsKHR( state_.physical_device, state_.surface, &format_count, formats.data()); assert(!formats.empty()); // Shouldn't be possible. 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) { state_.surface_format = format; } } // -------------------------------------------------------------------------- // Choose the presentable image size that's as close as possible to the // window size. // -------------------------------------------------------------------------- VkExtent2D clientSize; VkSurfaceCapabilitiesKHR surface_capabilities; bluevk::vkGetPhysicalDeviceSurfaceCapabilitiesKHR( state_.physical_device, state_.surface, &surface_capabilities); if (surface_capabilities.currentExtent.width != UINT32_MAX) { // If the surface reports a specific extent, we must use it. clientSize = surface_capabilities.currentExtent; } else { VkExtent2D actual_extent = { .width = width_, .height = height_, }; clientSize.width = std::max(surface_capabilities.minImageExtent.width, std::min(surface_capabilities.maxImageExtent.width, actual_extent.width)); clientSize.height = std::max(surface_capabilities.minImageExtent.height, std::min(surface_capabilities.maxImageExtent.height, actual_extent.height)); } // -------------------------------------------------------------------------- // Desired image count // -------------------------------------------------------------------------- const uint32_t maxImageCount = surface_capabilities.maxImageCount; const uint32_t minImageCount = surface_capabilities.minImageCount; uint32_t desiredImageCount = minImageCount + 1; // According to section 30.5 of VK 1.1, maxImageCount of zero means "that // there is no limit on the number of images, though there may be limits // related to the total amount of memory used by presentable images." if (maxImageCount != 0 && desiredImageCount > maxImageCount) { spdlog::error("Swap chain does not support {} images.", desiredImageCount); desiredImageCount = surface_capabilities.minImageCount; } // -------------------------------------------------------------------------- // Choose the present mode. // -------------------------------------------------------------------------- uint32_t mode_count; bluevk::vkGetPhysicalDeviceSurfacePresentModesKHR( state_.physical_device, state_.surface, &mode_count, nullptr); std::vector modes(mode_count); bluevk::vkGetPhysicalDeviceSurfacePresentModesKHR( state_.physical_device, 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. // -------------------------------------------------------------------------- const VkCompositeAlphaFlagBitsKHR compositeAlpha = (surface_capabilities.supportedCompositeAlpha & VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR) ? VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR : VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR; VkSwapchainCreateInfoKHR info = { .sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR, .pNext = nullptr, .surface = state_.surface, .minImageCount = desiredImageCount, .imageFormat = state_.surface_format.format, .imageColorSpace = state_.surface_format.colorSpace, .imageExtent = clientSize, .imageArrayLayers = 1, .imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT | // Allows use as a blit // destination. VK_IMAGE_USAGE_TRANSFER_SRC_BIT, // Allows use as a blit // source (for readPixels) .imageSharingMode = VK_SHARING_MODE_EXCLUSIVE, .queueFamilyIndexCount = 0, .pQueueFamilyIndices = nullptr, .preTransform = surface_capabilities.currentTransform, .compositeAlpha = compositeAlpha, .presentMode = present_mode, .clipped = VK_TRUE, .oldSwapchain = VK_NULL_HANDLE, }; VkResult result = bluevk::vkCreateSwapchainKHR(state_.device, &info, VKALLOC, &state_.swapchain); if (result != VK_SUCCESS) { spdlog::error("vkGetSwapchainImagesKHR(): {}", static_cast(result)); return false; } // -------------------------------------------------------------------------- // Fetch swapchain images // -------------------------------------------------------------------------- uint32_t image_count; bluevk::vkGetSwapchainImagesKHR(state_.device, state_.swapchain, &image_count, nullptr); state_.swapchain_images.reserve(image_count); state_.swapchain_images.resize(image_count); bluevk::vkGetSwapchainImagesKHR(state_.device, state_.swapchain, &image_count, state_.swapchain_images.data()); // -------------------------------------------------------------------------- // Record a command buffer for each of the images to be executed prior to // presenting. // -------------------------------------------------------------------------- state_.present_transition_buffers.resize(state_.swapchain_images.size()); VkCommandBufferAllocateInfo buffers_info = { .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO, .commandPool = state_.swapchain_command_pool, .level = VK_COMMAND_BUFFER_LEVEL_PRIMARY, .commandBufferCount = static_cast(state_.present_transition_buffers.size()), }; bluevk::vkAllocateCommandBuffers(state_.device, &buffers_info, state_.present_transition_buffers.data()); for (size_t i = 0; i < state_.swapchain_images.size(); i++) { auto image = state_.swapchain_images[i]; auto buffer = state_.present_transition_buffers[i]; VkCommandBufferBeginInfo begin_info = { .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO}; bluevk::vkBeginCommandBuffer(buffer, &begin_info); // Filament 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, }}; bluevk::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 ); bluevk::vkEndCommandBuffer(buffer); } return true; } std::vector WaylandVulkanBackend::enumerateInstanceLayerProperties() { std::vector properties; uint32_t propertyCount; VkResult res; do { res = bluevk::vkEnumerateInstanceLayerProperties(&propertyCount, nullptr); if ((res == VK_SUCCESS) && propertyCount) { properties.resize(propertyCount); res = bluevk::vkEnumerateInstanceLayerProperties( &propertyCount, reinterpret_cast(properties.data())); } } while (res == VK_INCOMPLETE); if (res == VK_SUCCESS) { assert(propertyCount <= properties.size()); properties.resize(propertyCount); } return properties; } std::vector WaylandVulkanBackend::enumerateInstanceExtensionProperties() { std::vector properties; uint32_t propertyCount; VkResult res; do { res = bluevk::vkEnumerateInstanceExtensionProperties( nullptr, &propertyCount, nullptr); if ((res == VK_SUCCESS) && propertyCount) { properties.resize(propertyCount); res = bluevk::vkEnumerateInstanceExtensionProperties( nullptr, &propertyCount, reinterpret_cast(properties.data())); } } while (res == VK_INCOMPLETE); if (res == VK_SUCCESS) { assert(propertyCount <= properties.size()); properties.resize(propertyCount); } return properties; } VKAPI_ATTR VkBool32 VKAPI_CALL WaylandVulkanBackend::debugReportCallback( VkDebugReportFlagsEXT flags, VkDebugReportObjectTypeEXT objectType, uint64_t object, size_t location, int32_t messageCode, const char* pLayerPrefix, const char* pMessage, void* pUserData) { (void)objectType; (void)object; (void)location; (void)messageCode; (void)pUserData; if (flags & VK_DEBUG_REPORT_ERROR_BIT_EXT) { spdlog::error("VULKAN ERROR: ({}) {}", pLayerPrefix, pMessage); } else { spdlog::warn("VULKAN WARNING: ({}) {}", pLayerPrefix, pMessage); } return VK_FALSE; } VKAPI_ATTR VkBool32 VKAPI_CALL WaylandVulkanBackend::debugUtilsCallback( VkDebugUtilsMessageSeverityFlagBitsEXT severity, VkDebugUtilsMessageTypeFlagsEXT types, const VkDebugUtilsMessengerCallbackDataEXT* cbdata, void* pUserData) { (void)types; (void)pUserData; if (severity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT) { spdlog::error("VULKAN ERROR: ({}) {}", cbdata->pMessageIdName, cbdata->pMessage); } else { // TODO: emit best practices warnings about aggressive pipeline barriers. if (strstr(cbdata->pMessage, "ALL_GRAPHICS_BIT") || strstr(cbdata->pMessage, "ALL_COMMANDS_BIT")) { return VK_FALSE; } spdlog::warn("VULKAN WARNING: ({}) {}", cbdata->pMessageIdName, cbdata->pMessage); } return VK_TRUE; } FlutterVulkanImage WaylandVulkanBackend::GetNextImageCallback( void* user_data, const FlutterFrameInfo* frame_info) { (void)frame_info; // If the framebuffer has been resized, discard the swapchain and create // a new one. auto e = reinterpret_cast(user_data); auto b = reinterpret_cast(e->GetBackend()); if (b->state_.resize_pending) { b->InitializeSwapchain(); } bluevk::vkAcquireNextImageKHR( b->state_.device, b->state_.swapchain, UINT64_MAX, nullptr, b->state_.image_ready_fence, &b->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. bluevk::vkWaitForFences(b->state_.device, 1, &b->state_.image_ready_fence, true, UINT64_MAX); bluevk::vkResetFences(b->state_.device, 1, &b->state_.image_ready_fence); return { .struct_size = sizeof(FlutterVulkanImage), .image = reinterpret_cast( b->state_.swapchain_images[b->state_.last_image_index]), .format = b->state_.surface_format.format, }; } bool WaylandVulkanBackend::PresentCallback(void* user_data, const FlutterVulkanImage* image) { (void)image; auto e = reinterpret_cast(user_data); auto b = reinterpret_cast(e->GetBackend()); 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 = &b->state_.present_transition_buffers[b->state_.last_image_index], .signalSemaphoreCount = 1, .pSignalSemaphores = &b->state_.present_transition_semaphore, }; bluevk::vkQueueSubmit(b->state_.queue, 1, &submit_info, nullptr); VkPresentInfoKHR present_info = { .sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR, .waitSemaphoreCount = 1, .pWaitSemaphores = &b->state_.present_transition_semaphore, .swapchainCount = 1, .pSwapchains = &b->state_.swapchain, .pImageIndices = &b->state_.last_image_index, }; VkResult result = bluevk::vkQueuePresentKHR(b->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) { b->InitializeSwapchain(); } bluevk::vkQueueWaitIdle(b->state_.queue); return result == VK_SUCCESS; } void* WaylandVulkanBackend::GetInstanceProcAddressCallback( void* user_data, FlutterVulkanInstanceHandle instance, const char* procname) { (void)user_data; auto* proc = bluevk::vkGetInstanceProcAddr( reinterpret_cast(instance), procname); return reinterpret_cast(proc); } void WaylandVulkanBackend::Resize(void* user_data, size_t index, Engine* engine, int32_t width, int32_t height) { (void)index; auto b = reinterpret_cast(user_data); if (b->width_ != width || b->height_ != height) { b->resize_pending_ = true; b->width_ = static_cast(width); b->height_ = static_cast(height); if (engine) { auto result = engine->SetWindowSize(static_cast(height), static_cast(width)); if (result != kSuccess) { spdlog::error("Failed to set Flutter Engine Window Size"); } } } } void WaylandVulkanBackend::CreateSurface(void* user_data, size_t index, wl_surface* surface, int32_t width, int32_t height) { (void)index; (void)width; (void)height; SPDLOG_DEBUG("CreateSurface"); auto b = reinterpret_cast(user_data); assert(b->state_.instance != VK_NULL_HANDLE); assert(b->state_.surface == VK_NULL_HANDLE); assert(b->wl_display_ != nullptr); assert(surface != nullptr); b->state_.surface = VK_NULL_HANDLE; VkWaylandSurfaceCreateInfoKHR createInfo = { .sType = VK_STRUCTURE_TYPE_WAYLAND_SURFACE_CREATE_INFO_KHR, .pNext = nullptr, .flags = 0, .display = b->wl_display_, .surface = surface}; VkResult result = bluevk::vkCreateWaylandSurfaceKHR( b->state_.instance, &createInfo, nullptr, &b->state_.surface); if (result != VK_SUCCESS) { spdlog::critical("vkCreateWaylandSurfaceKHR failed."); assert(false); exit(EXIT_FAILURE); } b->findPhysicalDevice(); b->createLogicalDevice(); // -------------------------------------------------------------------------- // Create sync primitives and command pool to use in the render loop // callbacks. // -------------------------------------------------------------------------- { VkFenceCreateInfo f_info = {.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO}; bluevk::vkCreateFence(b->state_.device, &f_info, nullptr, &b->state_.image_ready_fence); VkSemaphoreCreateInfo s_info = { .sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO}; bluevk::vkCreateSemaphore(b->state_.device, &s_info, nullptr, &b->state_.present_transition_semaphore); VkCommandPoolCreateInfo pool_info = { .sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO, .queueFamilyIndex = b->state_.queue_family_index, }; bluevk::vkCreateCommandPool(b->state_.device, &pool_info, nullptr, &b->state_.swapchain_command_pool); } // -------------------------------------------------------------------------- // Create swapchain // -------------------------------------------------------------------------- if (!b->InitializeSwapchain()) { spdlog::critical("Failed to create swapchain."); exit(EXIT_FAILURE); } } bool WaylandVulkanBackend::CollectBackingStore( const FlutterBackingStore* renderer, void* user_data) { (void)renderer; (void)user_data; SPDLOG_DEBUG("CollectBackingStore"); return false; } bool WaylandVulkanBackend::CreateBackingStore( const FlutterBackingStoreConfig* config, FlutterBackingStore* backing_store_out, void* user_data) { (void)config; (void)backing_store_out; (void)user_data; SPDLOG_DEBUG("CreateBackingStore"); #if 0 auto surface_size = SkISize::Make(config->size.width, config->size.height); TestVulkanImage* test_image = new TestVulkanImage( std::move(test_vulkan_context_->CreateImage(surface_size).value())); GrVkImageInfo image_info = { .fImage = test_image->GetImage(), .fImageTiling = VK_IMAGE_TILING_OPTIMAL, .fImageLayout = VK_IMAGE_LAYOUT_UNDEFINED, .fFormat = VK_FORMAT_R8G8B8A8_UNORM, .fImageUsageFlags = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, .fSampleCount = 1, .fLevelCount = 1, }; GrBackendTexture backend_texture(surface_size.width(), surface_size.height(), image_info); SkSurfaceProps surface_properties(0, kUnknown_SkPixelGeometry); SkSurface::TextureReleaseProc release_vktexture = [](void* user_data) { delete reinterpret_cast(user_data); }; sk_sp surface = SkSurface::MakeFromBackendTexture( context_.get(), // context backend_texture, // back-end texture kTopLeft_GrSurfaceOrigin, // surface origin 1, // sample count kRGBA_8888_SkColorType, // color type SkColorSpace::MakeSRGB(), // color space &surface_properties, // surface properties release_vktexture, // texture release proc test_image // release context ); if (!surface) { spdlog::error("Could not create Skia surface from Vulkan image."); return false; } backing_store_out->type = kFlutterBackingStoreTypeVulkan; auto* image = new FlutterVulkanImage(); image->image = reinterpret_cast(image_info.fImage); image->format = VK_FORMAT_R8G8B8A8_UNORM; backing_store_out->vulkan.image = image; // Collect all allocated resources in the destruction_callback. { UserData* user_data = new UserData(); user_data->image = image; user_data->surface = surface.get(); backing_store_out->user_data = user_data; backing_store_out->vulkan.user_data = user_data; backing_store_out->vulkan.destruction_callback = [](void* user_data) { UserData* d = reinterpret_cast(user_data); d->surface->unref(); delete d->image; delete d; }; // The balancing unref is in the destruction callback. surface->ref(); } return true; #endif return false; } bool WaylandVulkanBackend::PresentLayers(const FlutterLayer** layers, size_t layers_count, void* user_data) { (void)layers; (void)layers_count; (void)user_data; SPDLOG_DEBUG("PresentLayers"); return false; }