diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 3c82a74..cf4bc04 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -72,7 +72,17 @@ jobs: - uses: actions/upload-artifact@v4 with: name: tizen-${{ matrix.api-version }}-${{ matrix.arch }} - path: src/out/build/libflutter_tizen*.so + path: | + src/out/build/libflutter_tizen_common.so + src/out/build/libflutter_tizen_mobile.so + src/out/build/libflutter_tizen_tv.so + if-no-files-found: error + + - uses: actions/upload-artifact@v4 + with: + name: tizen-${{ matrix.api-version }}-${{ matrix.arch }}_experimental + path: | + src/out/build/libflutter_tizen*_experimental.so if-no-files-found: error - uses: actions/upload-artifact@v4 @@ -85,7 +95,17 @@ jobs: if: ${{ github.event_name == 'push' }} with: name: tizen-${{ matrix.api-version }}-${{ matrix.arch }}_symbols - path: src/out/build/so.unstripped/libflutter_tizen*.so + path: | + src/out/build/so.unstripped/libflutter_tizen_common.so + src/out/build/so.unstripped/libflutter_tizen_mobile.so + src/out/build/so.unstripped/libflutter_tizen_tv.so + if-no-files-found: error + + - uses: actions/upload-artifact@v4 + if: ${{ github.event_name == 'push' }} + with: + name: tizen-${{ matrix.api-version }}-${{ matrix.arch }}_experimental_symbols + path: src/out/build/so.unstripped/libflutter_tizen*_experimental.so if-no-files-found: error - uses: actions/upload-artifact@v4 diff --git a/flutter/shell/platform/tizen/BUILD.gn b/flutter/shell/platform/tizen/BUILD.gn index 2cf4a85..2cc7ec4 100644 --- a/flutter/shell/platform/tizen/BUILD.gn +++ b/flutter/shell/platform/tizen/BUILD.gn @@ -140,7 +140,8 @@ template("embedder") { "GLESv2", ] - if (target_name == "flutter_tizen_common") { + if (target_name == "flutter_tizen_common" || + target_name == "flutter_tizen_common_experimental") { sources += [ "channels/tizen_shell.cc" ] libs += [ @@ -152,6 +153,14 @@ template("embedder") { defines += invoker.defines defines += [ "FLUTTER_ENGINE_NO_PROTOTYPES" ] + if (target_name == "flutter_tizen_mobile_experimental" || + target_name == "flutter_tizen_tv_experimental" || + target_name == "flutter_tizen_common_experimental") { + defines += [ "FLUTTER_TIZEN_EXPERIMENTAL" ] + sources += [ "tizen_renderer_vulkan.cc" ] + libs += [ "vulkan" ] + } + if (api_version != "6.0") { sources += [ "flutter_tizen_nui.cc", @@ -201,18 +210,36 @@ embedder("flutter_tizen_mobile") { defines = [ "MOBILE_PROFILE" ] } +embedder("flutter_tizen_mobile_experimental") { + target_type = "shared_library" + + defines = [ "MOBILE_PROFILE" ] +} + embedder("flutter_tizen_tv") { target_type = "shared_library" defines = [ "TV_PROFILE" ] } +embedder("flutter_tizen_tv_experimental") { + target_type = "shared_library" + + defines = [ "TV_PROFILE" ] +} + embedder("flutter_tizen_common") { target_type = "shared_library" defines = [ "COMMON_PROFILE" ] } +embedder("flutter_tizen_common_experimental") { + target_type = "shared_library" + + defines = [ "COMMON_PROFILE" ] +} + embedder("flutter_tizen_source") { target_type = "source_set" @@ -257,8 +284,11 @@ copy("publish_headers_tizen") { group("flutter_tizen") { deps = [ ":flutter_tizen_common", + ":flutter_tizen_common_experimental", ":flutter_tizen_mobile", + ":flutter_tizen_mobile_experimental", ":flutter_tizen_tv", + ":flutter_tizen_tv_experimental", ":publish_cpp_client_wrapper", ":publish_headers_tizen", ] diff --git a/flutter/shell/platform/tizen/flutter_tizen.cc b/flutter/shell/platform/tizen/flutter_tizen.cc index 67862e7..9615b11 100644 --- a/flutter/shell/platform/tizen/flutter_tizen.cc +++ b/flutter/shell/platform/tizen/flutter_tizen.cc @@ -205,7 +205,8 @@ FlutterDesktopViewRef FlutterDesktopViewCreateFromNewWindow( window_geometry, window_properties.transparent, window_properties.focusable, window_properties.top_level, window_properties.pointing_device_support, - window_properties.floating_menu_support, window_properties.window_handle); + window_properties.floating_menu_support, window_properties.window_handle, + window_properties.renderer_type == kEVulkan); auto view = std::make_unique( flutter::kImplicitViewId, std::move(window), diff --git a/flutter/shell/platform/tizen/flutter_tizen_engine.cc b/flutter/shell/platform/tizen/flutter_tizen_engine.cc index dfa7338..acfb334 100644 --- a/flutter/shell/platform/tizen/flutter_tizen_engine.cc +++ b/flutter/shell/platform/tizen/flutter_tizen_engine.cc @@ -18,6 +18,10 @@ #include "flutter/shell/platform/tizen/tizen_input_method_context.h" #include "flutter/shell/platform/tizen/tizen_renderer_egl.h" +#ifdef FLUTTER_TIZEN_EXPERIMENTAL +#include "flutter/shell/platform/tizen/tizen_renderer_vulkan.h" +#endif + #ifdef NUI_SUPPORT #include "flutter/shell/platform/tizen/tizen_renderer_nui_gl.h" #include "flutter/shell/platform/tizen/tizen_view_nui.h" @@ -95,6 +99,12 @@ std::unique_ptr FlutterTizenEngine::CreateRenderer( #endif return std::make_unique( view_->tizen_view(), project_->HasArgument("--enable-impeller")); + case FlutterDesktopRendererType::kEVulkan: +#ifdef FLUTTER_TIZEN_EXPERIMENTAL + return std::make_unique(view_->tizen_view()); +#else + return nullptr; +#endif } } diff --git a/flutter/shell/platform/tizen/public/flutter_tizen.h b/flutter/shell/platform/tizen/public/flutter_tizen.h index 0712575..7d54db2 100644 --- a/flutter/shell/platform/tizen/public/flutter_tizen.h +++ b/flutter/shell/platform/tizen/public/flutter_tizen.h @@ -28,6 +28,8 @@ typedef struct FlutterDesktopView* FlutterDesktopViewRef; typedef enum { // The renderer based on EGL. kEGL, + // The renderer based on Vulkan. + kEVulkan } FlutterDesktopRendererType; typedef enum { diff --git a/flutter/shell/platform/tizen/tizen_renderer_vulkan.cc b/flutter/shell/platform/tizen/tizen_renderer_vulkan.cc new file mode 100644 index 0000000..764ac5c --- /dev/null +++ b/flutter/shell/platform/tizen/tizen_renderer_vulkan.cc @@ -0,0 +1,992 @@ +// Copyright 2025 Samsung Electronics Co., Ltd. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "flutter/shell/platform/tizen/tizen_renderer_vulkan.h" + +#include +#include +#include + +#include "flutter/shell/platform/tizen/flutter_tizen_engine.h" +#include "flutter/shell/platform/tizen/logger.h" + +namespace flutter { + +const std::vector validation_layers = { + "VK_LAYER_KHRONOS_validation"}; + +VkResult CreateDebugUtilsMessengerEXT( + VkInstance instance, + const VkDebugUtilsMessengerCreateInfoEXT* pCreateInfo, + const VkAllocationCallbacks* pAllocator, + VkDebugUtilsMessengerEXT* pDebugMessenger) { + auto func = (PFN_vkCreateDebugUtilsMessengerEXT)vkGetInstanceProcAddr( + instance, "vkCreateDebugUtilsMessengerEXT"); + if (func != nullptr) { + return func(instance, pCreateInfo, pAllocator, pDebugMessenger); + } else { + return VK_ERROR_EXTENSION_NOT_PRESENT; + } +} + +void DestroyDebugUtilsMessengerEXT(VkInstance instance, + VkDebugUtilsMessengerEXT debugMessenger, + const VkAllocationCallbacks* pAllocator) { + auto func = (PFN_vkDestroyDebugUtilsMessengerEXT)vkGetInstanceProcAddr( + instance, "vkDestroyDebugUtilsMessengerEXT"); + if (func != nullptr) { + func(instance, debugMessenger, pAllocator); + } +} + +TizenRendererVulkan::TizenRendererVulkan(TizenViewBase* view) { + InitVulkan(view); +} + +bool TizenRendererVulkan::InitVulkan(TizenViewBase* view) { + if (!CreateInstance()) { + FT_LOG(Error) << "Failed to create Vulkan instance"; + return false; + } + if (enable_validation_layers_) { + SetupDebugMessenger(); + } + + if (!TizenRenderer::CreateSurface(view)) { + FT_LOG(Error) << "Failed to create surface"; + return false; + } + if (!PickPhysicalDevice()) { + FT_LOG(Error) << "Failed to pick physical device"; + return false; + } + if (!CreateLogicalDevice()) { + FT_LOG(Error) << "Failed to create logical device"; + return false; + } + if (!GetDeviceQueue()) { + FT_LOG(Error) << "Failed to get device queue"; + return false; + } + if (!CreateSemaphore()) { + FT_LOG(Error) << "Failed to create semaphore"; + return false; + } + if (!CreateFence()) { + FT_LOG(Error) << "Failed to create fence"; + return false; + } + if (!CreateCommandPool()) { + FT_LOG(Error) << "Failed to create command pool"; + return false; + } + if (!InitializeSwapchain()) { + FT_LOG(Error) << "Failed to initialize swapchain"; + return false; + } + is_valid_ = true; + return true; +} + +void TizenRendererVulkan::Cleanup() { + if (logical_device_) { + // Ensure all GPU work is complete before destroying anything. + vkDeviceWaitIdle(logical_device_); + + for (size_t i = 0; i < present_transition_buffers_.size(); ++i) { + vkFreeCommandBuffers(logical_device_, swapchain_command_pool_, 1, + &present_transition_buffers_[i]); + } + if (swapchain_ != VK_NULL_HANDLE) { + vkDestroySwapchainKHR(logical_device_, swapchain_, nullptr); + swapchain_ = VK_NULL_HANDLE; + } + DestroyCommandPool(); + vkDestroyFence(logical_device_, image_ready_fence_, nullptr); + vkDestroySemaphore(logical_device_, present_transition_semaphore_, nullptr); + vkDestroyDevice(logical_device_, nullptr); + logical_device_ = VK_NULL_HANDLE; + } + DestroySurface(); + if (enable_validation_layers_) { + DestroyDebugUtilsMessengerEXT(instance_, debug_messenger_, nullptr); + } + if (instance_ != VK_NULL_HANDLE) { + vkDestroyInstance(instance_, nullptr); + instance_ = VK_NULL_HANDLE; + } +} + +std::unique_ptr TizenRendererVulkan::CreateExternalTexture( + const FlutterDesktopTextureInfo* texture_info) { + return nullptr; +} + +FlutterRendererConfig TizenRendererVulkan::GetRendererConfig() { + FlutterRendererConfig config = {}; + config.type = kVulkan; + config.vulkan.struct_size = sizeof(config.vulkan); + config.vulkan.version = GetVersion(); + config.vulkan.instance = GetInstanceHandle(); + config.vulkan.physical_device = GetPhysicalDeviceHandle(); + config.vulkan.device = GetDeviceHandle(); + config.vulkan.queue = GetQueueHandle(); + config.vulkan.queue_family_index = GetQueueIndex(); + config.vulkan.enabled_instance_extension_count = + GetEnabledInstanceExtensionCount(); + config.vulkan.enabled_instance_extensions = GetEnabledInstanceExtensions(); + config.vulkan.enabled_device_extension_count = + GetEnabledDeviceExtensionCount(); + config.vulkan.enabled_device_extensions = GetEnabledDeviceExtensions(); + config.vulkan.get_instance_proc_address_callback = + [](void* user_data, FlutterVulkanInstanceHandle instance, + const char* name) -> void* { + auto* engine = reinterpret_cast(user_data); + if (!engine->view()) { + return nullptr; + } + return dynamic_cast(engine->renderer()) + ->GetInstanceProcAddress(instance, name); + }; + config.vulkan.get_next_image_callback = + [](void* user_data, const FlutterFrameInfo* frame) -> FlutterVulkanImage { + auto* engine = reinterpret_cast(user_data); + if (!engine->view()) { + return FlutterVulkanImage(); + } + return dynamic_cast(engine->renderer()) + ->GetNextImage(frame); + }; + config.vulkan.present_image_callback = + [](void* user_data, const FlutterVulkanImage* image) -> bool { + auto* engine = reinterpret_cast(user_data); + if (!engine->view()) { + return false; + } + + return dynamic_cast(engine->renderer()) + ->Present(image); + }; + return config; +} + +bool TizenRendererVulkan::CreateInstance() { + if (enable_validation_layers_ && !CheckValidationLayerSupport()) { + FT_LOG(Error) << "Validation layers requested, but not available"; + return false; + } + + if (!GetRequiredExtensions(enabled_instance_extensions_)) { + FT_LOG(Error) << "Failed to get required extensions"; + return false; + } + + // Create instance. + VkApplicationInfo app_info = { + .sType = VK_STRUCTURE_TYPE_APPLICATION_INFO, + .pNext = nullptr, + .pApplicationName = "Tizen Embedder", + .applicationVersion = VK_MAKE_VERSION(1, 0, 0), + .pEngineName = "Tizen Embedder", + .engineVersion = VK_MAKE_VERSION(1, 0, 0), + .apiVersion = VK_API_VERSION_1_1, + }; + VkInstanceCreateInfo create_info = {}; + create_info.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; + create_info.pApplicationInfo = &app_info; + create_info.enabledExtensionCount = + static_cast(enabled_instance_extensions_.size()); + create_info.ppEnabledExtensionNames = enabled_instance_extensions_.data(); + create_info.flags = 0; + + VkDebugUtilsMessengerCreateInfoEXT debug_create_info{}; + + if (enable_validation_layers_) { + create_info.enabledLayerCount = + static_cast(validation_layers.size()); + create_info.ppEnabledLayerNames = validation_layers.data(); + PopulateDebugMessengerCreateInfo(debug_create_info); + create_info.pNext = reinterpret_cast( + &debug_create_info); + } else { + create_info.enabledLayerCount = 0; + create_info.pNext = nullptr; + } + + if (vkCreateInstance(&create_info, nullptr, &instance_) != VK_SUCCESS) { + FT_LOG(Error) << "Create instance failed."; + return false; + } + return true; +} + +bool TizenRendererVulkan::GetRequiredExtensions( + std::vector& extensions) { + uint32_t instance_extension_count = 0; + bool has_surface_extension = false; + + if (vkEnumerateInstanceExtensionProperties(nullptr, &instance_extension_count, + nullptr) != VK_SUCCESS) { + FT_LOG(Error) << "Failed to enumerate instance extension count"; + return false; + } + if (instance_extension_count > 0) { + std::vector instance_extension_properties( + instance_extension_count); + + if (vkEnumerateInstanceExtensionProperties( + nullptr, &instance_extension_count, + instance_extension_properties.data()) != VK_SUCCESS) { + FT_LOG(Error) << "Failed to enumerate instance extension properties"; + return false; + } + for (const auto& ext : instance_extension_properties) { + if (!strcmp(VK_KHR_SURFACE_EXTENSION_NAME, ext.extensionName)) { + has_surface_extension = true; + extensions.push_back(VK_KHR_SURFACE_EXTENSION_NAME); + } else if (!strcmp(VK_KHR_WAYLAND_SURFACE_EXTENSION_NAME, + ext.extensionName)) { + has_surface_extension = true; + extensions.push_back(VK_KHR_WAYLAND_SURFACE_EXTENSION_NAME); + } else if (!strcmp(VK_EXT_DEBUG_UTILS_EXTENSION_NAME, + ext.extensionName)) { + if (enable_validation_layers_) { + extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); + } + } + } + } + + if (!has_surface_extension) { + FT_LOG(Error) << "vkEnumerateInstanceExtensionProperties failed to find " + "the extensions"; + return false; + } + + return true; +} + +void TizenRendererVulkan::SetupDebugMessenger() { + VkDebugUtilsMessengerCreateInfoEXT debug_create_info; + PopulateDebugMessengerCreateInfo(debug_create_info); + + if (CreateDebugUtilsMessengerEXT(instance_, &debug_create_info, nullptr, + &debug_messenger_) != VK_SUCCESS) { + FT_LOG(Error) << "Failed to set up debug messenger"; + } +} + +bool TizenRendererVulkan::CheckValidationLayerSupport() { + uint32_t layer_count; + if (vkEnumerateInstanceLayerProperties(&layer_count, nullptr) != VK_SUCCESS) { + FT_LOG(Error) << "Failed to enumerate instance layer properties"; + return false; + } + std::vector available_layers(layer_count); + if (vkEnumerateInstanceLayerProperties( + &layer_count, available_layers.data()) != VK_SUCCESS) { + FT_LOG(Error) << "Failed to enumerate instance layer properties"; + return false; + } + + for (const char* layer_name : validation_layers) { + bool layer_found = false; + for (const auto& layer_properties : available_layers) { + FT_LOG(Info) << "layer_properties.layerName : " + << layer_properties.layerName; + if (strcmp(layer_name, layer_properties.layerName) == 0) { + layer_found = true; + break; + } + } + if (!layer_found) { + FT_LOG(Error) << "Layer requested is not available"; + return false; + } + } + return true; +} + +static VKAPI_ATTR VkBool32 VKAPI_CALL DebugMessengerCallback( + VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, + VkDebugUtilsMessageTypeFlagsEXT messageType, + const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData, + void* pUserData) { + FT_LOG(Error) << pCallbackData->pMessage; + return VK_FALSE; +} + +void TizenRendererVulkan::PopulateDebugMessengerCreateInfo( + VkDebugUtilsMessengerCreateInfoEXT& createInfo) { + createInfo = {}; + createInfo.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT; + createInfo.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT | + VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | + VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT; + createInfo.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; + createInfo.pfnUserCallback = DebugMessengerCallback; +} + +bool TizenRendererVulkan::PickPhysicalDevice() { + uint32_t gpu_count = 0; + if (vkEnumeratePhysicalDevices(instance_, &gpu_count, nullptr) != + VK_SUCCESS || + gpu_count == 0) { + FT_LOG(Error) << "Error occurred during physical devices enumeration."; + return false; + } + + std::vector physical_devices(gpu_count); + if (vkEnumeratePhysicalDevices(instance_, &gpu_count, + physical_devices.data()) != VK_SUCCESS) { + FT_LOG(Error) << "Error occurred during physical devices enumeration."; + return false; + } + + FT_LOG(Info) << "Enumerating " << gpu_count << " physical device(s)."; + + uint32_t selected_score = 0; + for (const auto& physical_device : physical_devices) { + VkPhysicalDeviceProperties properties; + VkPhysicalDeviceFeatures features; + vkGetPhysicalDeviceProperties(physical_device, &properties); + vkGetPhysicalDeviceFeatures(physical_device, &features); + FT_LOG(Info) << "Checking device : " << properties.deviceName; + uint32_t score = 0; + std::vector supported_extensions; + uint32_t qfp_count; + vkGetPhysicalDeviceQueueFamilyProperties(physical_device, &qfp_count, + nullptr); + std::vector qfp(qfp_count); + vkGetPhysicalDeviceQueueFamilyProperties(physical_device, &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; + vkGetPhysicalDeviceSurfaceSupportKHR(physical_device, i, 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()) { + FT_LOG(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; + vkEnumerateDeviceExtensionProperties(physical_device, nullptr, + &extension_count, nullptr); + std::vector available_extensions(extension_count); + vkEnumerateDeviceExtensionProperties(physical_device, 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); + } else if (strcmp("VK_KHR_portability_subset", + available_extension.extensionName) == 0) { + supported_extensions.push_back("VK_KHR_portability_subset"); + } 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(available_extension.extensionName, + VK_KHR_SAMPLER_YCBCR_CONVERSION_EXTENSION_NAME) == 0) { + supported_extensions.push_back( + VK_KHR_SAMPLER_YCBCR_CONVERSION_EXTENSION_NAME); + } else if (strcmp(available_extension.extensionName, + VK_KHR_EXTERNAL_MEMORY_FD_EXTENSION_NAME) == 0) { + supported_extensions.push_back( + VK_KHR_EXTERNAL_MEMORY_FD_EXTENSION_NAME); + } + } + // Skip physical devices that don't have swapchain support. + if (!supports_swapchain) { + FT_LOG(Info) << "Skipping due to lack of swapchain support."; + continue; + } + // Prefer GPUs with larger max texture sizes. + score += properties.limits.maxImageDimension2D; + if (selected_score < score) { + FT_LOG(Info) << "This is the best device so far. Score: " << score; + + selected_score = score; + physical_device_ = physical_device; + enabled_device_extensions_ = supported_extensions; + graphics_queue_family_index_ = + graphics_queue_family.value_or(std::numeric_limits::max()); + } + } + return physical_device_ != VK_NULL_HANDLE; +} + +TizenRendererVulkan::~TizenRendererVulkan() { + Cleanup(); +} +bool TizenRendererVulkan::CreateSurface(void* render_target, + void* render_target_display, + int32_t width, + int32_t height) { + width_ = width; + height_ = height; + VkWaylandSurfaceCreateInfoKHR createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_WAYLAND_SURFACE_CREATE_INFO_KHR; + createInfo.pNext = nullptr; + createInfo.flags = 0; + createInfo.display = static_cast(render_target_display); + createInfo.surface = static_cast(render_target); + + PFN_vkCreateWaylandSurfaceKHR vkCreateWaylandSurfaceKHR; + vkCreateWaylandSurfaceKHR = + (PFN_vkCreateWaylandSurfaceKHR)vkGetInstanceProcAddr( + instance_, "vkCreateWaylandSurfaceKHR"); + + if (!vkCreateWaylandSurfaceKHR) { + FT_LOG(Error) << "Wayland: Vulkan instance missing " + "VK_KHR_wayland_surface extension"; + return false; + } + + if (vkCreateWaylandSurfaceKHR(instance_, &createInfo, nullptr, &surface_) != + VK_SUCCESS) { + FT_LOG(Error) << "Failed to create surface."; + return false; + } + return true; +} + +bool TizenRendererVulkan::CreateLogicalDevice() { + VkDeviceQueueCreateInfo queue_info{}; + queue_info.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; + queue_info.queueFamilyIndex = graphics_queue_family_index_; + queue_info.queueCount = 1; + float priority = 1.0f; + queue_info.pQueuePriorities = &priority; + + VkPhysicalDeviceFeatures device_features{}; + VkDeviceCreateInfo device_info{}; + device_info.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; + device_info.queueCreateInfoCount = 1; + device_info.pQueueCreateInfos = &queue_info; + device_info.enabledExtensionCount = + static_cast(enabled_device_extensions_.size()); + device_info.ppEnabledExtensionNames = enabled_device_extensions_.data(); + device_info.pEnabledFeatures = &device_features; + + if (vkCreateDevice(physical_device_, &device_info, nullptr, + &logical_device_) != VK_SUCCESS) { + FT_LOG(Error) << "Failed to create device."; + return false; + } + return true; +} + +bool TizenRendererVulkan::GetDeviceQueue() { + vkGetDeviceQueue(logical_device_, graphics_queue_family_index_, 0, + &graphics_queue_); + return graphics_queue_ != VK_NULL_HANDLE; +} + +bool TizenRendererVulkan::CreateCommandPool() { + VkCommandPoolCreateInfo pool_info; + pool_info.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; + pool_info.pNext = nullptr; + pool_info.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT | + VK_COMMAND_POOL_CREATE_TRANSIENT_BIT; + pool_info.queueFamilyIndex = graphics_queue_family_index_; + if (vkCreateCommandPool(logical_device_, &pool_info, nullptr, + &swapchain_command_pool_) != VK_SUCCESS) { + FT_LOG(Error) << "Failed to create command pool."; + return false; + } + return true; +} + +void TizenRendererVulkan::DestroyCommandPool() { + if (swapchain_command_pool_) { + vkDestroyCommandPool(logical_device_, swapchain_command_pool_, nullptr); + swapchain_command_pool_ = VK_NULL_HANDLE; + } +} + +bool TizenRendererVulkan::CreateSemaphore() { + VkSemaphoreCreateInfo semaphore_info{}; + semaphore_info.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; + if (vkCreateSemaphore(logical_device_, &semaphore_info, nullptr, + &present_transition_semaphore_) != VK_SUCCESS) { + FT_LOG(Error) << "Failed to create semaphore."; + return false; + } + return true; +} + +bool TizenRendererVulkan::CreateFence() { + VkFenceCreateInfo fence_info{}; + fence_info.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO; + if (vkCreateFence(logical_device_, &fence_info, nullptr, + &image_ready_fence_) != VK_SUCCESS) { + FT_LOG(Error) << "Failed to create image ready fence."; + return false; + } + + return true; +} + +VkSurfaceFormatKHR TizenRendererVulkan::GetSwapChainFormat( + std::vector& surface_formats) { + // If the list contains only one entry with undefined format + // it means that there are no preferred surface formats and any can be chosen + if ((surface_formats.size() == 1) && + (surface_formats[0].format == VK_FORMAT_UNDEFINED)) { + return {VK_FORMAT_R8G8B8A8_UNORM, VK_COLORSPACE_SRGB_NONLINEAR_KHR}; + } + + // Check if list contains most widely used R8 G8 B8 A8 format + // with nonlinear color space + for (VkSurfaceFormatKHR& surface_format : surface_formats) { + if (surface_format.format == VK_FORMAT_R8G8B8A8_UNORM) { + return surface_format; + } + } + + // Return the first format from the list + return surface_formats[0]; +} + +VkExtent2D TizenRendererVulkan::GetSwapChainExtent( + VkSurfaceCapabilitiesKHR& surface_capabilities) { + if (surface_capabilities.currentExtent.width != UINT32_MAX) { + // If the surface reports a specific extent, we must use it. + return surface_capabilities.currentExtent; + } else { + VkExtent2D swap_chain_extent{}; + swap_chain_extent.width = width_; + swap_chain_extent.height = height_; + + swap_chain_extent.width = + std::max(surface_capabilities.minImageExtent.width, + std::min(surface_capabilities.maxImageExtent.width, + swap_chain_extent.width)); + swap_chain_extent.height = + std::max(surface_capabilities.minImageExtent.height, + std::min(surface_capabilities.maxImageExtent.height, + swap_chain_extent.height)); + return swap_chain_extent; + } +} + +uint32_t TizenRendererVulkan::GetSwapChainNumImages( + VkSurfaceCapabilitiesKHR& surface_capabilities) { + 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) { + desiredImageCount = surface_capabilities.minImageCount; + } + return desiredImageCount; +} + +VkPresentModeKHR TizenRendererVulkan::GetSwapChainPresentMode( + std::vector& present_modes) { + VkPresentModeKHR present_mode = present_modes[0]; + for (const auto& mode : present_modes) { + if (mode == VK_PRESENT_MODE_FIFO_KHR) { + present_mode = mode; + break; + } + } + return present_mode; +} + +VkCompositeAlphaFlagBitsKHR TizenRendererVulkan::GetSwapChainCompositeAlpha( + VkSurfaceCapabilitiesKHR& surface_capabilities) { + if (surface_capabilities.supportedCompositeAlpha & + VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR) { + return VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR; + } else { + return VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR; + } +} + +bool TizenRendererVulkan::InitializeSwapchain() { + VkSwapchainKHR oldSwapchain = swapchain_; + uint32_t format_count; + if (vkGetPhysicalDeviceSurfaceFormatsKHR( + physical_device_, surface_, &format_count, nullptr) != VK_SUCCESS || + format_count == 0) { + FT_LOG(Error) + << "Error occurred during presentation surface formats enumeration"; + return false; + } + std::vector formats(format_count); + if (vkGetPhysicalDeviceSurfaceFormatsKHR(physical_device_, surface_, + &format_count, + formats.data()) != VK_SUCCESS) { + FT_LOG(Error) + << "Error occurred during presentation surface formats enumeration"; + return false; + } + VkSurfaceCapabilitiesKHR surface_capabilities; + if (vkGetPhysicalDeviceSurfaceCapabilitiesKHR( + physical_device_, surface_, &surface_capabilities) != VK_SUCCESS) { + FT_LOG(Error) << "Could not check presentation surface capabilities"; + return false; + } + + uint32_t mode_count; + if (vkGetPhysicalDeviceSurfacePresentModesKHR( + physical_device_, surface_, &mode_count, nullptr) != VK_SUCCESS) { + FT_LOG(Error) << "Error occurred during presentation surface present modes " + "enumeration"; + return false; + } + std::vector modes(mode_count); + if (vkGetPhysicalDeviceSurfacePresentModesKHR(physical_device_, surface_, + &mode_count, modes.data())) { + FT_LOG(Error) << "Error occurred during presentation surface present modes " + "enumeration"; + return false; + } + + surface_format_ = GetSwapChainFormat(formats); + VkExtent2D clientSize = GetSwapChainExtent(surface_capabilities); + uint32_t desiredImageCount = GetSwapChainNumImages(surface_capabilities); + VkPresentModeKHR present_mode = GetSwapChainPresentMode(modes); + VkCompositeAlphaFlagBitsKHR compositeAlpha = + GetSwapChainCompositeAlpha(surface_capabilities); + + VkSwapchainCreateInfoKHR info{}; + info.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR; + info.surface = surface_; + info.minImageCount = desiredImageCount; + info.imageFormat = surface_format_.format; + info.imageColorSpace = surface_format_.colorSpace; + info.imageExtent = clientSize; + info.imageArrayLayers = 1; + info.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; + info.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE; + info.queueFamilyIndexCount = 0; + info.pQueueFamilyIndices = nullptr; + info.preTransform = surface_capabilities.currentTransform; + info.compositeAlpha = compositeAlpha; + info.presentMode = present_mode; + info.clipped = VK_TRUE; + + if (vkCreateSwapchainKHR(logical_device_, &info, nullptr, &swapchain_) != + VK_SUCCESS) { + FT_LOG(Error) << "Could not create swap chain KHR"; + return false; + } + + if (oldSwapchain != VK_NULL_HANDLE) { + vkDestroySwapchainKHR(logical_device_, oldSwapchain, nullptr); + } + + // -------------------------------------------------------------------------- + // Fetch swapchain images + // -------------------------------------------------------------------------- + + uint32_t image_count; + if (vkGetSwapchainImagesKHR(logical_device_, swapchain_, &image_count, + nullptr) != VK_SUCCESS) { + FT_LOG(Error) << "Could not get swap chain images count"; + return false; + } + // swapchain_images_.reserve(image_count); + swapchain_images_.resize(image_count); + if (vkGetSwapchainImagesKHR(logical_device_, swapchain_, &image_count, + swapchain_images_.data()) != VK_SUCCESS) { + FT_LOG(Error) << "Could not get swap chain images"; + return false; + } + + // -------------------------------------------------------------------------- + // Record a command buffer for each of the images to be executed prior to + // presenting. + // -------------------------------------------------------------------------- + + present_transition_buffers_.resize(swapchain_images_.size()); + + VkCommandBufferAllocateInfo buffers_info{}; + buffers_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; + buffers_info.commandPool = swapchain_command_pool_; + buffers_info.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; + buffers_info.commandBufferCount = + static_cast(present_transition_buffers_.size()); + + if (vkAllocateCommandBuffers(logical_device_, &buffers_info, + present_transition_buffers_.data()) != + VK_SUCCESS) { + FT_LOG(Error) << "Could not allocate command buffers for swapchain images!"; + return false; + } + for (size_t i = 0; i < swapchain_images_.size(); i++) { + auto image = swapchain_images_[i]; + auto buffer = present_transition_buffers_[i]; + + VkCommandBufferBeginInfo begin_info{}; + begin_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; + + if (vkBeginCommandBuffer(buffer, &begin_info) != VK_SUCCESS) { + FT_LOG(Error) << "Could not begin command buffer!"; + return false; + } + + // Filament Engine hands back the image after writing to it + VkImageMemoryBarrier barrier{}; + barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; + barrier.srcAccessMask = 0; + barrier.dstAccessMask = VK_ACCESS_MEMORY_READ_BIT; + barrier.oldLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; + barrier.newLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; + barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier.image = image; + barrier.subresourceRange = { + .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, + .baseMipLevel = 0, + .levelCount = 1, + .baseArrayLayer = 0, + .layerCount = 1, + }; + vkCmdPipelineBarrier(buffer, VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, + VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, 0, 0, nullptr, 0, + nullptr, 1, &barrier); + if (vkEndCommandBuffer(buffer) != VK_SUCCESS) { + FT_LOG(Error) << "Could not end command buffer!"; + return false; + } + } + return true; +} + +bool TizenRendererVulkan::RecreateSwapChain() { + vkDeviceWaitIdle(logical_device_); + resize_pending_ = false; + for (size_t i = 0; i < present_transition_buffers_.size(); ++i) { + vkFreeCommandBuffers(logical_device_, swapchain_command_pool_, 1, + &present_transition_buffers_[i]); + } + DestroyCommandPool(); + if (!CreateCommandPool()) { + FT_LOG(Error) << "Fail to create command pool!"; + return false; + } + if (!InitializeSwapchain()) { + FT_LOG(Error) << "Fail to create swapchain!"; + return false; + } + return true; +} + +void TizenRendererVulkan::DestroySurface() { + if (surface_) { + vkDestroySurfaceKHR(instance_, surface_, nullptr); + } +} + +void TizenRendererVulkan::ResizeSurface(int32_t width, int32_t height) { + if (width_ != width || height_ != height) { + width_ = width; + height_ = height; + resize_pending_ = true; + } +} +uint32_t TizenRendererVulkan::GetVersion() { + return VK_API_VERSION_1_1; +} + +FlutterVulkanInstanceHandle TizenRendererVulkan::GetInstanceHandle() { + return instance_; +} + +FlutterVulkanQueueHandle TizenRendererVulkan::GetQueueHandle() { + return graphics_queue_; +} + +FlutterVulkanPhysicalDeviceHandle +TizenRendererVulkan::GetPhysicalDeviceHandle() { + return physical_device_; +} + +FlutterVulkanDeviceHandle TizenRendererVulkan::GetDeviceHandle() { + return logical_device_; +} + +uint32_t TizenRendererVulkan::GetQueueIndex() { + return graphics_queue_family_index_; +} + +const char** TizenRendererVulkan::GetEnabledInstanceExtensions() { + return enabled_instance_extensions_.data(); +} + +const char** TizenRendererVulkan::GetEnabledDeviceExtensions() { + return enabled_device_extensions_.data(); +} + +void* TizenRendererVulkan::GetInstanceProcAddress( + FlutterVulkanInstanceHandle instance, + const char* name) { + return reinterpret_cast( + vkGetInstanceProcAddr(reinterpret_cast(instance), name)); +} + +FlutterVulkanImage TizenRendererVulkan::GetNextImage( + const FlutterFrameInfo* frameInfo) { + if (resize_pending_) { + RecreateSwapChain(); + } + VkResult result; + while (true) { + result = vkAcquireNextImageKHR(logical_device_, swapchain_, UINT64_MAX, + VK_NULL_HANDLE, image_ready_fence_, + &last_image_index_); + if (result == VK_SUCCESS) { + // Image successfully acquired. + break; + } else if (result == VK_ERROR_OUT_OF_DATE_KHR) { + // Swapchain is out of date, recreate it and try again. + RecreateSwapChain(); + continue; + } else if (result == VK_SUBOPTIMAL_KHR) { + // Swapchain is suboptimal, but we can present with it. Break to proceed. + break; + } else { + // An unexpected error occurred. Log it and break to avoid an infinite + // loop. + FT_LOG(Error) << "vkAcquireNextImageKHR failed with critical error: " + << result; + break; + } + } + + // Check if the acquisition was successful or suboptimal. + // If not, return an empty image to signal failure to the Flutter engine. + if (result != VK_SUCCESS && result != VK_SUBOPTIMAL_KHR) { + return FlutterVulkanImage{}; + } + + vkWaitForFences(logical_device_, 1, &image_ready_fence_, VK_TRUE, UINT64_MAX); + vkResetFences(logical_device_, 1, &image_ready_fence_); + return { + .struct_size = sizeof(FlutterVulkanImage), + .image = reinterpret_cast(swapchain_images_[last_image_index_]), + .format = static_cast(surface_format_.format), + }; +} + +bool TizenRendererVulkan::Present(const FlutterVulkanImage* image) { + VkPipelineStageFlags stage_flags = + VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; + VkSubmitInfo submit_info{}; + submit_info.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; + submit_info.waitSemaphoreCount = 0; + submit_info.pWaitSemaphores = VK_NULL_HANDLE; + submit_info.pWaitDstStageMask = &stage_flags; + submit_info.commandBufferCount = 1; + submit_info.pCommandBuffers = &present_transition_buffers_[last_image_index_]; + submit_info.signalSemaphoreCount = 1; + submit_info.pSignalSemaphores = &present_transition_semaphore_; + vkQueueSubmit(graphics_queue_, 1, &submit_info, VK_NULL_HANDLE); + + VkPresentInfoKHR present_info{}; + present_info.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR; + present_info.waitSemaphoreCount = 1; + present_info.pWaitSemaphores = &present_transition_semaphore_; + present_info.swapchainCount = 1; + present_info.pSwapchains = &swapchain_; + present_info.pImageIndices = &last_image_index_; + VkResult result = vkQueuePresentKHR(graphics_queue_, &present_info); + + if (result == VK_SUBOPTIMAL_KHR || result == VK_ERROR_OUT_OF_DATE_KHR) { + return RecreateSwapChain(); + } else { + vkDeviceWaitIdle(logical_device_); + return result == VK_SUCCESS; + } +} + +size_t TizenRendererVulkan::GetEnabledInstanceExtensionCount() { + return enabled_instance_extensions_.size(); +} + +size_t TizenRendererVulkan::GetEnabledDeviceExtensionCount() { + return enabled_device_extensions_.size(); +} + +VkCommandBuffer TizenRendererVulkan::BeginSingleTimeCommands() { + VkCommandBufferAllocateInfo allocInfo{}; + allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; + allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; + allocInfo.commandPool = swapchain_command_pool_; + allocInfo.commandBufferCount = 1; + + VkCommandBuffer commandBuffer; + vkAllocateCommandBuffers(logical_device_, &allocInfo, &commandBuffer); + + VkCommandBufferBeginInfo beginInfo{}; + beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; + beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; + + if (vkBeginCommandBuffer(commandBuffer, &beginInfo) != VK_SUCCESS) { + vkFreeCommandBuffers(logical_device_, swapchain_command_pool_, 1, + &commandBuffer); + FT_LOG(Error) << "Failed to begin one-time command buffer."; + return VK_NULL_HANDLE; + } + + return commandBuffer; +} + +void TizenRendererVulkan::EndSingleTimeCommands(VkCommandBuffer commandBuffer) { + if (vkEndCommandBuffer(commandBuffer) != VK_SUCCESS) { + FT_LOG(Error) << "Failed to end one-time command buffer."; + vkFreeCommandBuffers(logical_device_, swapchain_command_pool_, 1, + &commandBuffer); + return; + } + + VkSubmitInfo submitInfo{}; + submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; + submitInfo.commandBufferCount = 1; + submitInfo.pCommandBuffers = &commandBuffer; + + if (vkQueueSubmit(graphics_queue_, 1, &submitInfo, VK_NULL_HANDLE) != + VK_SUCCESS) { + FT_LOG(Error) << "Failed to submit one-time command buffer."; + vkFreeCommandBuffers(logical_device_, swapchain_command_pool_, 1, + &commandBuffer); + return; + } + vkQueueWaitIdle(graphics_queue_); + + vkFreeCommandBuffers(logical_device_, swapchain_command_pool_, 1, + &commandBuffer); +} + +} // namespace flutter diff --git a/flutter/shell/platform/tizen/tizen_renderer_vulkan.h b/flutter/shell/platform/tizen/tizen_renderer_vulkan.h new file mode 100644 index 0000000..e2a71d4 --- /dev/null +++ b/flutter/shell/platform/tizen/tizen_renderer_vulkan.h @@ -0,0 +1,105 @@ +// Copyright 2025 Samsung Electronics Co., Ltd. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef EMBEDDER_TIZEN_RENDERER_VULKAN_H_ +#define EMBEDDER_TIZEN_RENDERER_VULKAN_H_ + +#include "flutter/shell/platform/tizen/tizen_renderer.h" +#include "flutter/shell/platform/tizen/tizen_view_base.h" + +#include +#include +#include +#include +#include + +namespace flutter { + +class TizenRendererVulkan : public TizenRenderer { + public: + explicit TizenRendererVulkan(TizenViewBase* view); + virtual ~TizenRendererVulkan(); + + std::unique_ptr CreateExternalTexture( + const FlutterDesktopTextureInfo* texture_info) override; + + FlutterRendererConfig GetRendererConfig() override; + + bool CreateSurface(void* render_target, + void* render_target_display, + int32_t width, + int32_t height) override; + void DestroySurface() override; + void ResizeSurface(int32_t width, int32_t height) override; + uint32_t GetVersion(); + FlutterVulkanInstanceHandle GetInstanceHandle(); + FlutterVulkanQueueHandle GetQueueHandle(); + FlutterVulkanPhysicalDeviceHandle GetPhysicalDeviceHandle(); + FlutterVulkanDeviceHandle GetDeviceHandle(); + uint32_t GetQueueIndex(); + size_t GetEnabledInstanceExtensionCount(); + const char** GetEnabledInstanceExtensions(); + size_t GetEnabledDeviceExtensionCount(); + const char** GetEnabledDeviceExtensions(); + void* GetInstanceProcAddress(FlutterVulkanInstanceHandle instance, + const char* name); + FlutterVulkanImage GetNextImage(const FlutterFrameInfo* frameInfo); + bool Present(const FlutterVulkanImage* image); + VkCommandBuffer BeginSingleTimeCommands(); + void EndSingleTimeCommands(VkCommandBuffer commandBuffer); + + private: + bool CreateCommandPool(); + bool CreateInstance(); + bool CreateLogicalDevice(); + bool CreateFence(); + bool CreateSemaphore(); + void Cleanup(); + bool CheckValidationLayerSupport(); + void DestroyCommandPool(); + bool GetDeviceQueue(); + bool GetRequiredExtensions(std::vector& extensions); + VkSurfaceFormatKHR GetSwapChainFormat( + std::vector& surface_formats); + VkExtent2D GetSwapChainExtent(VkSurfaceCapabilitiesKHR& surface_capabilities); + uint32_t GetSwapChainNumImages( + VkSurfaceCapabilitiesKHR& surface_capabilities); + VkPresentModeKHR GetSwapChainPresentMode( + std::vector& present_modes); + VkCompositeAlphaFlagBitsKHR GetSwapChainCompositeAlpha( + VkSurfaceCapabilitiesKHR& surface_capabilities); + bool InitVulkan(TizenViewBase* view); + bool InitializeSwapchain(); + bool RecreateSwapChain(); + void PopulateDebugMessengerCreateInfo( + VkDebugUtilsMessengerCreateInfoEXT& createInfo); + bool PickPhysicalDevice(); + void SetupDebugMessenger(); + + bool enable_validation_layers_ = false; + + VkDebugUtilsMessengerEXT debug_messenger_ = VK_NULL_HANDLE; + VkDevice logical_device_ = VK_NULL_HANDLE; + VkInstance instance_ = VK_NULL_HANDLE; + VkPhysicalDevice physical_device_ = VK_NULL_HANDLE; + VkQueue graphics_queue_ = VK_NULL_HANDLE; + VkSurfaceKHR surface_ = VK_NULL_HANDLE; + VkSurfaceFormatKHR surface_format_; + VkSemaphore present_transition_semaphore_ = VK_NULL_HANDLE; + VkFence image_ready_fence_ = VK_NULL_HANDLE; + VkSwapchainKHR swapchain_ = VK_NULL_HANDLE; + VkCommandPool swapchain_command_pool_ = VK_NULL_HANDLE; + std::vector swapchain_images_; + std::vector present_transition_buffers_; + std::vector enabled_device_extensions_; + std::vector enabled_instance_extensions_; + uint32_t graphics_queue_family_index_ = 0; + uint32_t last_image_index_ = 0; + bool resize_pending_ = false; + int32_t width_ = 0; + int32_t height_ = 0; +}; +} // namespace flutter + +#endif // EMBEDDER_TIZEN_RENDERER_VULKAN_H_ diff --git a/flutter/shell/platform/tizen/tizen_window_ecore_wl2.cc b/flutter/shell/platform/tizen/tizen_window_ecore_wl2.cc index b92a690..41d16b9 100644 --- a/flutter/shell/platform/tizen/tizen_window_ecore_wl2.cc +++ b/flutter/shell/platform/tizen/tizen_window_ecore_wl2.cc @@ -148,14 +148,16 @@ TizenWindowEcoreWl2::TizenWindowEcoreWl2(TizenGeometry geometry, bool top_level, bool pointing_device_support, bool floating_menu_support, - void* window_handle = nullptr) + void* window_handle = nullptr, + bool is_vulkan = false) : TizenWindow(geometry, transparent, focusable, top_level) #ifdef TV_PROFILE , pointing_device_support_(pointing_device_support), floating_menu_support_(floating_menu_support) #endif -{ + , + is_vulkan_(is_vulkan) { if (!CreateWindow(window_handle)) { FT_LOG(Error) << "Failed to create a platform window."; return; @@ -210,9 +212,14 @@ bool TizenWindowEcoreWl2::CreateWindow(void* window_handle) { ecore_wl2_window_ = static_cast(window_handle); } - ecore_wl2_egl_window_ = ecore_wl2_egl_window_create( - ecore_wl2_window_, initial_geometry_.width, initial_geometry_.height); - return ecore_wl2_egl_window_ && wl2_display_; + if (is_vulkan_) { + wl2_surface_ = ecore_wl2_window_surface_get(ecore_wl2_window_); + return wl2_surface_ && wl2_display_; + } else { + ecore_wl2_egl_window_ = ecore_wl2_egl_window_create( + ecore_wl2_window_, initial_geometry_.width, initial_geometry_.height); + return ecore_wl2_egl_window_ && wl2_display_; + } } void TizenWindowEcoreWl2::SetWindowOptions() { @@ -423,30 +430,30 @@ void TizenWindowEcoreWl2::RegisterEventHandlers() { return ECORE_CALLBACK_PASS_ON; }, this)); - - ecore_event_handlers_.push_back(ecore_event_handler_add( - ECORE_WL2_EVENT_WINDOW_CONFIGURE, - [](void* data, int type, void* event) -> Eina_Bool { - auto* self = static_cast(data); - if (self->view_delegate_) { - auto* configure_event = - reinterpret_cast(event); - if (configure_event->win == self->GetWindowId()) { - ecore_wl2_egl_window_resize_with_rotation( - self->ecore_wl2_egl_window_, configure_event->x, - configure_event->y, configure_event->w, configure_event->h, - self->GetRotation()); - - self->view_delegate_->OnResize( - configure_event->x, configure_event->y, configure_event->w, - configure_event->h); - return ECORE_CALLBACK_DONE; + if (!is_vulkan_) { + ecore_event_handlers_.push_back(ecore_event_handler_add( + ECORE_WL2_EVENT_WINDOW_CONFIGURE, + [](void* data, int type, void* event) -> Eina_Bool { + auto* self = static_cast(data); + if (self->view_delegate_) { + auto* configure_event = + reinterpret_cast(event); + if (configure_event->win == self->GetWindowId()) { + ecore_wl2_egl_window_resize_with_rotation( + self->ecore_wl2_egl_window_, configure_event->x, + configure_event->y, configure_event->w, configure_event->h, + self->GetRotation()); + + self->view_delegate_->OnResize( + configure_event->x, configure_event->y, configure_event->w, + configure_event->h); + return ECORE_CALLBACK_DONE; + } } - } - return ECORE_CALLBACK_PASS_ON; - }, - this)); - + return ECORE_CALLBACK_PASS_ON; + }, + this)); + } ecore_event_handlers_.push_back(ecore_event_handler_add( ECORE_EVENT_MOUSE_BUTTON_DOWN, [](void* data, int type, void* event) -> Eina_Bool { @@ -848,6 +855,14 @@ void TizenWindowEcoreWl2::PrepareInputMethod() { [this](std::string str) { view_delegate_->OnCommit(str); }); } +void* TizenWindowEcoreWl2::GetRenderTarget() { + if (is_vulkan_) { + return wl2_surface_; + } else { + return ecore_wl2_egl_window_; + } +} + void TizenWindowEcoreWl2::ActivateWindow() { ecore_wl2_window_activate(ecore_wl2_window_); } diff --git a/flutter/shell/platform/tizen/tizen_window_ecore_wl2.h b/flutter/shell/platform/tizen/tizen_window_ecore_wl2.h index 2ec9a94..3cc1a9d 100644 --- a/flutter/shell/platform/tizen/tizen_window_ecore_wl2.h +++ b/flutter/shell/platform/tizen/tizen_window_ecore_wl2.h @@ -25,7 +25,8 @@ class TizenWindowEcoreWl2 : public TizenWindow { bool top_level, bool pointing_device_support, bool floating_menu_support, - void* window_handle); + void* window_handle, + bool is_vulkan); ~TizenWindowEcoreWl2(); @@ -35,7 +36,7 @@ class TizenWindowEcoreWl2 : public TizenWindow { TizenGeometry GetScreenGeometry() override; - void* GetRenderTarget() override { return ecore_wl2_egl_window_; } + void* GetRenderTarget() override; void* GetRenderTargetDisplay() override { return wl2_display_; } @@ -90,11 +91,10 @@ class TizenWindowEcoreWl2 : public TizenWindow { Ecore_Wl2_Display* ecore_wl2_display_ = nullptr; Ecore_Wl2_Window* ecore_wl2_window_ = nullptr; - Ecore_Wl2_Egl_Window* ecore_wl2_egl_window_ = nullptr; wl_display* wl2_display_ = nullptr; + wl_surface* wl2_surface_ = nullptr; std::vector ecore_event_handlers_; - tizen_policy* tizen_policy_ = nullptr; uint32_t resource_id_ = 0; @@ -103,6 +103,7 @@ class TizenWindowEcoreWl2 : public TizenWindow { bool floating_menu_support_ = true; bool show_unsupported_toast_ = false; #endif + bool is_vulkan_ = false; }; } // namespace flutter