diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter index 451829e08617e..21443efbc68e6 100755 --- a/ci/licenses_golden/licenses_flutter +++ b/ci/licenses_golden/licenses_flutter @@ -1365,6 +1365,8 @@ FILE: ../../../flutter/shell/platform/embedder/embedder_surface_metal.h FILE: ../../../flutter/shell/platform/embedder/embedder_surface_metal.mm FILE: ../../../flutter/shell/platform/embedder/embedder_surface_software.cc FILE: ../../../flutter/shell/platform/embedder/embedder_surface_software.h +FILE: ../../../flutter/shell/platform/embedder/embedder_surface_vulkan.cc +FILE: ../../../flutter/shell/platform/embedder/embedder_surface_vulkan.h FILE: ../../../flutter/shell/platform/embedder/embedder_task_runner.cc FILE: ../../../flutter/shell/platform/embedder/embedder_task_runner.h FILE: ../../../flutter/shell/platform/embedder/embedder_thread_host.cc @@ -1387,6 +1389,8 @@ FILE: ../../../flutter/shell/platform/embedder/fixtures/scene_without_custom_com FILE: ../../../flutter/shell/platform/embedder/fixtures/snapshot_large_scene.png FILE: ../../../flutter/shell/platform/embedder/fixtures/verifyb143464703.png FILE: ../../../flutter/shell/platform/embedder/fixtures/verifyb143464703_soft_noxform.png +FILE: ../../../flutter/shell/platform/embedder/fixtures/vk_dpr_noxform.png +FILE: ../../../flutter/shell/platform/embedder/fixtures/vk_gradient.png FILE: ../../../flutter/shell/platform/embedder/platform_view_embedder.cc FILE: ../../../flutter/shell/platform/embedder/platform_view_embedder.h FILE: ../../../flutter/shell/platform/embedder/test_utils/key_codes.h diff --git a/shell/gpu/BUILD.gn b/shell/gpu/BUILD.gn index 9687b94fbb2b2..ec1d377d963f7 100644 --- a/shell/gpu/BUILD.gn +++ b/shell/gpu/BUILD.gn @@ -43,8 +43,9 @@ source_set("gpu_surface_vulkan") { "gpu_surface_vulkan_delegate.cc", "gpu_surface_vulkan_delegate.h", ] - - deps = gpu_common_deps + [ "//flutter/vulkan" ] + deps = [ "//flutter/shell/platform/embedder:embedder_headers" ] + deps += gpu_common_deps + public_deps = [ "//flutter/vulkan" ] } source_set("gpu_surface_metal") { diff --git a/shell/gpu/gpu_surface_metal_delegate.h b/shell/gpu/gpu_surface_metal_delegate.h index 56112042fbc1d..cb3900e392c1f 100644 --- a/shell/gpu/gpu_surface_metal_delegate.h +++ b/shell/gpu/gpu_surface_metal_delegate.h @@ -75,14 +75,14 @@ class GPUSurfaceMetalDelegate { //------------------------------------------------------------------------------ /// @brief Returns the handle to the MTLTexture to render to. This is only - /// called when the specefied render target type is `kMTLTexture`. + /// called when the specified render target type is `kMTLTexture`. /// virtual GPUMTLTextureInfo GetMTLTexture(const SkISize& frame_info) const = 0; //------------------------------------------------------------------------------ /// @brief Presents the texture with `texture_id` to the "screen". /// `texture_id` corresponds to a texture that has been obtained by an earlier - /// call to `GetMTLTexture`. This is only called when the specefied render + /// call to `GetMTLTexture`. This is only called when the specified render /// target type is `kMTLTexture`. /// /// @see |GPUSurfaceMetalDelegate::GetMTLTexture| diff --git a/shell/gpu/gpu_surface_vulkan.cc b/shell/gpu/gpu_surface_vulkan.cc index 570783bffa13d..6040f459cc012 100644 --- a/shell/gpu/gpu_surface_vulkan.cc +++ b/shell/gpu/gpu_surface_vulkan.cc @@ -5,68 +5,77 @@ #include "flutter/shell/gpu/gpu_surface_vulkan.h" #include "flutter/fml/logging.h" +#include "fml/trace_event.h" +#include "include/core/SkSize.h" +#include "third_party/swiftshader/include/vulkan/vulkan_core.h" namespace flutter { -GPUSurfaceVulkan::GPUSurfaceVulkan( - GPUSurfaceVulkanDelegate* delegate, - std::unique_ptr native_surface, - bool render_to_surface) - : GPUSurfaceVulkan(/*context=*/nullptr, - delegate, - std::move(native_surface), - render_to_surface) {} - -GPUSurfaceVulkan::GPUSurfaceVulkan( - const sk_sp& context, - GPUSurfaceVulkanDelegate* delegate, - std::unique_ptr native_surface, - bool render_to_surface) - : window_(context, - delegate->vk(), - std::move(native_surface), - render_to_surface), +GPUSurfaceVulkan::GPUSurfaceVulkan(GPUSurfaceVulkanDelegate* delegate, + const sk_sp& skia_context, + bool render_to_surface) + : delegate_(delegate), + skia_context_(skia_context), render_to_surface_(render_to_surface), weak_factory_(this) {} GPUSurfaceVulkan::~GPUSurfaceVulkan() = default; bool GPUSurfaceVulkan::IsValid() { - return window_.IsValid(); + return skia_context_ != nullptr; } std::unique_ptr GPUSurfaceVulkan::AcquireFrame( - const SkISize& size) { - SurfaceFrame::FramebufferInfo framebuffer_info; - framebuffer_info.supports_readback = true; + const SkISize& frame_size) { + if (!IsValid()) { + FML_LOG(ERROR) << "Vulkan surface was invalid."; + return nullptr; + } + + if (frame_size.isEmpty()) { + FML_LOG(ERROR) << "Vulkan surface was asked for an empty frame."; + return nullptr; + } - // TODO(38466): Refactor GPU surface APIs take into account the fact that an - // external view embedder may want to render to the root surface. if (!render_to_surface_) { return std::make_unique( - nullptr, std::move(framebuffer_info), + nullptr, SurfaceFrame::FramebufferInfo(), [](const SurfaceFrame& surface_frame, SkCanvas* canvas) { return true; }); } - auto surface = window_.AcquireSurface(); + FlutterVulkanImage image = delegate_->AcquireImage(frame_size); + if (!image.image) { + FML_LOG(ERROR) << "Invalid VkImage given by the embedder."; + return nullptr; + } - if (surface == nullptr) { + sk_sp surface = CreateSurfaceFromVulkanImage( + reinterpret_cast(image.image), + static_cast(image.format), frame_size); + if (!surface) { + FML_LOG(ERROR) << "Could not create the SkSurface from the Vulkan image."; return nullptr; } - SurfaceFrame::SubmitCallback callback = - [weak_this = weak_factory_.GetWeakPtr()](const SurfaceFrame&, - SkCanvas* canvas) -> bool { - // Frames are only ever acquired on the raster thread. This is also the - // thread on which the weak pointer factory is collected (as this instance - // is owned by the rasterizer). So this use of weak pointers is safe. - if (canvas == nullptr || !weak_this) { + SurfaceFrame::SubmitCallback callback = [image = image, delegate = delegate_]( + const SurfaceFrame&, + SkCanvas* canvas) -> bool { + TRACE_EVENT0("flutter", "GPUSurfaceVulkan::PresentImage"); + if (canvas == nullptr) { + FML_DLOG(ERROR) << "Canvas not available."; return false; } - return weak_this->window_.SwapBuffers(); + + canvas->flush(); + + return delegate->PresentImage(reinterpret_cast(image.image), + static_cast(image.format)); }; + + SurfaceFrame::FramebufferInfo framebuffer_info{.supports_readback = true}; + return std::make_unique( std::move(surface), std::move(framebuffer_info), std::move(callback)); } @@ -80,7 +89,54 @@ SkMatrix GPUSurfaceVulkan::GetRootTransformation() const { } GrDirectContext* GPUSurfaceVulkan::GetContext() { - return window_.GetSkiaGrContext(); + return skia_context_.get(); +} + +sk_sp GPUSurfaceVulkan::CreateSurfaceFromVulkanImage( + const VkImage image, + const VkFormat format, + const SkISize& size) { + GrVkImageInfo image_info = { + .fImage = image, + .fImageTiling = VK_IMAGE_TILING_OPTIMAL, + .fImageLayout = VK_IMAGE_LAYOUT_UNDEFINED, + .fFormat = format, + .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(size.width(), // + size.height(), // + image_info // + ); + + SkSurfaceProps surface_properties(0, kUnknown_SkPixelGeometry); + + return SkSurface::MakeFromBackendTexture( + skia_context_.get(), // context + backend_texture, // back-end texture + kTopLeft_GrSurfaceOrigin, // surface origin + 1, // sample count + ColorTypeFromFormat(format), // color type + SkColorSpace::MakeSRGB(), // color space + &surface_properties // surface properties + ); +} + +SkColorType GPUSurfaceVulkan::ColorTypeFromFormat(const VkFormat format) { + switch (format) { + case VK_FORMAT_R8G8B8A8_UNORM: + case VK_FORMAT_R8G8B8A8_SRGB: + return SkColorType::kRGBA_8888_SkColorType; + case VK_FORMAT_B8G8R8A8_UNORM: + case VK_FORMAT_B8G8R8A8_SRGB: + return SkColorType::kBGRA_8888_SkColorType; + default: + return SkColorType::kUnknown_SkColorType; + } } } // namespace flutter diff --git a/shell/gpu/gpu_surface_vulkan.h b/shell/gpu/gpu_surface_vulkan.h index 50c420e38790b..b281ac8c0f160 100644 --- a/shell/gpu/gpu_surface_vulkan.h +++ b/shell/gpu/gpu_surface_vulkan.h @@ -11,29 +11,25 @@ #include "flutter/fml/macros.h" #include "flutter/fml/memory/weak_ptr.h" #include "flutter/shell/gpu/gpu_surface_vulkan_delegate.h" +#include "flutter/vulkan/vulkan_backbuffer.h" #include "flutter/vulkan/vulkan_native_surface.h" #include "flutter/vulkan/vulkan_window.h" #include "include/core/SkRefCnt.h" namespace flutter { +//------------------------------------------------------------------------------ +/// @brief A GPU surface backed by VkImages provided by a +/// GPUSurfaceVulkanDelegate. +/// class GPUSurfaceVulkan : public Surface { public: - //------------------------------------------------------------------------------ - /// @brief Create a GPUSurfaceVulkan which implicitly creates its own - /// GrDirectContext for Skia. - /// - GPUSurfaceVulkan(GPUSurfaceVulkanDelegate* delegate, - std::unique_ptr native_surface, - bool render_to_surface); - //------------------------------------------------------------------------------ /// @brief Create a GPUSurfaceVulkan while letting it reuse an existing /// GrDirectContext. /// - GPUSurfaceVulkan(const sk_sp& context, - GPUSurfaceVulkanDelegate* delegate, - std::unique_ptr native_surface, + GPUSurfaceVulkan(GPUSurfaceVulkanDelegate* delegate, + const sk_sp& context, bool render_to_surface); ~GPUSurfaceVulkan() override; @@ -50,11 +46,19 @@ class GPUSurfaceVulkan : public Surface { // |Surface| GrDirectContext* GetContext() override; + static SkColorType ColorTypeFromFormat(const VkFormat format); + private: - vulkan::VulkanWindow window_; - const bool render_to_surface_; + GPUSurfaceVulkanDelegate* delegate_; + sk_sp skia_context_; + bool render_to_surface_; fml::WeakPtrFactory weak_factory_; + + sk_sp CreateSurfaceFromVulkanImage(const VkImage image, + const VkFormat format, + const SkISize& size); + FML_DISALLOW_COPY_AND_ASSIGN(GPUSurfaceVulkan); }; diff --git a/shell/gpu/gpu_surface_vulkan_delegate.h b/shell/gpu/gpu_surface_vulkan_delegate.h index 9b1a495959562..023fe9261900b 100644 --- a/shell/gpu/gpu_surface_vulkan_delegate.h +++ b/shell/gpu/gpu_surface_vulkan_delegate.h @@ -6,16 +6,43 @@ #define FLUTTER_SHELL_GPU_GPU_SURFACE_VULKAN_DELEGATE_H_ #include "flutter/fml/memory/ref_ptr.h" +#include "flutter/shell/platform/embedder/embedder.h" +#include "flutter/vulkan/vulkan_device.h" +#include "flutter/vulkan/vulkan_image.h" #include "flutter/vulkan/vulkan_proc_table.h" +#include "third_party/skia/include/core/SkSize.h" namespace flutter { +//------------------------------------------------------------------------------ +/// @brief Interface implemented by all platform surfaces that can present +/// a Vulkan backing store to the "screen". The GPU surface +/// abstraction (which abstracts the client rendering API) uses this +/// delegation pattern to tell the platform surface (which abstracts +/// how backing stores fulfilled by the selected client rendering +/// API end up on the "screen" on a particular platform) when the +/// rasterizer needs to allocate and present the Vulkan backing +/// store. +/// +/// @see |EmbedderSurfaceVulkan|. +/// class GPUSurfaceVulkanDelegate { public: - ~GPUSurfaceVulkanDelegate(); + virtual ~GPUSurfaceVulkanDelegate(); - // Obtain a reference to the Vulkan implementation's proc table. - virtual fml::RefPtr vk() = 0; + /// @brief Obtain a reference to the Vulkan implementation's proc table. + /// + virtual const vulkan::VulkanProcTable& vk() = 0; + + /// @brief Called by the engine to fetch a VkImage for writing the next + /// frame. + /// + virtual FlutterVulkanImage AcquireImage(const SkISize& size) = 0; + + /// @brief Called by the engine once a frame has been rendered to the image + /// and it's ready to be bound for further reading/writing. + /// + virtual bool PresentImage(VkImage image, VkFormat format) = 0; }; } // namespace flutter diff --git a/shell/platform/embedder/BUILD.gn b/shell/platform/embedder/BUILD.gn index 622432d2c11b1..1e6ac775d5e82 100644 --- a/shell/platform/embedder/BUILD.gn +++ b/shell/platform/embedder/BUILD.gn @@ -109,6 +109,13 @@ template("embedder_source_set") { deps += [ "//flutter/shell/platform/darwin/graphics" ] } + if (embedder_enable_vulkan) { + sources += [ + "embedder_surface_vulkan.cc", + "embedder_surface_vulkan.h", + ] + } + public_deps = [ ":embedder_headers" ] public_configs += [ @@ -168,6 +175,8 @@ test_fixtures("fixtures") { "fixtures/dpr_noxform.png", "fixtures/dpr_xform.png", "fixtures/gradient.png", + "fixtures/vk_dpr_noxform.png", + "fixtures/vk_gradient.png", "fixtures/gradient_metal.png", "fixtures/external_texture_metal.png", "fixtures/gradient_xform.png", @@ -250,6 +259,13 @@ if (enable_unittests) { } if (test_enable_vulkan) { + sources += [ + "tests/embedder_test_compositor_vulkan.cc", + "tests/embedder_test_compositor_vulkan.h", + "tests/embedder_test_context_vulkan.cc", + "tests/embedder_test_context_vulkan.h", + ] + deps += [ "//flutter/testing:vulkan", "//flutter/vulkan", diff --git a/shell/platform/embedder/embedder.cc b/shell/platform/embedder/embedder.cc index 838c4c496590a..0f826a570104f 100644 --- a/shell/platform/embedder/embedder.cc +++ b/shell/platform/embedder/embedder.cc @@ -164,6 +164,26 @@ static bool IsMetalRendererConfigValid(const FlutterRendererConfig* config) { return device && command_queue && present && get_texture; } +static bool IsVulkanRendererConfigValid(const FlutterRendererConfig* config) { + if (config->type != kVulkan) { + return false; + } + + const FlutterVulkanRendererConfig* vulkan_config = &config->vulkan; + + if (!SAFE_EXISTS(vulkan_config, instance) || + !SAFE_EXISTS(vulkan_config, physical_device) || + !SAFE_EXISTS(vulkan_config, device) || + !SAFE_EXISTS(vulkan_config, queue) || + !SAFE_EXISTS(vulkan_config, get_instance_proc_address_callback) || + !SAFE_EXISTS(vulkan_config, get_next_image_callback) || + !SAFE_EXISTS(vulkan_config, present_image_callback)) { + return false; + } + + return true; +} + static bool IsRendererValid(const FlutterRendererConfig* config) { if (config == nullptr) { return false; @@ -176,6 +196,8 @@ static bool IsRendererValid(const FlutterRendererConfig* config) { return IsSoftwareRendererConfigValid(config); case kMetal: return IsMetalRendererConfigValid(config); + case kVulkan: + return IsVulkanRendererConfigValid(config); default: return false; } @@ -388,6 +410,89 @@ InferMetalPlatformViewCreationCallback( #endif } +static flutter::Shell::CreateCallback +InferVulkanPlatformViewCreationCallback( + const FlutterRendererConfig* config, + void* user_data, + flutter::PlatformViewEmbedder::PlatformDispatchTable + platform_dispatch_table, + std::unique_ptr + external_view_embedder) { + if (config->type != kVulkan) { + return nullptr; + } + +#ifdef SHELL_ENABLE_VULKAN + std::function + vulkan_get_instance_proc_address = + [ptr = config->vulkan.get_instance_proc_address_callback, user_data]( + VkInstance instance, const char* proc_name) -> void* { + return ptr(user_data, instance, proc_name); + }; + + auto vulkan_get_next_image = + [ptr = config->vulkan.get_next_image_callback, + user_data](const SkISize& frame_size) -> FlutterVulkanImage { + FlutterFrameInfo frame_info = { + .struct_size = sizeof(FlutterFrameInfo), + .size = {static_cast(frame_size.width()), + static_cast(frame_size.height())}, + }; + + return ptr(user_data, &frame_info); + }; + + auto vulkan_present_image_callback = + [ptr = config->vulkan.present_image_callback, user_data]( + VkImage image, VkFormat format) -> bool { + FlutterVulkanImage image_desc = { + .struct_size = sizeof(FlutterVulkanImage), + .image = reinterpret_cast(image), + .format = static_cast(format), + }; + return ptr(user_data, &image_desc); + }; + + flutter::EmbedderSurfaceVulkan::VulkanDispatchTable vulkan_dispatch_table = { + .get_instance_proc_address = vulkan_get_instance_proc_address, + .get_next_image = vulkan_get_next_image, + .present_image = vulkan_present_image_callback, + }; + + std::shared_ptr view_embedder = + std::move(external_view_embedder); + + std::unique_ptr embedder_surface = + std::make_unique( + config->vulkan.version, + static_cast(config->vulkan.instance), + config->vulkan.enabled_instance_extension_count, + config->vulkan.enabled_instance_extensions, + config->vulkan.enabled_device_extension_count, + config->vulkan.enabled_device_extensions, + static_cast(config->vulkan.physical_device), + static_cast(config->vulkan.device), + config->vulkan.queue_family_index, + static_cast(config->vulkan.queue), vulkan_dispatch_table, + view_embedder); + + return fml::MakeCopyable( + [embedder_surface = std::move(embedder_surface), platform_dispatch_table, + external_view_embedder = + std::move(view_embedder)](flutter::Shell& shell) mutable { + return std::make_unique( + shell, // delegate + shell.GetTaskRunners(), // task runners + std::move(embedder_surface), // embedder surface + platform_dispatch_table, // platform dispatch table + std::move(external_view_embedder) // external view embedder + ); + }); +#else + return nullptr; +#endif +} + static flutter::Shell::CreateCallback InferSoftwarePlatformViewCreationCallback( const FlutterRendererConfig* config, @@ -450,6 +555,10 @@ InferPlatformViewCreationCallback( return InferMetalPlatformViewCreationCallback( config, user_data, platform_dispatch_table, std::move(external_view_embedder)); + case kVulkan: + return InferVulkanPlatformViewCreationCallback( + config, user_data, platform_dispatch_table, + std::move(external_view_embedder)); default: return nullptr; } @@ -628,6 +737,59 @@ static sk_sp MakeSkSurfaceFromBackingStore( #endif } +static sk_sp MakeSkSurfaceFromBackingStore( + GrDirectContext* context, + const FlutterBackingStoreConfig& config, + const FlutterVulkanBackingStore* vulkan) { +#ifdef SHELL_ENABLE_VULKAN + if (!vulkan->image) { + FML_LOG(ERROR) << "Embedder supplied null Vulkan image."; + return nullptr; + } + GrVkImageInfo image_info = { + .fImage = reinterpret_cast(vulkan->image->image), + .fImageTiling = VK_IMAGE_TILING_OPTIMAL, + .fImageLayout = VK_IMAGE_LAYOUT_UNDEFINED, + .fFormat = static_cast(vulkan->image->format), + .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(config.size.width, // + config.size.height, // + image_info // + ); + + SkSurfaceProps surface_properties(0, kUnknown_SkPixelGeometry); + + auto surface = SkSurface::MakeFromBackendTexture( + context, // context + backend_texture, // back-end texture + kTopLeft_GrSurfaceOrigin, // surface origin + 1, // sample count + flutter::GPUSurfaceVulkan::ColorTypeFromFormat( + static_cast(vulkan->image->format)), // color type + SkColorSpace::MakeSRGB(), // color space + &surface_properties, // surface properties + static_cast( + vulkan->destruction_callback), // release proc + vulkan->user_data // release context + ); + + if (!surface) { + FML_LOG(ERROR) << "Could not wrap embedder supplied Vulkan render texture."; + return nullptr; + } + + return surface; +#else + return nullptr; +#endif +} + static std::unique_ptr CreateEmbedderRenderTarget(const FlutterCompositor* compositor, const FlutterBackingStoreConfig& config, @@ -689,6 +851,11 @@ CreateEmbedderRenderTarget(const FlutterCompositor* compositor, render_surface = MakeSkSurfaceFromBackingStore(context, config, &backing_store.metal); break; + + case kFlutterBackingStoreTypeVulkan: + render_surface = + MakeSkSurfaceFromBackingStore(context, config, &backing_store.vulkan); + break; }; if (!render_surface) { diff --git a/shell/platform/embedder/embedder.h b/shell/platform/embedder/embedder.h index e8b769ef4cbef..a8a2592c624bc 100644 --- a/shell/platform/embedder/embedder.h +++ b/shell/platform/embedder/embedder.h @@ -76,6 +76,7 @@ typedef enum { /// iOS version >= 10.0 (device), 13.0 (simulator) /// macOS version >= 10.14 kMetal, + kVulkan, } FlutterRendererType; /// Additional accessibility features that may be enabled by the platform. @@ -494,7 +495,7 @@ typedef struct { size_t struct_size; /// Embedder provided unique identifier to the texture buffer. Given that the /// `texture` handle is passed to the engine to render to, the texture buffer - /// is itseld owned by the embedder. This `texture_id` is then also given to + /// is itself owned by the embedder. This `texture_id` is then also given to /// the embedder in the present callback. int64_t texture_id; /// Handle to the MTLTexture that is owned by the embedder. Engine will render @@ -541,6 +542,104 @@ typedef struct { FlutterMetalTextureFrameCallback external_texture_frame_callback; } FlutterMetalRendererConfig; +/// Alias for VkInstance. +typedef void* FlutterVulkanInstanceHandle; + +/// Alias for VkPhysicalDevice. +typedef void* FlutterVulkanPhysicalDeviceHandle; + +/// Alias for VkDevice. +typedef void* FlutterVulkanDeviceHandle; + +/// Alias for VkQueue. +typedef void* FlutterVulkanQueueHandle; + +/// Alias for VkImage. +typedef uint64_t FlutterVulkanImageHandle; + +typedef struct { + /// The size of this struct. Must be sizeof(FlutterVulkanImage). + size_t struct_size; + /// Handle to the VkImage that is owned by the embedder. The engine will + /// bind this image for writing the frame. + FlutterVulkanImageHandle image; + /// The VkFormat of the image (for example: VK_FORMAT_R8G8B8A8_UNORM). + uint32_t format; +} FlutterVulkanImage; + +/// Callback to fetch a Vulkan function pointer for a given instance. Normally, +/// this should return the results of vkGetInstanceProcAddr. +typedef void* (*FlutterVulkanInstanceProcAddressCallback)( + void* /* user data */, + FlutterVulkanInstanceHandle /* instance */, + const char* /* name */); + +/// Callback for when a VkImage is requested. +typedef FlutterVulkanImage (*FlutterVulkanImageCallback)( + void* /* user data */, + const FlutterFrameInfo* /* frame info */); + +/// Callback for when a VkImage has been written to and is ready for use by the +/// embedder. +typedef bool (*FlutterVulkanPresentCallback)( + void* /* user data */, + const FlutterVulkanImage* /* image */); + +typedef struct { + /// The size of this struct. Must be sizeof(FlutterVulkanRendererConfig). + size_t struct_size; + + /// The Vulkan API version. This should match the value set in + /// VkApplicationInfo::apiVersion when the VkInstance was created. + uint32_t version; + /// VkInstance handle. Must not be destroyed before `FlutterEngineShutdown` is + /// called. + FlutterVulkanInstanceHandle instance; + /// VkPhysicalDevice handle. + FlutterVulkanPhysicalDeviceHandle physical_device; + /// VkDevice handle. Must not be destroyed before `FlutterEngineShutdown` is + /// called. + FlutterVulkanDeviceHandle device; + /// The queue family index of the VkQueue supplied in the next field. + uint32_t queue_family_index; + /// VkQueue handle. + FlutterVulkanQueueHandle queue; + /// The number of instance extensions available for enumerating in the next + /// field. + size_t enabled_instance_extension_count; + /// Array of enabled instance extension names. This should match the names + /// passed to `VkInstanceCreateInfo.ppEnabledExtensionNames` when the instance + /// was created, but any subset of enabled instance extensions may be + /// specified. + /// This field is optional; `nullptr` may be specified. + /// This memory is only accessed during the call to FlutterEngineInitialize. + const char** enabled_instance_extensions; + /// The number of device extensions available for enumerating in the next + /// field. + size_t enabled_device_extension_count; + /// Array of enabled logical device extension names. This should match the + /// names passed to `VkDeviceCreateInfo.ppEnabledExtensionNames` when the + /// logical device was created, but any subset of enabled logical device + /// extensions may be specified. + /// This field is optional; `nullptr` may be specified. + /// This memory is only accessed during the call to FlutterEngineInitialize. + /// For example: VK_KHR_GET_MEMORY_REQUIREMENTS_2_EXTENSION_NAME + const char** enabled_device_extensions; + /// The callback invoked when resolving Vulkan function pointers. + FlutterVulkanInstanceProcAddressCallback get_instance_proc_address_callback; + /// The callback invoked when the engine requests a VkImage from the embedder + /// for rendering the next frame. + /// Not used if a FlutterCompositor is supplied in FlutterProjectArgs. + FlutterVulkanImageCallback get_next_image_callback; + /// The callback invoked when a VkImage has been written to and is ready for + /// use by the embedder. Prior to calling this callback, the engine performs + /// a host sync, and so the VkImage can be used in a pipeline by the embedder + /// without any additional synchronization. + /// Not used if a FlutterCompositor is supplied in FlutterProjectArgs. + FlutterVulkanPresentCallback present_image_callback; + +} FlutterVulkanRendererConfig; + typedef struct { /// The size of this struct. Must be sizeof(FlutterSoftwareRendererConfig). size_t struct_size; @@ -557,6 +656,7 @@ typedef struct { FlutterOpenGLRendererConfig open_gl; FlutterSoftwareRendererConfig software; FlutterMetalRendererConfig metal; + FlutterVulkanRendererConfig vulkan; }; } FlutterRendererConfig; @@ -989,6 +1089,25 @@ typedef struct { }; } FlutterMetalBackingStore; +typedef struct { + /// The size of this struct. Must be sizeof(FlutterVulkanBackingStore). + size_t struct_size; + /// The image that the layer will be rendered to. This image must already be + /// available for the engine to bind for writing when it's given to the engine + /// via the backing store creation callback. The engine will perform a host + /// sync for all layers prior to calling the compositor present callback, and + /// so the written layer images can be freely bound by the embedder without + /// any additional synchronization. + const FlutterVulkanImage* image; + /// A baton that is not interpreted by the engine in any way. It will be given + /// back to the embedder in the destruction callback below. Embedder resources + /// may be associated with this baton. + void* user_data; + /// The callback invoked by the engine when it no longer needs this backing + /// store. + VoidCallback destruction_callback; +} FlutterVulkanBackingStore; + typedef enum { /// Indicates that the Flutter application requested that an opacity be /// applied to the platform view. @@ -1048,6 +1167,8 @@ typedef enum { kFlutterBackingStoreTypeSoftware, /// Specifies a Metal backing store. This is backed by a Metal texture. kFlutterBackingStoreTypeMetal, + /// Specifies a Vulkan backing store. This is backed by a Vulkan VkImage. + kFlutterBackingStoreTypeVulkan, } FlutterBackingStoreType; typedef struct { @@ -1069,6 +1190,8 @@ typedef struct { FlutterSoftwareBackingStore software; // The description of the Metal backing store. FlutterMetalBackingStore metal; + // The description of the Vulkan backing store. + FlutterVulkanBackingStore vulkan; }; } FlutterBackingStore; diff --git a/shell/platform/embedder/embedder_surface_vulkan.cc b/shell/platform/embedder/embedder_surface_vulkan.cc new file mode 100644 index 0000000000000..b3918a2420070 --- /dev/null +++ b/shell/platform/embedder/embedder_surface_vulkan.cc @@ -0,0 +1,155 @@ +// 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 "flutter/shell/platform/embedder/embedder_surface_vulkan.h" + +#include "flutter/shell/common/shell_io_manager.h" +#include "include/gpu/GrDirectContext.h" +#include "include/gpu/vk/GrVkBackendContext.h" +#include "include/gpu/vk/GrVkExtensions.h" +#include "shell/gpu/gpu_surface_vulkan.h" +#include "shell/gpu/gpu_surface_vulkan_delegate.h" + +namespace flutter { + +EmbedderSurfaceVulkan::EmbedderSurfaceVulkan( + uint32_t version, + VkInstance instance, + size_t instance_extension_count, + const char** instance_extensions, + size_t device_extension_count, + const char** device_extensions, + VkPhysicalDevice physical_device, + VkDevice device, + uint32_t queue_family_index, + VkQueue queue, + VulkanDispatchTable vulkan_dispatch_table, + std::shared_ptr external_view_embedder) + : vk_(fml::MakeRefCounted( + vulkan_dispatch_table.get_instance_proc_address)), + device_(*vk_, + vulkan::VulkanHandle{physical_device}, + vulkan::VulkanHandle{device}, + queue_family_index, + vulkan::VulkanHandle{queue}), + vulkan_dispatch_table_(vulkan_dispatch_table), + external_view_embedder_(external_view_embedder) { + // Make sure all required members of the dispatch table are checked. + if (!vulkan_dispatch_table_.get_instance_proc_address || + !vulkan_dispatch_table_.get_next_image || + !vulkan_dispatch_table_.present_image) { + return; + } + + vk_->SetupInstanceProcAddresses(vulkan::VulkanHandle{instance}); + vk_->SetupDeviceProcAddresses(vulkan::VulkanHandle{device}); + if (!vk_->IsValid()) { + FML_LOG(ERROR) << "VulkanProcTable invalid."; + return; + } + + main_context_ = CreateGrContext(instance, version, instance_extension_count, + instance_extensions, device_extension_count, + device_extensions, ContextType::kRender); + // TODO(96954): Add a second (optional) queue+family index to the Embedder API + // to allow embedders to specify a dedicated transfer queue for + // use by the resource context. Queue families with graphics + // capability can always be used for memory transferring, but it + // would be advantageous to use a dedicated transter queue here. + resource_context_ = CreateGrContext( + instance, version, instance_extension_count, instance_extensions, + device_extension_count, device_extensions, ContextType::kResource); + + valid_ = main_context_ && resource_context_; +} + +EmbedderSurfaceVulkan::~EmbedderSurfaceVulkan() { + if (main_context_) { + main_context_->releaseResourcesAndAbandonContext(); + } + if (resource_context_) { + resource_context_->releaseResourcesAndAbandonContext(); + } +} + +// |GPUSurfaceVulkanDelegate| +const vulkan::VulkanProcTable& EmbedderSurfaceVulkan::vk() { + return *vk_; +} + +// |GPUSurfaceVulkanDelegate| +FlutterVulkanImage EmbedderSurfaceVulkan::AcquireImage(const SkISize& size) { + return vulkan_dispatch_table_.get_next_image(size); +} + +// |GPUSurfaceVulkanDelegate| +bool EmbedderSurfaceVulkan::PresentImage(VkImage image, VkFormat format) { + return vulkan_dispatch_table_.present_image(image, format); +} + +// |EmbedderSurface| +bool EmbedderSurfaceVulkan::IsValid() const { + return valid_; +} + +// |EmbedderSurface| +std::unique_ptr EmbedderSurfaceVulkan::CreateGPUSurface() { + const bool render_to_surface = !external_view_embedder_; + return std::make_unique(this, main_context_, + render_to_surface); +} + +// |EmbedderSurface| +sk_sp EmbedderSurfaceVulkan::CreateResourceContext() const { + return resource_context_; +} + +sk_sp EmbedderSurfaceVulkan::CreateGrContext( + VkInstance instance, + uint32_t version, + size_t instance_extension_count, + const char** instance_extensions, + size_t device_extension_count, + const char** device_extensions, + ContextType context_type) const { + uint32_t skia_features = 0; + if (!device_.GetPhysicalDeviceFeaturesSkia(&skia_features)) { + FML_LOG(ERROR) << "Failed to get physical device features."; + + return nullptr; + } + + auto get_proc = vk_->CreateSkiaGetProc(); + if (get_proc == nullptr) { + FML_LOG(ERROR) << "Failed to create Vulkan getProc for Skia."; + return nullptr; + } + + GrVkExtensions extensions; + + GrVkBackendContext backend_context = {}; + backend_context.fInstance = instance; + backend_context.fPhysicalDevice = device_.GetPhysicalDeviceHandle(); + backend_context.fDevice = device_.GetHandle(); + backend_context.fQueue = device_.GetQueueHandle(); + backend_context.fGraphicsQueueIndex = device_.GetGraphicsQueueIndex(); + backend_context.fMinAPIVersion = version; + backend_context.fMaxAPIVersion = version; + backend_context.fFeatures = skia_features; + backend_context.fVkExtensions = &extensions; + backend_context.fGetProc = get_proc; + backend_context.fOwnsInstanceAndDevice = false; + + extensions.init(backend_context.fGetProc, backend_context.fInstance, + backend_context.fPhysicalDevice, instance_extension_count, + instance_extensions, device_extension_count, + device_extensions); + + GrContextOptions options = + MakeDefaultContextOptions(context_type, GrBackendApi::kVulkan); + options.fReduceOpsTaskSplitting = GrContextOptions::Enable::kNo; + return GrDirectContext::MakeVulkan(backend_context, options); +} + +} // namespace flutter diff --git a/shell/platform/embedder/embedder_surface_vulkan.h b/shell/platform/embedder/embedder_surface_vulkan.h new file mode 100644 index 0000000000000..3fa28fa09a7bd --- /dev/null +++ b/shell/platform/embedder/embedder_surface_vulkan.h @@ -0,0 +1,89 @@ +// 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. + +#ifndef FLUTTER_SHELL_PLATFORM_EMBEDDER_EMBEDDER_SURFACE_VULKAN_H_ +#define FLUTTER_SHELL_PLATFORM_EMBEDDER_EMBEDDER_SURFACE_VULKAN_H_ + +#include "flutter/fml/macros.h" +#include "flutter/shell/gpu/gpu_surface_vulkan.h" +#include "flutter/shell/platform/embedder/embedder_external_view_embedder.h" +#include "flutter/shell/platform/embedder/embedder_surface.h" +#include "shell/common/context_options.h" +#include "shell/gpu/gpu_surface_vulkan_delegate.h" +#include "shell/platform/embedder/embedder.h" +#include "vulkan/vulkan_proc_table.h" + +namespace flutter { + +class EmbedderSurfaceVulkan final : public EmbedderSurface, + public GPUSurfaceVulkanDelegate { + public: + struct VulkanDispatchTable { + std::function + get_instance_proc_address; // required + std::function + get_next_image; // required + std::function + present_image; // required + }; + + EmbedderSurfaceVulkan( + uint32_t version, + VkInstance instance, + size_t instance_extension_count, + const char** instance_extensions, + size_t device_extension_count, + const char** device_extensions, + VkPhysicalDevice physical_device, + VkDevice device, + uint32_t queue_family_index, + VkQueue queue, + VulkanDispatchTable vulkan_dispatch_table, + std::shared_ptr external_view_embedder); + + ~EmbedderSurfaceVulkan() override; + + // |GPUSurfaceVulkanDelegate| + const vulkan::VulkanProcTable& vk() override; + + // |GPUSurfaceVulkanDelegate| + FlutterVulkanImage AcquireImage(const SkISize& size) override; + + // |GPUSurfaceVulkanDelegate| + bool PresentImage(VkImage image, VkFormat format) override; + + private: + bool valid_ = false; + fml::RefPtr vk_; + vulkan::VulkanDevice device_; + VulkanDispatchTable vulkan_dispatch_table_; + std::shared_ptr external_view_embedder_; + sk_sp main_context_; + sk_sp resource_context_; + + // |EmbedderSurface| + bool IsValid() const override; + + // |EmbedderSurface| + std::unique_ptr CreateGPUSurface() override; + + // |EmbedderSurface| + sk_sp CreateResourceContext() const override; + + sk_sp CreateGrContext(VkInstance instance, + uint32_t version, + size_t instance_extension_count, + const char** instance_extensions, + size_t device_extension_count, + const char** device_extensions, + ContextType context_type) const; + + void* GetInstanceProcAddress(VkInstance instance, const char* proc_name); + + FML_DISALLOW_COPY_AND_ASSIGN(EmbedderSurfaceVulkan); +}; + +} // namespace flutter + +#endif // FLUTTER_SHELL_PLATFORM_EMBEDDER_EMBEDDER_SURFACE_VULKAN_H_ diff --git a/shell/platform/embedder/fixtures/vk_dpr_noxform.png b/shell/platform/embedder/fixtures/vk_dpr_noxform.png new file mode 100644 index 0000000000000..7f5c28ba808fd Binary files /dev/null and b/shell/platform/embedder/fixtures/vk_dpr_noxform.png differ diff --git a/shell/platform/embedder/fixtures/vk_gradient.png b/shell/platform/embedder/fixtures/vk_gradient.png new file mode 100644 index 0000000000000..b00a8baf8d2e5 Binary files /dev/null and b/shell/platform/embedder/fixtures/vk_gradient.png differ diff --git a/shell/platform/embedder/platform_view_embedder.cc b/shell/platform/embedder/platform_view_embedder.cc index 2a2752446f5e2..a24e13e2636d0 100644 --- a/shell/platform/embedder/platform_view_embedder.cc +++ b/shell/platform/embedder/platform_view_embedder.cc @@ -49,6 +49,19 @@ PlatformViewEmbedder::PlatformViewEmbedder( platform_dispatch_table_(platform_dispatch_table) {} #endif +#ifdef SHELL_ENABLE_VULKAN +PlatformViewEmbedder::PlatformViewEmbedder( + PlatformView::Delegate& delegate, + flutter::TaskRunners task_runners, + std::unique_ptr embedder_surface, + PlatformDispatchTable platform_dispatch_table, + std::shared_ptr external_view_embedder) + : PlatformView(delegate, std::move(task_runners)), + external_view_embedder_(external_view_embedder), + embedder_surface_(std::move(embedder_surface)), + platform_dispatch_table_(platform_dispatch_table) {} +#endif + PlatformViewEmbedder::~PlatformViewEmbedder() = default; void PlatformViewEmbedder::UpdateSemantics( diff --git a/shell/platform/embedder/platform_view_embedder.h b/shell/platform/embedder/platform_view_embedder.h index 22f4d2dee6ea0..5c40ce1e16e79 100644 --- a/shell/platform/embedder/platform_view_embedder.h +++ b/shell/platform/embedder/platform_view_embedder.h @@ -23,6 +23,10 @@ #include "flutter/shell/platform/embedder/embedder_surface_metal.h" #endif +#ifdef SHELL_ENABLE_VULKAN +#include "flutter/shell/platform/embedder/embedder_surface_vulkan.h" +#endif + namespace flutter { class PlatformViewEmbedder final : public PlatformView { @@ -79,6 +83,16 @@ class PlatformViewEmbedder final : public PlatformView { std::shared_ptr external_view_embedder); #endif +#ifdef SHELL_ENABLE_VULKAN + // Creates a platform view that sets up an Vulkan rasterizer. + PlatformViewEmbedder( + PlatformView::Delegate& delegate, + flutter::TaskRunners task_runners, + std::unique_ptr embedder_surface, + PlatformDispatchTable platform_dispatch_table, + std::shared_ptr external_view_embedder); +#endif + ~PlatformViewEmbedder() override; // |PlatformView| diff --git a/shell/platform/embedder/tests/embedder_assertions.h b/shell/platform/embedder/tests/embedder_assertions.h index 423c1239f34c7..e631305852ac9 100644 --- a/shell/platform/embedder/tests/embedder_assertions.h +++ b/shell/platform/embedder/tests/embedder_assertions.h @@ -70,6 +70,16 @@ inline bool operator==(const FlutterMetalTexture& a, return a.texture_id == b.texture_id && a.texture == b.texture; } +inline bool operator==(const FlutterVulkanImage& a, + const FlutterVulkanImage& b) { + return a.image == b.image && a.format == b.format; +} + +inline bool operator==(const FlutterVulkanBackingStore& a, + const FlutterVulkanBackingStore& b) { + return a.image == b.image; +} + inline bool operator==(const FlutterMetalBackingStore& a, const FlutterMetalBackingStore& b) { return a.texture == b.texture; @@ -112,6 +122,8 @@ inline bool operator==(const FlutterBackingStore& a, return a.software == b.software; case kFlutterBackingStoreTypeMetal: return a.metal == b.metal; + case kFlutterBackingStoreTypeVulkan: + return a.vulkan == b.vulkan; } return false; @@ -230,6 +242,8 @@ inline std::string FlutterBackingStoreTypeToString( return "kFlutterBackingStoreTypeSoftware"; case kFlutterBackingStoreTypeMetal: return "kFlutterBackingStoreTypeMetal"; + case kFlutterBackingStoreTypeVulkan: + return "kFlutterBackingStoreTypeVulkan"; } return "Unknown"; } @@ -256,6 +270,13 @@ inline std::ostream& operator<<(std::ostream& out, << item.texture_id << std::dec << " Handle: 0x" << std::hex << item.texture; } + +inline std::ostream& operator<<(std::ostream& out, + const FlutterVulkanImage& item) { + return out << "(FlutterVulkanTexture) Image Handle: " << std::hex + << item.image << std::dec << " Format: " << item.format; +} + inline std::string FlutterPlatformViewMutationTypeToString( FlutterPlatformViewMutationType type) { switch (type) { @@ -347,6 +368,11 @@ inline std::ostream& operator<<(std::ostream& out, return out << "(FlutterMetalBackingStore) Texture: " << item.texture; } +inline std::ostream& operator<<(std::ostream& out, + const FlutterVulkanBackingStore& item) { + return out << "(FlutterVulkanBackingStore) Image: " << item.image; +} + inline std::ostream& operator<<(std::ostream& out, const FlutterBackingStore& backing_store) { out << "(FlutterBackingStore) Struct size: " << backing_store.struct_size @@ -366,6 +392,10 @@ inline std::ostream& operator<<(std::ostream& out, case kFlutterBackingStoreTypeMetal: out << backing_store.metal; break; + + case kFlutterBackingStoreTypeVulkan: + out << backing_store.vulkan; + break; } return out; diff --git a/shell/platform/embedder/tests/embedder_config_builder.cc b/shell/platform/embedder/tests/embedder_config_builder.cc index d04188e0d3070..ae63a3220f41f 100644 --- a/shell/platform/embedder/tests/embedder_config_builder.cc +++ b/shell/platform/embedder/tests/embedder_config_builder.cc @@ -6,13 +6,20 @@ #include "flutter/runtime/dart_vm.h" #include "flutter/shell/platform/embedder/embedder.h" +#include "tests/embedder_test_context.h" #include "third_party/skia/include/core/SkBitmap.h" +#include "third_party/swiftshader/include/vulkan/vulkan_core.h" #ifdef SHELL_ENABLE_GL #include "flutter/shell/platform/embedder/tests/embedder_test_compositor_gl.h" #include "flutter/shell/platform/embedder/tests/embedder_test_context_gl.h" #endif +#ifdef SHELL_ENABLE_VULKAN +#include "flutter/shell/platform/embedder/tests/embedder_test_context_vulkan.h" +#include "flutter/vulkan/vulkan_device.h" +#endif + #ifdef SHELL_ENABLE_METAL #include "flutter/shell/platform/embedder/tests/embedder_test_context_metal.h" #endif @@ -73,6 +80,10 @@ EmbedderConfigBuilder::EmbedderConfigBuilder( InitializeMetalRendererConfig(); #endif +#ifdef SHELL_ENABLE_VULKAN + InitializeVulkanRendererConfig(); +#endif + software_renderer_config_.struct_size = sizeof(FlutterSoftwareRendererConfig); software_renderer_config_.surface_present_callback = [](void* context, const void* allocation, size_t row_bytes, @@ -154,6 +165,24 @@ void EmbedderConfigBuilder::SetOpenGLPresentCallBack() { #endif } +void EmbedderConfigBuilder::SetRendererConfig(EmbedderTestContextType type, + SkISize surface_size) { + switch (type) { + case EmbedderTestContextType::kOpenGLContext: + SetOpenGLRendererConfig(surface_size); + break; + case EmbedderTestContextType::kMetalContext: + SetMetalRendererConfig(surface_size); + break; + case EmbedderTestContextType::kVulkanContext: + SetVulkanRendererConfig(surface_size); + break; + case EmbedderTestContextType::kSoftwareContext: + SetSoftwareRendererConfig(surface_size); + break; + } +} + void EmbedderConfigBuilder::SetOpenGLRendererConfig(SkISize surface_size) { #ifdef SHELL_ENABLE_GL renderer_config_.type = FlutterRendererType::kOpenGL; @@ -170,6 +199,14 @@ void EmbedderConfigBuilder::SetMetalRendererConfig(SkISize surface_size) { #endif } +void EmbedderConfigBuilder::SetVulkanRendererConfig(SkISize surface_size) { +#ifdef SHELL_ENABLE_VULKAN + renderer_config_.type = FlutterRendererType::kVulkan; + renderer_config_.vulkan = vulkan_renderer_config_; + context_.SetupSurface(surface_size); +#endif +} + void EmbedderConfigBuilder::SetAssetsPath() { project_args_.assets_path = context_.GetAssetsPath().c_str(); } @@ -428,5 +465,60 @@ void EmbedderConfigBuilder::InitializeMetalRendererConfig() { #endif // SHELL_ENABLE_METAL +#ifdef SHELL_ENABLE_VULKAN + +void EmbedderConfigBuilder::InitializeVulkanRendererConfig() { + if (context_.GetContextType() != EmbedderTestContextType::kVulkanContext) { + return; + } + + vulkan_renderer_config_.struct_size = sizeof(FlutterVulkanRendererConfig); + vulkan_renderer_config_.version = + static_cast(context_) + .vulkan_context_->application_->GetAPIVersion(); + vulkan_renderer_config_.instance = + static_cast(context_) + .vulkan_context_->application_->GetInstance(); + vulkan_renderer_config_.physical_device = + static_cast(context_) + .vulkan_context_->device_->GetPhysicalDeviceHandle(); + vulkan_renderer_config_.device = + static_cast(context_) + .vulkan_context_->device_->GetHandle(); + vulkan_renderer_config_.queue_family_index = + static_cast(context_) + .vulkan_context_->device_->GetGraphicsQueueIndex(); + vulkan_renderer_config_.queue = + static_cast(context_) + .vulkan_context_->device_->GetQueueHandle(); + vulkan_renderer_config_.get_instance_proc_address_callback = + [](void* context, FlutterVulkanInstanceHandle instance, + const char* name) -> void* { + return reinterpret_cast(context) + ->vulkan_context_->vk_->GetInstanceProcAddr( + reinterpret_cast(instance), name); + }; + vulkan_renderer_config_.get_next_image_callback = + [](void* context, + const FlutterFrameInfo* frame_info) -> FlutterVulkanImage { + VkImage image = + reinterpret_cast(context)->GetNextImage( + {static_cast(frame_info->size.width), + static_cast(frame_info->size.height)}); + return { + .struct_size = sizeof(FlutterVulkanImage), + .image = reinterpret_cast(image), + .format = VK_FORMAT_R8G8B8A8_UNORM, + }; + }; + vulkan_renderer_config_.present_image_callback = + [](void* context, const FlutterVulkanImage* image) -> bool { + return reinterpret_cast(context)->PresentImage( + reinterpret_cast(image->image)); + }; +} + +#endif + } // namespace testing } // namespace flutter diff --git a/shell/platform/embedder/tests/embedder_config_builder.h b/shell/platform/embedder/tests/embedder_config_builder.h index ee1fb514bb611..73256a36b3e01 100644 --- a/shell/platform/embedder/tests/embedder_config_builder.h +++ b/shell/platform/embedder/tests/embedder_config_builder.h @@ -45,12 +45,16 @@ class EmbedderConfigBuilder { FlutterProjectArgs& GetProjectArgs(); + void SetRendererConfig(EmbedderTestContextType type, SkISize surface_size); + void SetSoftwareRendererConfig(SkISize surface_size = SkISize::Make(1, 1)); void SetOpenGLRendererConfig(SkISize surface_size); void SetMetalRendererConfig(SkISize surface_size); + void SetVulkanRendererConfig(SkISize surface_size); + // Used to explicitly set an `open_gl.fbo_callback`. Using this method will // cause your test to fail since the ctor for this class sets // `open_gl.fbo_callback_with_frame_info`. This method exists as a utility to @@ -117,6 +121,10 @@ class EmbedderConfigBuilder { #ifdef SHELL_ENABLE_GL FlutterOpenGLRendererConfig opengl_renderer_config_ = {}; #endif +#ifdef SHELL_ENABLE_VULKAN + void InitializeVulkanRendererConfig(); + FlutterVulkanRendererConfig vulkan_renderer_config_ = {}; +#endif #ifdef SHELL_ENABLE_METAL void InitializeMetalRendererConfig(); FlutterMetalRendererConfig metal_renderer_config_ = {}; diff --git a/shell/platform/embedder/tests/embedder_test.cc b/shell/platform/embedder/tests/embedder_test.cc index b816ab5d22307..9a315dec3c415 100644 --- a/shell/platform/embedder/tests/embedder_test.cc +++ b/shell/platform/embedder/tests/embedder_test.cc @@ -4,6 +4,7 @@ #include "flutter/shell/platform/embedder/tests/embedder_test.h" #include "flutter/shell/platform/embedder/tests/embedder_test_context_software.h" +#include "flutter/shell/platform/embedder/tests/embedder_test_context_vulkan.h" #ifdef SHELL_ENABLE_GL #include "flutter/shell/platform/embedder/tests/embedder_test_context_gl.h" @@ -33,6 +34,12 @@ EmbedderTestContext& EmbedderTest::GetEmbedderContext( std::make_unique( GetFixturesDirectory()); break; +#ifdef SHELL_ENABLE_VULKAN + case EmbedderTestContextType::kVulkanContext: + embedder_contexts_[type] = + std::make_unique(GetFixturesDirectory()); + break; +#endif #ifdef SHELL_ENABLE_GL case EmbedderTestContextType::kOpenGLContext: embedder_contexts_[type] = diff --git a/shell/platform/embedder/tests/embedder_test.h b/shell/platform/embedder/tests/embedder_test.h index f00d802e0f23d..eb16637872872 100644 --- a/shell/platform/embedder/tests/embedder_test.h +++ b/shell/platform/embedder/tests/embedder_test.h @@ -12,6 +12,7 @@ #include "flutter/shell/platform/embedder/tests/embedder_test_context.h" #include "flutter/testing/testing.h" #include "flutter/testing/thread_test.h" +#include "gtest/gtest.h" namespace flutter { namespace testing { @@ -31,6 +32,10 @@ class EmbedderTest : public ThreadTest { FML_DISALLOW_COPY_AND_ASSIGN(EmbedderTest); }; +class EmbedderTestMultiBackend + : public EmbedderTest, + public ::testing::WithParamInterface {}; + } // namespace testing } // namespace flutter diff --git a/shell/platform/embedder/tests/embedder_test_backingstore_producer.cc b/shell/platform/embedder/tests/embedder_test_backingstore_producer.cc index 6d0c6b9d254d5..94fa852d10c44 100644 --- a/shell/platform/embedder/tests/embedder_test_backingstore_producer.cc +++ b/shell/platform/embedder/tests/embedder_test_backingstore_producer.cc @@ -5,8 +5,8 @@ #include "flutter/shell/platform/embedder/tests/embedder_test_backingstore_producer.h" #include "flutter/fml/logging.h" -#include "include/core/SkImageInfo.h" -#include "include/core/SkSize.h" +#include "third_party/skia/include/core/SkImageInfo.h" +#include "third_party/skia/include/core/SkSize.h" #include "third_party/skia/include/core/SkSurface.h" #include @@ -23,6 +23,10 @@ EmbedderTestBackingStoreProducer::EmbedderTestBackingStoreProducer( , test_metal_context_(std::make_unique()) #endif +#ifdef SHELL_ENABLE_VULKAN + , + test_vulkan_context_(nullptr) +#endif { } @@ -43,6 +47,10 @@ bool EmbedderTestBackingStoreProducer::Create( #ifdef SHELL_ENABLE_METAL case RenderTargetType::kMetalTexture: return CreateMTLTexture(config, renderer_out); +#endif +#ifdef SHELL_ENABLE_VULKAN + case RenderTargetType::kVulkanImage: + return CreateVulkanImage(config, renderer_out); #endif default: return false; @@ -229,5 +237,86 @@ bool EmbedderTestBackingStoreProducer::CreateMTLTexture( #endif } +bool EmbedderTestBackingStoreProducer::CreateVulkanImage( + const FlutterBackingStoreConfig* config, + FlutterBackingStore* backing_store_out) { +#ifdef SHELL_ENABLE_VULKAN + if (!test_vulkan_context_) { + test_vulkan_context_ = fml::MakeRefCounted(); + } + + 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) { + FML_LOG(ERROR) << "Could not create Skia surface from Vulkan image."; + return false; + } + backing_store_out->type = kFlutterBackingStoreTypeVulkan; + + FlutterVulkanImage* 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; +#else + return false; +#endif +} + } // namespace testing } // namespace flutter diff --git a/shell/platform/embedder/tests/embedder_test_backingstore_producer.h b/shell/platform/embedder/tests/embedder_test_backingstore_producer.h index f993891e44414..956c565d29f43 100644 --- a/shell/platform/embedder/tests/embedder_test_backingstore_producer.h +++ b/shell/platform/embedder/tests/embedder_test_backingstore_producer.h @@ -7,6 +7,7 @@ #include #include "flutter/fml/macros.h" +#include "flutter/fml/memory/ref_ptr_internal.h" #include "flutter/shell/platform/embedder/embedder.h" #include "third_party/skia/include/gpu/GrDirectContext.h" @@ -14,16 +15,26 @@ #include "flutter/testing/test_metal_context.h" #endif +#ifdef SHELL_ENABLE_VULKAN +#include "flutter/testing/test_vulkan_context.h" +#endif + namespace flutter { namespace testing { class EmbedderTestBackingStoreProducer { public: + struct UserData { + SkSurface* surface; + FlutterVulkanImage* image; + }; + enum class RenderTargetType { kSoftwareBuffer, kOpenGLFramebuffer, kOpenGLTexture, kMetalTexture, + kVulkanImage, }; EmbedderTestBackingStoreProducer(sk_sp context, @@ -46,6 +57,9 @@ class EmbedderTestBackingStoreProducer { bool CreateMTLTexture(const FlutterBackingStoreConfig* config, FlutterBackingStore* renderer_out); + bool CreateVulkanImage(const FlutterBackingStoreConfig* config, + FlutterBackingStore* renderer_out); + sk_sp context_; RenderTargetType type_; @@ -53,6 +67,10 @@ class EmbedderTestBackingStoreProducer { std::unique_ptr test_metal_context_; #endif +#ifdef SHELL_ENABLE_VULKAN + fml::RefPtr test_vulkan_context_; +#endif + FML_DISALLOW_COPY_AND_ASSIGN(EmbedderTestBackingStoreProducer); }; diff --git a/shell/platform/embedder/tests/embedder_test_compositor_vulkan.cc b/shell/platform/embedder/tests/embedder_test_compositor_vulkan.cc new file mode 100644 index 0000000000000..a932260513b98 --- /dev/null +++ b/shell/platform/embedder/tests/embedder_test_compositor_vulkan.cc @@ -0,0 +1,111 @@ +// 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 "flutter/shell/platform/embedder/tests/embedder_test_compositor_vulkan.h" + +#include "flutter/fml/logging.h" +#include "flutter/shell/platform/embedder/tests/embedder_assertions.h" +#include "flutter/shell/platform/embedder/tests/embedder_test_backingstore_producer.h" +#include "third_party/skia/include/core/SkSurface.h" + +namespace flutter { +namespace testing { + +EmbedderTestCompositorVulkan::EmbedderTestCompositorVulkan( + SkISize surface_size, + sk_sp context) + : EmbedderTestCompositor(surface_size, context) {} + +EmbedderTestCompositorVulkan::~EmbedderTestCompositorVulkan() = default; + +bool EmbedderTestCompositorVulkan::UpdateOffscrenComposition( + const FlutterLayer** layers, + size_t layers_count) { + last_composition_ = nullptr; + + const auto image_info = SkImageInfo::MakeN32Premul(surface_size_); + + sk_sp surface = + SkSurface::MakeRenderTarget(context_.get(), // context + SkBudgeted::kNo, // budgeted + image_info, // image info + 1, // sample count + kTopLeft_GrSurfaceOrigin, // surface origin + nullptr, // surface properties + false // create mipmaps + ); + + if (!surface) { + FML_LOG(ERROR) << "Could not update the off-screen composition."; + return false; + } + + auto canvas = surface->getCanvas(); + + // This has to be transparent because we are going to be compositing this + // sub-hierarchy onto the on-screen surface. + canvas->clear(SK_ColorTRANSPARENT); + + for (size_t i = 0; i < layers_count; ++i) { + const auto* layer = layers[i]; + + sk_sp platform_rendered_contents; + + sk_sp layer_image; + SkIPoint canvas_offset = SkIPoint::Make(0, 0); + + switch (layer->type) { + case kFlutterLayerContentTypeBackingStore: + layer_image = + reinterpret_cast( + layer->backing_store->user_data) + ->surface->makeImageSnapshot(); + break; + case kFlutterLayerContentTypePlatformView: + layer_image = + platform_view_renderer_callback_ + ? platform_view_renderer_callback_(*layer, context_.get()) + : nullptr; + canvas_offset = SkIPoint::Make(layer->offset.x, layer->offset.y); + break; + }; + + // If the layer is not a platform view but the engine did not specify an + // image for the backing store, it is an error. + if (!layer_image && layer->type != kFlutterLayerContentTypePlatformView) { + FML_LOG(ERROR) << "Could not snapshot layer in test compositor: " + << *layer; + return false; + } + + // The test could have just specified no contents to be rendered in place of + // a platform view. This is not an error. + if (layer_image) { + // The image rendered by Flutter already has the correct offset and + // transformation applied. The layers offset is meant for the platform. + canvas->drawImage(layer_image.get(), canvas_offset.x(), + canvas_offset.y()); + } + } + + last_composition_ = surface->makeImageSnapshot(); + + if (!last_composition_) { + FML_LOG(ERROR) << "Could not update the contents of the sub-composition."; + return false; + } + + if (next_scene_callback_) { + auto last_composition_snapshot = last_composition_->makeRasterImage(); + FML_CHECK(last_composition_snapshot); + auto callback = next_scene_callback_; + next_scene_callback_ = nullptr; + callback(std::move(last_composition_snapshot)); + } + + return true; +} + +} // namespace testing +} // namespace flutter diff --git a/shell/platform/embedder/tests/embedder_test_compositor_vulkan.h b/shell/platform/embedder/tests/embedder_test_compositor_vulkan.h new file mode 100644 index 0000000000000..608bda52ec858 --- /dev/null +++ b/shell/platform/embedder/tests/embedder_test_compositor_vulkan.h @@ -0,0 +1,32 @@ +// 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. + +#ifndef FLUTTER_SHELL_PLATFORM_EMBEDDER_TESTS_EMBEDDER_TEST_COMPOSITOR_VULKAN_H_ +#define FLUTTER_SHELL_PLATFORM_EMBEDDER_TESTS_EMBEDDER_TEST_COMPOSITOR_VULKAN_H_ + +#include "flutter/fml/macros.h" +#include "flutter/shell/platform/embedder/embedder.h" +#include "flutter/shell/platform/embedder/tests/embedder_test_compositor.h" + +namespace flutter { +namespace testing { + +class EmbedderTestCompositorVulkan : public EmbedderTestCompositor { + public: + EmbedderTestCompositorVulkan(SkISize surface_size, + sk_sp context); + + ~EmbedderTestCompositorVulkan() override; + + private: + bool UpdateOffscrenComposition(const FlutterLayer** layers, + size_t layers_count) override; + + FML_DISALLOW_COPY_AND_ASSIGN(EmbedderTestCompositorVulkan); +}; + +} // namespace testing +} // namespace flutter + +#endif // FLUTTER_SHELL_PLATFORM_EMBEDDER_TESTS_EMBEDDER_TEST_COMPOSITOR_VULKAN_H_ diff --git a/shell/platform/embedder/tests/embedder_test_context.h b/shell/platform/embedder/tests/embedder_test_context.h index 3f4f6d06b9c7d..f7df168122a3d 100644 --- a/shell/platform/embedder/tests/embedder_test_context.h +++ b/shell/platform/embedder/tests/embedder_test_context.h @@ -44,6 +44,7 @@ enum class EmbedderTestContextType { kSoftwareContext, kOpenGLContext, kMetalContext, + kVulkanContext, }; class EmbedderTestContext { @@ -105,6 +106,12 @@ class EmbedderTestContext { using NextSceneCallback = std::function image)>; +#ifdef SHELL_ENABLE_VULKAN + // The TestVulkanContext destructor must be called _after_ the compositor is + // freed. + fml::RefPtr vulkan_context_ = nullptr; +#endif + std::string assets_path_; ELFAOTSymbols aot_symbols_; std::unique_ptr vm_snapshot_data_; diff --git a/shell/platform/embedder/tests/embedder_test_context_vulkan.cc b/shell/platform/embedder/tests/embedder_test_context_vulkan.cc new file mode 100644 index 0000000000000..0fb93fa1e367c --- /dev/null +++ b/shell/platform/embedder/tests/embedder_test_context_vulkan.cc @@ -0,0 +1,61 @@ +// 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 "flutter/shell/platform/embedder/tests/embedder_test_context_vulkan.h" + +#include + +#include "flutter/fml/logging.h" +#include "flutter/shell/platform/embedder/tests/embedder_test_compositor_vulkan.h" +#include "flutter/testing/test_vulkan_context.h" +#include "flutter/testing/test_vulkan_surface.h" +#include "flutter/vulkan/vulkan_device.h" +#include "flutter/vulkan/vulkan_proc_table.h" +#include "third_party/skia/include/core/SkSurface.h" + +namespace flutter { +namespace testing { + +EmbedderTestContextVulkan::EmbedderTestContextVulkan(std::string assets_path) + : EmbedderTestContext(assets_path), surface_() { + vulkan_context_ = fml::MakeRefCounted(); +} + +EmbedderTestContextVulkan::~EmbedderTestContextVulkan() {} + +void EmbedderTestContextVulkan::SetupSurface(SkISize surface_size) { + FML_CHECK(surface_size_.isEmpty()); + surface_size_ = surface_size; + surface_ = TestVulkanSurface::Create(*vulkan_context_, surface_size_); +} + +size_t EmbedderTestContextVulkan::GetSurfacePresentCount() const { + return present_count_; +} + +VkImage EmbedderTestContextVulkan::GetNextImage(const SkISize& size) { + return surface_->GetImage(); +} + +bool EmbedderTestContextVulkan::PresentImage(VkImage image) { + FireRootSurfacePresentCallbackIfPresent( + [&]() { return surface_->GetSurfaceSnapshot(); }); + present_count_++; + return true; +} + +EmbedderTestContextType EmbedderTestContextVulkan::GetContextType() const { + return EmbedderTestContextType::kVulkanContext; +} + +void EmbedderTestContextVulkan::SetupCompositor() { + FML_CHECK(!compositor_) << "Already set up a compositor in this context."; + FML_CHECK(surface_) + << "Set up the Vulkan surface before setting up a compositor."; + compositor_ = std::make_unique( + surface_size_, vulkan_context_->GetGrDirectContext()); +} + +} // namespace testing +} // namespace flutter diff --git a/shell/platform/embedder/tests/embedder_test_context_vulkan.h b/shell/platform/embedder/tests/embedder_test_context_vulkan.h new file mode 100644 index 0000000000000..b08aa43941708 --- /dev/null +++ b/shell/platform/embedder/tests/embedder_test_context_vulkan.h @@ -0,0 +1,52 @@ +// 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. + +#ifndef FLUTTER_SHELL_PLATFORM_EMBEDDER_TESTS_EMBEDDER_CONTEXT_VULKAN_H_ +#define FLUTTER_SHELL_PLATFORM_EMBEDDER_TESTS_EMBEDDER_CONTEXT_VULKAN_H_ + +#include +#include "flutter/shell/platform/embedder/tests/embedder_test_context.h" +#include "flutter/testing/test_vulkan_context.h" +#include "flutter/vulkan/vulkan_application.h" +#include "testing/test_vulkan_surface.h" + +namespace flutter { +namespace testing { + +class EmbedderTestContextVulkan : public EmbedderTestContext { + public: + explicit EmbedderTestContextVulkan(std::string assets_path = ""); + + ~EmbedderTestContextVulkan() override; + + // |EmbedderTestContext| + EmbedderTestContextType GetContextType() const override; + + // |EmbedderTestContext| + size_t GetSurfacePresentCount() const override; + + // |EmbedderTestContext| + void SetupCompositor() override; + + VkImage GetNextImage(const SkISize& size); + + bool PresentImage(VkImage image); + + private: + std::unique_ptr surface_; + + SkISize surface_size_ = SkISize::MakeEmpty(); + size_t present_count_ = 0; + + void SetupSurface(SkISize surface_size) override; + + friend class EmbedderConfigBuilder; + + FML_DISALLOW_COPY_AND_ASSIGN(EmbedderTestContextVulkan); +}; + +} // namespace testing +} // namespace flutter + +#endif // FLUTTER_SHELL_PLATFORM_EMBEDDER_TESTS_EMBEDDER_CONTEXT_VULKAN_H_ diff --git a/shell/platform/embedder/tests/embedder_unittests_gl.cc b/shell/platform/embedder/tests/embedder_unittests_gl.cc index c8086191c8ba3..94f2eddb42fde 100644 --- a/shell/platform/embedder/tests/embedder_unittests_gl.cc +++ b/shell/platform/embedder/tests/embedder_unittests_gl.cc @@ -2,19 +2,21 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#include "tests/embedder_test_context.h" #define FML_USED_ON_EMBEDDER #include #include -#include "embedder.h" -#include "embedder_engine.h" +#include "vulkan/vulkan.h" + #include "flutter/flow/raster_cache.h" #include "flutter/fml/file.h" #include "flutter/fml/make_copyable.h" #include "flutter/fml/mapping.h" #include "flutter/fml/message_loop.h" #include "flutter/fml/message_loop_task_queues.h" +#include "flutter/fml/native_library.h" #include "flutter/fml/paths.h" #include "flutter/fml/synchronization/count_down_latch.h" #include "flutter/fml/synchronization/waitable_event.h" @@ -29,7 +31,6 @@ #include "flutter/shell/platform/embedder/tests/embedder_unittests_util.h" #include "flutter/testing/assertions_skia.h" #include "flutter/testing/test_gl_surface.h" -#include "flutter/testing/test_vulkan_context.h" #include "flutter/testing/testing.h" #include "third_party/skia/include/core/SkSurface.h" #include "third_party/skia/src/gpu/gl/GrGLDefines.h" @@ -40,14 +41,9 @@ namespace testing { using EmbedderTest = testing::EmbedderTest; -//------------------------------------------------------------------------------ -/// This is a sanity check to ensure Swiftshader Vulkan is working. Once Vulkan -/// support lands in the embedder API, it'll be tested via a new -/// EmbedderTestContext type/config. -/// -TEST_F(EmbedderTest, CanInitializeTestVulkanContext) { - TestVulkanContext ctx; - ASSERT_TRUE(ctx.IsValid()); +TEST_F(EmbedderTest, CanGetVulkanEmbedderContext) { + auto& context = GetEmbedderContext(EmbedderTestContextType::kVulkanContext); + EmbedderConfigBuilder builder(context); } TEST_F(EmbedderTest, CanCreateOpenGLRenderingEngine) { @@ -1238,13 +1234,14 @@ TEST_F(EmbedderTest, CanRenderSceneWithoutCustomCompositorWithTransformation) { "scene_without_custom_compositor_with_xform.png", rendered_scene)); } -TEST_F(EmbedderTest, CanRenderGradientWithoutCompositor) { - auto& context = GetEmbedderContext(EmbedderTestContextType::kOpenGLContext); +TEST_P(EmbedderTestMultiBackend, CanRenderGradientWithoutCompositor) { + EmbedderTestContextType backend = GetParam(); + auto& context = GetEmbedderContext(backend); EmbedderConfigBuilder builder(context); builder.SetDartEntrypoint("render_gradient"); - builder.SetOpenGLRendererConfig(SkISize::Make(800, 600)); + builder.SetRendererConfig(backend, SkISize::Make(800, 600)); auto rendered_scene = context.GetNextSceneImage(); @@ -1260,7 +1257,8 @@ TEST_F(EmbedderTest, CanRenderGradientWithoutCompositor) { ASSERT_EQ(FlutterEngineSendWindowMetricsEvent(engine.get(), &event), kSuccess); - ASSERT_TRUE(ImageMatchesFixture("gradient.png", rendered_scene)); + ASSERT_TRUE(ImageMatchesFixture( + FixtureNameForBackend(backend, "gradient.png"), rendered_scene)); } TEST_F(EmbedderTest, CanRenderGradientWithoutCompositorWithXform) { @@ -1296,16 +1294,16 @@ TEST_F(EmbedderTest, CanRenderGradientWithoutCompositorWithXform) { ASSERT_TRUE(ImageMatchesFixture("gradient_xform.png", rendered_scene)); } -TEST_F(EmbedderTest, CanRenderGradientWithCompositor) { - auto& context = GetEmbedderContext(EmbedderTestContextType::kOpenGLContext); +TEST_P(EmbedderTestMultiBackend, CanRenderGradientWithCompositor) { + EmbedderTestContextType backend = GetParam(); + auto& context = GetEmbedderContext(backend); EmbedderConfigBuilder builder(context); builder.SetDartEntrypoint("render_gradient"); - builder.SetOpenGLRendererConfig(SkISize::Make(800, 600)); + builder.SetRendererConfig(backend, SkISize::Make(800, 600)); builder.SetCompositor(); - builder.SetRenderTargetType( - EmbedderTestBackingStoreProducer::RenderTargetType::kOpenGLFramebuffer); + builder.SetRenderTargetType(GetRenderTargetFromBackend(backend, true)); auto rendered_scene = context.GetNextSceneImage(); @@ -1321,7 +1319,8 @@ TEST_F(EmbedderTest, CanRenderGradientWithCompositor) { ASSERT_EQ(FlutterEngineSendWindowMetricsEvent(engine.get(), &event), kSuccess); - ASSERT_TRUE(ImageMatchesFixture("gradient.png", rendered_scene)); + ASSERT_TRUE(ImageMatchesFixture( + FixtureNameForBackend(backend, "gradient.png"), rendered_scene)); } TEST_F(EmbedderTest, CanRenderGradientWithCompositorWithXform) { @@ -1361,16 +1360,17 @@ TEST_F(EmbedderTest, CanRenderGradientWithCompositorWithXform) { ASSERT_TRUE(ImageMatchesFixture("gradient_xform.png", rendered_scene)); } -TEST_F(EmbedderTest, CanRenderGradientWithCompositorOnNonRootLayer) { - auto& context = GetEmbedderContext(EmbedderTestContextType::kOpenGLContext); +TEST_P(EmbedderTestMultiBackend, + CanRenderGradientWithCompositorOnNonRootLayer) { + EmbedderTestContextType backend = GetParam(); + auto& context = GetEmbedderContext(backend); EmbedderConfigBuilder builder(context); builder.SetDartEntrypoint("render_gradient_on_non_root_backing_store"); - builder.SetOpenGLRendererConfig(SkISize::Make(800, 600)); + builder.SetRendererConfig(backend, SkISize::Make(800, 600)); builder.SetCompositor(); - builder.SetRenderTargetType( - EmbedderTestBackingStoreProducer::RenderTargetType::kOpenGLFramebuffer); + builder.SetRenderTargetType(GetRenderTargetFromBackend(backend, true)); context.GetCompositor().SetNextPresentCallback( [&](const FlutterLayer** layers, size_t layers_count) { @@ -1379,9 +1379,8 @@ TEST_F(EmbedderTest, CanRenderGradientWithCompositorOnNonRootLayer) { // Layer Root { FlutterBackingStore backing_store = *layers[0]->backing_store; - backing_store.type = kFlutterBackingStoreTypeOpenGL; backing_store.did_update = true; - backing_store.open_gl.type = kFlutterOpenGLTargetTypeFramebuffer; + ConfigureBackingStore(backing_store, backend, true); FlutterLayer layer = {}; layer.struct_size = sizeof(layer); @@ -1412,9 +1411,8 @@ TEST_F(EmbedderTest, CanRenderGradientWithCompositorOnNonRootLayer) { // Layer 2 { FlutterBackingStore backing_store = *layers[2]->backing_store; - backing_store.type = kFlutterBackingStoreTypeOpenGL; backing_store.did_update = true; - backing_store.open_gl.type = kFlutterOpenGLTargetTypeFramebuffer; + ConfigureBackingStore(backing_store, backend, true); FlutterLayer layer = {}; layer.struct_size = sizeof(layer); @@ -1463,7 +1461,8 @@ TEST_F(EmbedderTest, CanRenderGradientWithCompositorOnNonRootLayer) { ASSERT_EQ(FlutterEngineSendWindowMetricsEvent(engine.get(), &event), kSuccess); - ASSERT_TRUE(ImageMatchesFixture("gradient.png", rendered_scene)); + ASSERT_TRUE(ImageMatchesFixture( + FixtureNameForBackend(backend, "gradient.png"), rendered_scene)); } TEST_F(EmbedderTest, CanRenderGradientWithCompositorOnNonRootLayerWithXform) { @@ -1738,7 +1737,7 @@ TEST_F(EmbedderTest, CanCreateEmbedderWithCustomRenderTaskRunner) { /// Asserts that the render task runner can be the same as the platform task /// runner. /// -TEST_F(EmbedderTest, +TEST_P(EmbedderTestMultiBackend, CanCreateEmbedderWithCustomRenderTaskRunnerTheSameAsPlatformTaskRunner) { // A new thread needs to be created for the platform thread because the test // can't wait for assertions to be completed on the same thread that services @@ -1760,10 +1759,10 @@ TEST_F(EmbedderTest, }); platform_task_runner->PostTask([&]() { - EmbedderConfigBuilder builder( - GetEmbedderContext(EmbedderTestContextType::kOpenGLContext)); + EmbedderTestContextType backend = GetParam(); + EmbedderConfigBuilder builder(GetEmbedderContext(backend)); builder.SetDartEntrypoint("can_render_scene_without_custom_compositor"); - builder.SetOpenGLRendererConfig(SkISize::Make(800, 600)); + builder.SetRendererConfig(backend, SkISize::Make(800, 600)); builder.SetRenderTaskRunner( &common_task_runner.GetFlutterTaskRunnerDescription()); builder.SetPlatformTaskRunner( @@ -1813,17 +1812,17 @@ TEST_F(EmbedderTest, } } -TEST_F(EmbedderTest, +TEST_P(EmbedderTestMultiBackend, CompositorMustBeAbleToRenderKnownScenePixelRatioOnSurface) { - auto& context = GetEmbedderContext(EmbedderTestContextType::kOpenGLContext); + EmbedderTestContextType backend = GetParam(); + auto& context = GetEmbedderContext(backend); EmbedderConfigBuilder builder(context); - builder.SetOpenGLRendererConfig(SkISize::Make(800, 600)); + builder.SetRendererConfig(backend, SkISize::Make(800, 600)); builder.SetCompositor(); builder.SetDartEntrypoint("can_display_platform_view_with_pixel_ratio"); - builder.SetRenderTargetType( - EmbedderTestBackingStoreProducer::RenderTargetType::kOpenGLTexture); + builder.SetRenderTargetType(GetRenderTargetFromBackend(backend, false)); fml::CountDownLatch latch(1); @@ -1836,9 +1835,8 @@ TEST_F(EmbedderTest, // Layer 0 (Root) { FlutterBackingStore backing_store = *layers[0]->backing_store; - backing_store.type = kFlutterBackingStoreTypeOpenGL; backing_store.did_update = true; - backing_store.open_gl.type = kFlutterOpenGLTargetTypeTexture; + ConfigureBackingStore(backing_store, backend, false); FlutterLayer layer = {}; layer.struct_size = sizeof(layer); @@ -1869,9 +1867,8 @@ TEST_F(EmbedderTest, // Layer 2 { FlutterBackingStore backing_store = *layers[2]->backing_store; - backing_store.type = kFlutterBackingStoreTypeOpenGL; backing_store.did_update = true; - backing_store.open_gl.type = kFlutterOpenGLTargetTypeTexture; + ConfigureBackingStore(backing_store, backend, false); FlutterLayer layer = {}; layer.struct_size = sizeof(layer); @@ -1900,7 +1897,8 @@ TEST_F(EmbedderTest, latch.Wait(); - ASSERT_TRUE(ImageMatchesFixture("dpr_noxform.png", rendered_scene)); + ASSERT_TRUE(ImageMatchesFixture( + FixtureNameForBackend(backend, "dpr_noxform.png"), rendered_scene)); } TEST_F( @@ -2084,16 +2082,16 @@ TEST_F(EmbedderTest, FlutterEngineShutdown(engine.release()); } -TEST_F(EmbedderTest, PlatformViewMutatorsAreValid) { - auto& context = GetEmbedderContext(EmbedderTestContextType::kOpenGLContext); +TEST_P(EmbedderTestMultiBackend, PlatformViewMutatorsAreValid) { + EmbedderTestContextType backend = GetParam(); + auto& context = GetEmbedderContext(backend); EmbedderConfigBuilder builder(context); - builder.SetOpenGLRendererConfig(SkISize::Make(800, 600)); + builder.SetRendererConfig(backend, SkISize::Make(800, 600)); builder.SetCompositor(); builder.SetDartEntrypoint("platform_view_mutators"); - builder.SetRenderTargetType( - EmbedderTestBackingStoreProducer::RenderTargetType::kOpenGLTexture); + builder.SetRenderTargetType(GetRenderTargetFromBackend(backend, false)); fml::CountDownLatch latch(1); context.GetCompositor().SetNextPresentCallback( @@ -2103,9 +2101,8 @@ TEST_F(EmbedderTest, PlatformViewMutatorsAreValid) { // Layer 0 (Root) { FlutterBackingStore backing_store = *layers[0]->backing_store; - backing_store.type = kFlutterBackingStoreTypeOpenGL; backing_store.did_update = true; - backing_store.open_gl.type = kFlutterOpenGLTargetTypeTexture; + ConfigureBackingStore(backing_store, backend, false); FlutterLayer layer = {}; layer.struct_size = sizeof(layer); @@ -3691,5 +3688,11 @@ TEST_F(EmbedderTest, ExternalTextureGLRefreshedTooOften) { EXPECT_TRUE(resolve_called); } +INSTANTIATE_TEST_SUITE_P( + EmbedderTestGlVk, + EmbedderTestMultiBackend, + ::testing::Values(EmbedderTestContextType::kOpenGLContext, + EmbedderTestContextType::kVulkanContext)); + } // namespace testing } // namespace flutter diff --git a/shell/platform/embedder/tests/embedder_unittests_util.cc b/shell/platform/embedder/tests/embedder_unittests_util.cc index 89d5fec9956e7..d076dc1c017c4 100644 --- a/shell/platform/embedder/tests/embedder_unittests_util.cc +++ b/shell/platform/embedder/tests/embedder_unittests_util.cc @@ -6,6 +6,7 @@ #include +#include "flutter/shell/platform/embedder/tests/embedder_test_backingstore_producer.h" #include "flutter/shell/platform/embedder/tests/embedder_unittests_util.h" namespace flutter { @@ -69,6 +70,61 @@ bool RasterImagesAreSame(sk_sp a, sk_sp b) { return normalized_a->equals(normalized_b.get()); } +std::string FixtureNameForBackend(EmbedderTestContextType backend, + const std::string& name) { + switch (backend) { + case EmbedderTestContextType::kVulkanContext: + return "vk_" + name; + default: + return name; + } +} + +EmbedderTestBackingStoreProducer::RenderTargetType GetRenderTargetFromBackend( + EmbedderTestContextType backend, + bool opengl_framebuffer) { + switch (backend) { + case EmbedderTestContextType::kVulkanContext: + return EmbedderTestBackingStoreProducer::RenderTargetType::kVulkanImage; + case EmbedderTestContextType::kOpenGLContext: + if (opengl_framebuffer) { + return EmbedderTestBackingStoreProducer::RenderTargetType:: + kOpenGLFramebuffer; + } + return EmbedderTestBackingStoreProducer::RenderTargetType::kOpenGLTexture; + case EmbedderTestContextType::kMetalContext: + return EmbedderTestBackingStoreProducer::RenderTargetType::kMetalTexture; + case EmbedderTestContextType::kSoftwareContext: + return EmbedderTestBackingStoreProducer::RenderTargetType:: + kSoftwareBuffer; + } +} + +void ConfigureBackingStore(FlutterBackingStore& backing_store, + EmbedderTestContextType backend, + bool opengl_framebuffer) { + switch (backend) { + case EmbedderTestContextType::kVulkanContext: + backing_store.type = kFlutterBackingStoreTypeVulkan; + break; + case EmbedderTestContextType::kOpenGLContext: + if (opengl_framebuffer) { + backing_store.type = kFlutterBackingStoreTypeOpenGL; + backing_store.open_gl.type = kFlutterOpenGLTargetTypeFramebuffer; + } else { + backing_store.type = kFlutterBackingStoreTypeOpenGL; + backing_store.open_gl.type = kFlutterOpenGLTargetTypeTexture; + } + break; + case EmbedderTestContextType::kMetalContext: + backing_store.type = kFlutterBackingStoreTypeMetal; + break; + case EmbedderTestContextType::kSoftwareContext: + backing_store.type = kFlutterBackingStoreTypeSoftware; + break; + } +} + bool WriteImageToDisk(const fml::UniqueFD& directory, const std::string& name, sk_sp image) { diff --git a/shell/platform/embedder/tests/embedder_unittests_util.h b/shell/platform/embedder/tests/embedder_unittests_util.h index 35944cb13adc8..f02c1d61c0cc5 100644 --- a/shell/platform/embedder/tests/embedder_unittests_util.h +++ b/shell/platform/embedder/tests/embedder_unittests_util.h @@ -9,7 +9,6 @@ #include -#include "embedder.h" #include "flutter/fml/mapping.h" #include "flutter/fml/message_loop.h" #include "flutter/fml/paths.h" @@ -26,6 +25,40 @@ sk_sp CreateRenderSurface(const FlutterLayer& layer, bool RasterImagesAreSame(sk_sp a, sk_sp b); +/// @brief Prepends a prefix to the name which is unique to the test +/// context type. This is useful for tests that use +/// EmbedderTestMultiBackend and require different fixtures per +/// backend. For OpenGL, the name remains unchanged. +/// @param[in] backend The test context type used to determine the prepended +/// prefix (e.g. `vk_[name]` for Vulkan). +/// @param[in] name The name of the fixture without any special prefixes. +std::string FixtureNameForBackend(EmbedderTestContextType backend, + const std::string& name); + +/// @brief Resolves a render target type for a given backend description. +/// This is useful for tests that use EmbedderTestMultiBackend. +/// @param[in] backend The test context type to resolve the render +/// target for. +/// @param[in] opengl_framebuffer Ignored for all non-OpenGL backends. Flutter +/// supports rendering to both OpenGL textures +/// and framebuffers. When false, the OpenGL +/// texture render target type is returned. +EmbedderTestBackingStoreProducer::RenderTargetType GetRenderTargetFromBackend( + EmbedderTestContextType backend, + bool opengl_framebuffer); + +/// @brief Configures per-backend properties for a given backing store. +/// @param[in] backing_store The backing store to configure. +/// @param[in] backend The test context type used to decide which +/// backend the backing store will be used with. +/// @param[in] opengl_framebuffer Ignored for all non-OpenGL backends. Flutter +/// supports rendering to both OpenGL textures +/// and framebuffers. When false, the backing +/// store is configured to be an OpenGL texture. +void ConfigureBackingStore(FlutterBackingStore& backing_store, + EmbedderTestContextType backend, + bool opengl_framebuffer); + bool WriteImageToDisk(const fml::UniqueFD& directory, const std::string& name, sk_sp image); diff --git a/testing/BUILD.gn b/testing/BUILD.gn index 9ae50f47e8d00..3b47f5ac25d59 100644 --- a/testing/BUILD.gn +++ b/testing/BUILD.gn @@ -128,6 +128,10 @@ if (enable_unittests) { sources = [ "test_vulkan_context.cc", "test_vulkan_context.h", + "test_vulkan_image.cc", + "test_vulkan_image.h", + "test_vulkan_surface.cc", + "test_vulkan_surface.h", ] defines = [ "TEST_VULKAN_PROCS" ] @@ -135,6 +139,7 @@ if (enable_unittests) { deps = [ ":skia", "//flutter/fml", + "//flutter/shell/common", "//flutter/vulkan", ] diff --git a/testing/test_vulkan_context.cc b/testing/test_vulkan_context.cc index 36c30989d08d0..56eeb558cc975 100644 --- a/testing/test_vulkan_context.cc +++ b/testing/test_vulkan_context.cc @@ -2,9 +2,19 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "test_vulkan_context.h" +#include +#include -#include "flutter/vulkan/vulkan_proc_table.h" +#include "flutter/fml/logging.h" +#include "flutter/shell/common/context_options.h" +#include "flutter/testing/test_vulkan_context.h" + +#include "flutter/fml/memory/ref_ptr.h" +#include "flutter/fml/native_library.h" +#include "third_party/skia/include/core/SkSurface.h" +#include "third_party/skia/include/gpu/GrDirectContext.h" +#include "third_party/skia/include/gpu/vk/GrVkExtensions.h" +#include "vulkan/vulkan_core.h" #ifdef FML_OS_MACOSX #define VULKAN_SO_PATH "libvk_swiftshader.dylib" @@ -15,38 +25,164 @@ #endif namespace flutter { +namespace testing { + +TestVulkanContext::TestVulkanContext() { + // --------------------------------------------------------------------------- + // Initialize basic Vulkan state using the Swiftshader ICD. + // --------------------------------------------------------------------------- + + const char* vulkan_icd = VULKAN_SO_PATH; + + // TODO(96949): Clean this up and pass a native library directly to + // VulkanProcTable. + if (!fml::NativeLibrary::Create(VULKAN_SO_PATH)) { + FML_LOG(ERROR) << "Couldn't find Vulkan ICD \"" << vulkan_icd + << "\", trying \"libvulkan.so\" instead."; + vulkan_icd = "libvulkan.so"; + } + + FML_LOG(INFO) << "Using Vulkan ICD: " << vulkan_icd; -TestVulkanContext::TestVulkanContext() : valid_(false) { - vk_ = fml::MakeRefCounted(VULKAN_SO_PATH); + vk_ = fml::MakeRefCounted(vulkan_icd); if (!vk_ || !vk_->HasAcquiredMandatoryProcAddresses()) { - FML_DLOG(ERROR) << "Proc table has not acquired mandatory proc addresses."; + FML_LOG(ERROR) << "Proc table has not acquired mandatory proc addresses."; return; } - application_ = std::unique_ptr( - new vulkan::VulkanApplication(*vk_, "Flutter Unittests", {})); + application_ = + std::unique_ptr(new vulkan::VulkanApplication( + *vk_, "Flutter Unittests", {}, VK_MAKE_VERSION(1, 0, 0), + VK_MAKE_VERSION(1, 0, 0), true)); if (!application_->IsValid()) { - FML_DLOG(ERROR) << "Failed to initialize basic Vulkan state."; + FML_LOG(ERROR) << "Failed to initialize basic Vulkan state."; return; } if (!vk_->AreInstanceProcsSetup()) { - FML_DLOG(ERROR) << "Failed to acquire full proc table."; + FML_LOG(ERROR) << "Failed to acquire full proc table."; + return; + } + + device_ = application_->AcquireFirstCompatibleLogicalDevice(); + if (!device_ || !device_->IsValid()) { + FML_LOG(ERROR) << "Failed to create compatible logical device."; + return; + } + + // --------------------------------------------------------------------------- + // Create a Skia context. + // For creating SkSurfaces from VkImages and snapshotting them, etc. + // --------------------------------------------------------------------------- + + uint32_t skia_features = 0; + if (!device_->GetPhysicalDeviceFeaturesSkia(&skia_features)) { + FML_LOG(ERROR) << "Failed to get physical device features."; + return; } - logical_device_ = application_->AcquireFirstCompatibleLogicalDevice(); - if (!logical_device_ || !logical_device_->IsValid()) { - FML_DLOG(ERROR) << "Failed to create compatible logical device."; + auto get_proc = vk_->CreateSkiaGetProc(); + if (get_proc == nullptr) { + FML_LOG(ERROR) << "Failed to create Vulkan getProc for Skia."; return; } - valid_ = true; + GrVkExtensions extensions; + + GrVkBackendContext backend_context = {}; + backend_context.fInstance = application_->GetInstance(); + backend_context.fPhysicalDevice = device_->GetPhysicalDeviceHandle(); + backend_context.fDevice = device_->GetHandle(); + backend_context.fQueue = device_->GetQueueHandle(); + backend_context.fGraphicsQueueIndex = device_->GetGraphicsQueueIndex(); + backend_context.fMinAPIVersion = VK_MAKE_VERSION(1, 0, 0); + backend_context.fMaxAPIVersion = VK_MAKE_VERSION(1, 0, 0); + backend_context.fFeatures = skia_features; + backend_context.fVkExtensions = &extensions; + backend_context.fGetProc = get_proc; + backend_context.fOwnsInstanceAndDevice = false; + + GrContextOptions options = + MakeDefaultContextOptions(ContextType::kRender, GrBackendApi::kVulkan); + options.fReduceOpsTaskSplitting = GrContextOptions::Enable::kNo; + context_ = GrDirectContext::MakeVulkan(backend_context, options); } -TestVulkanContext::~TestVulkanContext() = default; +TestVulkanContext::~TestVulkanContext() { + if (context_) { + context_->releaseResourcesAndAbandonContext(); + } +} + +std::optional TestVulkanContext::CreateImage( + const SkISize& size) const { + TestVulkanImage result; + + VkImageCreateInfo info = { + .sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + .imageType = VK_IMAGE_TYPE_2D, + .format = VK_FORMAT_R8G8B8A8_UNORM, + .extent = VkExtent3D{static_cast(size.width()), + static_cast(size.height()), 1}, + .mipLevels = 1, + .arrayLayers = 1, + .samples = VK_SAMPLE_COUNT_1_BIT, + .tiling = VK_IMAGE_TILING_OPTIMAL, + .usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | + VK_IMAGE_USAGE_TRANSFER_DST_BIT | + VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, + .sharingMode = VK_SHARING_MODE_EXCLUSIVE, + .queueFamilyIndexCount = 0, + .pQueueFamilyIndices = nullptr, + .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED, + }; + + VkImage image; + if (VK_CALL_LOG_ERROR(VK_CALL_LOG_ERROR( + vk_->CreateImage(device_->GetHandle(), &info, nullptr, &image)))) { + return std::nullopt; + } + + result.image_ = vulkan::VulkanHandle( + image, [&vk = vk_, &device = device_](VkImage image) { + vk->DestroyImage(device->GetHandle(), image, nullptr); + }); + + VkMemoryRequirements mem_req; + vk_->GetImageMemoryRequirements(device_->GetHandle(), image, &mem_req); + VkMemoryAllocateInfo alloc_info{}; + alloc_info.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; + alloc_info.allocationSize = mem_req.size; + alloc_info.memoryTypeIndex = static_cast(__builtin_ctz( + mem_req.memoryTypeBits & VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT)); + + VkDeviceMemory memory; + if (VK_CALL_LOG_ERROR(vk_->AllocateMemory(device_->GetHandle(), &alloc_info, + nullptr, &memory)) != VK_SUCCESS) { + return std::nullopt; + } + + result.memory_ = vulkan::VulkanHandle{ + memory, [&vk = vk_, &device = device_](VkDeviceMemory memory) { + vk->FreeMemory(device->GetHandle(), memory, nullptr); + }}; + + if (VK_CALL_LOG_ERROR(VK_CALL_LOG_ERROR(vk_->BindImageMemory( + device_->GetHandle(), result.image_, result.memory_, 0)))) { + return std::nullopt; + } + + result.context_ = + fml::RefPtr(const_cast(this)); + + return result; +} -bool TestVulkanContext::IsValid() { - return valid_; +sk_sp TestVulkanContext::GetGrDirectContext() const { + return context_; } +} // namespace testing } // namespace flutter diff --git a/testing/test_vulkan_context.h b/testing/test_vulkan_context.h index 5d79971809dd3..25d212bf05eb1 100644 --- a/testing/test_vulkan_context.h +++ b/testing/test_vulkan_context.h @@ -5,29 +5,44 @@ #ifndef FLUTTER_TESTING_TEST_VULKAN_CONTEXT_H_ #define FLUTTER_TESTING_TEST_VULKAN_CONTEXT_H_ +#include "flutter/fml/macros.h" +#include "flutter/fml/memory/ref_ptr.h" +#include "flutter/testing/test_vulkan_image.h" #include "flutter/vulkan/vulkan_application.h" #include "flutter/vulkan/vulkan_device.h" #include "flutter/vulkan/vulkan_proc_table.h" +#include "third_party/skia/include/core/SkSize.h" +#include "third_party/skia/include/gpu/GrDirectContext.h" + namespace flutter { +namespace testing { -/// @brief Utility class to create a Vulkan device context, a corresponding -/// Skia context, and device resources. -class TestVulkanContext { +class TestVulkanContext : public fml::RefCountedThreadSafe { public: TestVulkanContext(); ~TestVulkanContext(); - bool IsValid(); + + std::optional CreateImage(const SkISize& size) const; + + sk_sp GetGrDirectContext() const; private: - bool valid_ = false; fml::RefPtr vk_; std::unique_ptr application_; - std::unique_ptr logical_device_; + std::unique_ptr device_; + + sk_sp context_; + + friend class EmbedderTestContextVulkan; + friend class EmbedderConfigBuilder; + FML_FRIEND_MAKE_REF_COUNTED(TestVulkanContext); + FML_FRIEND_REF_COUNTED_THREAD_SAFE(TestVulkanContext); FML_DISALLOW_COPY_AND_ASSIGN(TestVulkanContext); }; +} // namespace testing } // namespace flutter #endif // FLUTTER_TESTING_TEST_VULKAN_CONTEXT_H_ diff --git a/testing/test_vulkan_image.cc b/testing/test_vulkan_image.cc new file mode 100644 index 0000000000000..4c365412ae6ec --- /dev/null +++ b/testing/test_vulkan_image.cc @@ -0,0 +1,24 @@ +// 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 "flutter/testing/test_vulkan_image.h" + +#include "flutter/testing/test_vulkan_context.h" + +namespace flutter { +namespace testing { + +TestVulkanImage::TestVulkanImage() = default; + +TestVulkanImage::TestVulkanImage(TestVulkanImage&& other) = default; +TestVulkanImage& TestVulkanImage::operator=(TestVulkanImage&& other) = default; + +TestVulkanImage::~TestVulkanImage() = default; + +VkImage TestVulkanImage::GetImage() { + return image_; +} + +} // namespace testing +} // namespace flutter diff --git a/testing/test_vulkan_image.h b/testing/test_vulkan_image.h new file mode 100644 index 0000000000000..c8a6a797f385e --- /dev/null +++ b/testing/test_vulkan_image.h @@ -0,0 +1,46 @@ +// 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. + +#ifndef FLUTTER_TESTING_TEST_VULKAN_IMAGE_H_ +#define FLUTTER_TESTING_TEST_VULKAN_IMAGE_H_ + +#include "flutter/fml/macros.h" +#include "flutter/vulkan/vulkan_handle.h" + +#include "flutter/fml/memory/ref_ptr.h" +#include "third_party/skia/include/core/SkSize.h" + +namespace flutter { +namespace testing { + +class TestVulkanContext; + +/// Captures the lifetime of a test VkImage along with its bound memory. +class TestVulkanImage { + public: + TestVulkanImage(TestVulkanImage&& other); + TestVulkanImage& operator=(TestVulkanImage&& other); + + ~TestVulkanImage(); + + VkImage GetImage(); + + private: + TestVulkanImage(); + + // The lifetime of the Vulkan state must exceed memory/image handles. + fml::RefPtr context_; + + vulkan::VulkanHandle image_; + vulkan::VulkanHandle memory_; + + FML_DISALLOW_COPY_AND_ASSIGN(TestVulkanImage); + + friend TestVulkanContext; +}; + +} // namespace testing +} // namespace flutter + +#endif // FLUTTER_TESTING_TEST_VULKAN_IMAGE_H_ diff --git a/testing/test_vulkan_surface.cc b/testing/test_vulkan_surface.cc new file mode 100644 index 0000000000000..6bca8ed44f599 --- /dev/null +++ b/testing/test_vulkan_surface.cc @@ -0,0 +1,110 @@ +// 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 "flutter/testing/test_vulkan_surface.h" +#include +#include "flutter/fml/logging.h" +#include "flutter/testing/test_vulkan_context.h" + +#include "third_party/skia/include/core/SkSurface.h" +#include "third_party/skia/include/core/SkSurfaceProps.h" + +namespace flutter { +namespace testing { + +TestVulkanSurface::TestVulkanSurface(TestVulkanImage&& image) + : image_(std::move(image)){}; + +std::unique_ptr TestVulkanSurface::Create( + const TestVulkanContext& context, + const SkISize& surface_size) { + auto image_result = context.CreateImage(surface_size); + + if (!image_result.has_value()) { + FML_LOG(ERROR) << "Could not create VkImage."; + return nullptr; + } + + GrVkImageInfo image_info = { + .fImage = image_result.value().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); + + auto result = std::unique_ptr( + new TestVulkanSurface(std::move(image_result.value()))); + result->surface_ = SkSurface::MakeFromBackendTexture( + context.GetGrDirectContext().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 + nullptr, // release proc + nullptr // release context + ); + + if (!result->surface_) { + FML_LOG(ERROR) + << "Could not wrap VkImage as an SkSurface Vulkan render texture."; + return nullptr; + } + + return result; +} + +bool TestVulkanSurface::IsValid() const { + return surface_ != nullptr; +} + +sk_sp TestVulkanSurface::GetSurfaceSnapshot() const { + if (!IsValid()) { + return nullptr; + } + + if (!surface_) { + FML_LOG(ERROR) << "Aborting snapshot because of on-screen surface " + "acquisition failure."; + return nullptr; + } + + auto device_snapshot = surface_->makeImageSnapshot(); + + if (!device_snapshot) { + FML_LOG(ERROR) << "Could not create the device snapshot while attempting " + "to snapshot the Vulkan surface."; + return nullptr; + } + + auto host_snapshot = device_snapshot->makeRasterImage(); + + if (!host_snapshot) { + FML_LOG(ERROR) << "Could not create the host snapshot while attempting to " + "snapshot the Vulkan surface."; + return nullptr; + } + + return host_snapshot; +} + +VkImage TestVulkanSurface::GetImage() { + return image_.GetImage(); +} + +} // namespace testing +} // namespace flutter diff --git a/testing/test_vulkan_surface.h b/testing/test_vulkan_surface.h new file mode 100644 index 0000000000000..4dce92fe725f8 --- /dev/null +++ b/testing/test_vulkan_surface.h @@ -0,0 +1,41 @@ +// 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. + +#ifndef FLUTTER_TESTING_TEST_VULKAN_SURFACE_IMPL_H_ +#define FLUTTER_TESTING_TEST_VULKAN_SURFACE_IMPL_H_ + +#include +#include "flutter/testing/test_vulkan_context.h" + +#include "third_party/skia/include/core/SkRefCnt.h" +#include "third_party/skia/include/core/SkSize.h" +#include "third_party/skia/include/gpu/GrDirectContext.h" + +namespace flutter { + +namespace testing { + +class TestVulkanSurface { + public: + static std::unique_ptr Create( + const TestVulkanContext& context, + const SkISize& surface_size); + + bool IsValid() const; + + sk_sp GetSurfaceSnapshot() const; + + VkImage GetImage(); + + private: + explicit TestVulkanSurface(TestVulkanImage&& image); + + TestVulkanImage image_; + sk_sp surface_; +}; + +} // namespace testing +} // namespace flutter + +#endif // FLUTTER_TESTING_TEST_VULKAN_SURFACE_IMPL_H_ diff --git a/vulkan/vulkan_device.cc b/vulkan/vulkan_device.cc index e981329d2174b..e58dd6e7db37f 100644 --- a/vulkan/vulkan_device.cc +++ b/vulkan/vulkan_device.cc @@ -35,8 +35,7 @@ VulkanDevice::VulkanDevice(VulkanProcTable& p_vk, : vk(p_vk), physical_device_(std::move(physical_device)), graphics_queue_index_(std::numeric_limits::max()), - valid_(false), - enable_validation_layers_(enable_validation_layers) { + valid_(false) { if (!physical_device_ || !vk.AreInstanceProcsSetup()) { return; } @@ -74,7 +73,7 @@ VulkanDevice::VulkanDevice(VulkanProcTable& p_vk, }; auto enabled_layers = - DeviceLayersToEnable(vk, physical_device_, enable_validation_layers_); + DeviceLayersToEnable(vk, physical_device_, enable_validation_layers); const char* layers[enabled_layers.size()]; @@ -122,6 +121,36 @@ VulkanDevice::VulkanDevice(VulkanProcTable& p_vk, queue_ = VulkanHandle(queue); + if (!InitializeCommandPool()) { + return; + } + + valid_ = true; +} + +VulkanDevice::VulkanDevice(VulkanProcTable& p_vk, + VulkanHandle physical_device, + VulkanHandle device, + uint32_t queue_family_index, + VulkanHandle queue) + : vk(p_vk), + physical_device_(std::move(physical_device)), + device_(std::move(device)), + queue_(std::move(queue)), + graphics_queue_index_(queue_family_index), + valid_(false) { + if (!physical_device_ || !vk.AreInstanceProcsSetup()) { + return; + } + + if (!InitializeCommandPool()) { + return; + } + + valid_ = true; +} + +bool VulkanDevice::InitializeCommandPool() { const VkCommandPoolCreateInfo command_pool_create_info = { .sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO, .pNext = nullptr, @@ -134,7 +163,7 @@ VulkanDevice::VulkanDevice(VulkanProcTable& p_vk, nullptr, &command_pool)) != VK_SUCCESS) { FML_DLOG(INFO) << "Could not create the command pool."; - return; + return false; } command_pool_ = VulkanHandle{ @@ -142,7 +171,7 @@ VulkanDevice::VulkanDevice(VulkanProcTable& p_vk, vk.DestroyCommandPool(device_, pool, nullptr); }}; - valid_ = true; + return true; } VulkanDevice::~VulkanDevice() { diff --git a/vulkan/vulkan_device.h b/vulkan/vulkan_device.h index 73219ea098d94..22f848dd00e97 100644 --- a/vulkan/vulkan_device.h +++ b/vulkan/vulkan_device.h @@ -18,10 +18,20 @@ class VulkanSurface; class VulkanDevice { public: + /// @brief Create a new VkDevice with a resolved VkQueue suitable for + /// rendering with Skia. + /// VulkanDevice(VulkanProcTable& vk, VulkanHandle physical_device, bool enable_validation_layers); + /// @brief Wrap an existing VkDevice and VkQueue. + /// + VulkanDevice(VulkanProcTable& vk, + VulkanHandle physical_device, + VulkanHandle device, + uint32_t queue_family_index, + VulkanHandle queue); ~VulkanDevice(); bool IsValid() const; @@ -72,8 +82,8 @@ class VulkanDevice { VulkanHandle command_pool_; uint32_t graphics_queue_index_; bool valid_; - bool enable_validation_layers_; + bool InitializeCommandPool(); std::vector GetQueueFamilyProperties() const; FML_DISALLOW_COPY_AND_ASSIGN(VulkanDevice); diff --git a/vulkan/vulkan_proc_table.cc b/vulkan/vulkan_proc_table.cc index f5ba8f97bcb3c..f1ccbe88586b2 100644 --- a/vulkan/vulkan_proc_table.cc +++ b/vulkan/vulkan_proc_table.cc @@ -16,10 +16,18 @@ namespace vulkan { VulkanProcTable::VulkanProcTable() : VulkanProcTable("libvulkan.so"){}; -VulkanProcTable::VulkanProcTable(const char* path) +VulkanProcTable::VulkanProcTable(const char* so_path) : handle_(nullptr), acquired_mandatory_proc_addresses_(false) { - acquired_mandatory_proc_addresses_ = - OpenLibraryHandle(path) && SetupLoaderProcAddresses(); + acquired_mandatory_proc_addresses_ = OpenLibraryHandle(so_path) && + SetupGetInstanceProcAddress() && + SetupLoaderProcAddresses(); +} + +VulkanProcTable::VulkanProcTable( + std::function get_instance_proc_addr) + : handle_(nullptr), acquired_mandatory_proc_addresses_(false) { + GetInstanceProcAddr = get_instance_proc_addr; + acquired_mandatory_proc_addresses_ = SetupLoaderProcAddresses(); } VulkanProcTable::~VulkanProcTable() { @@ -42,24 +50,27 @@ bool VulkanProcTable::AreDeviceProcsSetup() const { return device_; } -bool VulkanProcTable::SetupLoaderProcAddresses() { +bool VulkanProcTable::SetupGetInstanceProcAddress() { if (!handle_) { return true; } - GetInstanceProcAddr = + GetInstanceProcAddr = reinterpret_cast( #if VULKAN_LINK_STATICALLY - GetInstanceProcAddr = &vkGetInstanceProcAddr; + &vkGetInstanceProcAddr #else // VULKAN_LINK_STATICALLY - reinterpret_cast(const_cast( - handle_->ResolveSymbol("vkGetInstanceProcAddr"))); + const_cast(handle_->ResolveSymbol("vkGetInstanceProcAddr")) #endif // VULKAN_LINK_STATICALLY - + ); if (!GetInstanceProcAddr) { FML_DLOG(WARNING) << "Could not acquire vkGetInstanceProcAddr."; return false; } + return true; +} + +bool VulkanProcTable::SetupLoaderProcAddresses() { VulkanHandle null_instance(VK_NULL_HANDLE, nullptr); ACQUIRE_PROC(CreateInstance, null_instance); @@ -157,7 +168,11 @@ bool VulkanProcTable::OpenLibraryHandle(const char* path) { #else // VULKAN_LINK_STATICALLY handle_ = fml::NativeLibrary::Create(path); #endif // VULKAN_LINK_STATICALLY - return !!handle_; + if (!handle_) { + FML_DLOG(WARNING) << "Could not open Vulkan library handle: " << path; + return false; + } + return true; } bool VulkanProcTable::CloseLibraryHandle() { @@ -173,7 +188,8 @@ PFN_vkVoidFunction VulkanProcTable::AcquireProc( } // A VK_NULL_HANDLE as the instance is an acceptable parameter. - return GetInstanceProcAddr(instance, proc_name); + return reinterpret_cast( + GetInstanceProcAddr(instance, proc_name)); } PFN_vkVoidFunction VulkanProcTable::AcquireProc( diff --git a/vulkan/vulkan_proc_table.h b/vulkan/vulkan_proc_table.h index a7f2a3d5f35b8..93b4d0a3bfdd2 100644 --- a/vulkan/vulkan_proc_table.h +++ b/vulkan/vulkan_proc_table.h @@ -48,6 +48,12 @@ class VulkanProcTable : public fml::RefCountedThreadSafe { T proc_; }; + VulkanProcTable(); + explicit VulkanProcTable(const char* so_path); + explicit VulkanProcTable( + std::function get_instance_proc_addr); + ~VulkanProcTable(); + bool HasAcquiredMandatoryProcAddresses() const; bool IsValid() const; @@ -62,6 +68,8 @@ class VulkanProcTable : public fml::RefCountedThreadSafe { GrVkGetProc CreateSkiaGetProc() const; + std::function GetInstanceProcAddr = nullptr; + #define DEFINE_PROC(name) Proc name; DEFINE_PROC(AcquireNextImageKHR); @@ -98,7 +106,6 @@ class VulkanProcTable : public fml::RefCountedThreadSafe { DEFINE_PROC(GetDeviceProcAddr); DEFINE_PROC(GetDeviceQueue); DEFINE_PROC(GetImageMemoryRequirements); - DEFINE_PROC(GetInstanceProcAddr); DEFINE_PROC(GetPhysicalDeviceFeatures); DEFINE_PROC(GetPhysicalDeviceQueueFamilyProperties); DEFINE_PROC(QueueSubmit); @@ -135,10 +142,8 @@ class VulkanProcTable : public fml::RefCountedThreadSafe { VulkanHandle instance_; VulkanHandle device_; - VulkanProcTable(); - explicit VulkanProcTable(const char* path); - ~VulkanProcTable(); bool OpenLibraryHandle(const char* path); + bool SetupGetInstanceProcAddress(); bool SetupLoaderProcAddresses(); bool CloseLibraryHandle(); PFN_vkVoidFunction AcquireProc( diff --git a/vulkan/vulkan_swapchain.cc b/vulkan/vulkan_swapchain.cc index e33fd9742bcbc..7c3792d3fb571 100644 --- a/vulkan/vulkan_swapchain.cc +++ b/vulkan/vulkan_swapchain.cc @@ -350,7 +350,7 @@ VulkanSwapchain::AcquireResult VulkanSwapchain::AcquireSurface() { // --------------------------------------------------------------------------- // Step 2: - // Put semaphores in unsignaled state. + // Put fences in an unsignaled state. // --------------------------------------------------------------------------- if (!backbuffer->ResetFences()) { FML_DLOG(INFO) << "Could not reset fences."; diff --git a/vulkan/vulkan_window.cc b/vulkan/vulkan_window.cc index 1cee0820c615f..cf7730c7aa706 100644 --- a/vulkan/vulkan_window.cc +++ b/vulkan/vulkan_window.cc @@ -19,24 +19,21 @@ namespace vulkan { VulkanWindow::VulkanWindow(fml::RefPtr proc_table, - std::unique_ptr native_surface, - bool render_to_surface) + std::unique_ptr native_surface) : VulkanWindow(/*context/*/ nullptr, proc_table, - std::move(native_surface), - render_to_surface) {} + std::move(native_surface)) {} VulkanWindow::VulkanWindow(const sk_sp& context, fml::RefPtr proc_table, - std::unique_ptr native_surface, - bool render_to_surface) + std::unique_ptr native_surface) : valid_(false), vk(std::move(proc_table)), skia_gr_context_(context) { if (!vk || !vk->HasAcquiredMandatoryProcAddresses()) { FML_DLOG(INFO) << "Proc table has not acquired mandatory proc addresses."; return; } - if (native_surface == nullptr || !native_surface->IsValid()) { + if (native_surface && !native_surface->IsValid()) { FML_DLOG(INFO) << "Native surface is invalid."; return; } @@ -70,9 +67,7 @@ VulkanWindow::VulkanWindow(const sk_sp& context, return; } - // TODO(38466): Refactor GPU surface APIs take into account the fact that an - // external view embedder may want to render to the root surface. - if (!render_to_surface) { + if (!native_surface) { return; } diff --git a/vulkan/vulkan_window.h b/vulkan/vulkan_window.h index 685825024f7b8..bbaba38d47e1b 100644 --- a/vulkan/vulkan_window.h +++ b/vulkan/vulkan_window.h @@ -36,8 +36,7 @@ class VulkanWindow { /// GrDirectContext. /// VulkanWindow(fml::RefPtr proc_table, - std::unique_ptr native_surface, - bool render_to_surface); + std::unique_ptr native_surface); //------------------------------------------------------------------------------ /// @brief Construct a VulkanWindow. Let reuse an existing @@ -45,8 +44,7 @@ class VulkanWindow { /// VulkanWindow(const sk_sp& context, fml::RefPtr proc_table, - std::unique_ptr native_surface, - bool render_to_surface); + std::unique_ptr native_surface); ~VulkanWindow();