diff options
| author | Yilong Li <liyl@google.com> | 2025-02-16 01:13:24 -0800 |
|---|---|---|
| committer | Charles Giessen <46324611+charles-lunarg@users.noreply.github.com> | 2025-02-18 04:02:55 -0600 |
| commit | dbe142e8f3a7f11478c2e4741c0d4c4b748fce4b (patch) | |
| tree | bcc7e9fba1b8eb73d01270bb2e2896a65495a441 /cube | |
| parent | 8a7c2760f69a13ca333eea3066f5e29423557be0 (diff) | |
| download | usermoji-dbe142e8f3a7f11478c2e4741c0d4c4b748fce4b.tar.xz | |
vkcubepp: Support Fuchsia
This change was originally in Fuchsia's Vulkan-Tools fork
(https://fxrev.dev/441056). It adds Fuchsia OS
(https://fuchsia.dev) support to vkcubepp.
Fuchsia has two WSI platforms available: It can render directly to
display frame buffer (fuchsia_display), or use Fuchsia's Scenic
compositor (fuchsia_scenic).
All Fuchsia-specific code is under VK_USE_PLATFORM_FUCHSIA ifdef
build guards and is only compiled for Fuchsia targets.
This change also adds a BUILD.gn file used for Fuchsia in-tree
builds.
Test: vkcube-on-fb and vkcube-on-scenic on Fuchsia
Bug: https://fxbug.dev/378964821
Change-Id: Id4627bf209b4fc9400ce7f6847324cad2060c31c
Diffstat (limited to 'cube')
| -rw-r--r-- | cube/BUILD.gn | 152 | ||||
| -rw-r--r-- | cube/cube.cpp | 248 | ||||
| -rw-r--r-- | cube/fuchsia/flatland_view.cpp | 210 | ||||
| -rw-r--r-- | cube/fuchsia/flatland_view.h | 102 | ||||
| -rw-r--r-- | cube/fuchsia/meta/vkcube-on-fb.cml | 26 | ||||
| -rw-r--r-- | cube/fuchsia/meta/vkcube-on-scenic.cml | 36 |
6 files changed, 771 insertions, 3 deletions
diff --git a/cube/BUILD.gn b/cube/BUILD.gn new file mode 100644 index 00000000..a6a1e4ae --- /dev/null +++ b/cube/BUILD.gn @@ -0,0 +1,152 @@ +# Copyright (C) 2025 The Fuchsia Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +assert(is_fuchsia, "This build file works on Fuchsia GN build only.") + +import("//build/compiled_action.gni") +import("//build/components.gni") +import("//build_overrides/vulkan_tools.gni") +import("//src/lib/vulkan/layers.gni") + +group("cube") { + testonly = true + deps = [ + ":vkcube-on-fb", + ":vkcube-on-scenic", + ] +} + +fuchsia_test_component("vkcube-on-fb-component") { + component_name = "vkcube-on-fb" + deps = [ + ":vkcube", + "//src/lib/vulkan/swapchain:image_pipe_swapchain_fb_layer", + "//src/lib/vulkan/validation_layers", + ] + manifest = "fuchsia/meta/vkcube-on-fb.cml" + test_type = "system" +} + +fuchsia_test_package("vkcube-on-fb") { + test_components = [ ":vkcube-on-fb-component" ] + test_specs = { + # vkcube runs forever, so only run manually. + environments = [] + } +} + +fuchsia_component("vkcube-on-scenic-component") { + component_name = "vkcube-on-scenic" + deps = [ + ":vkcube", + "//src/lib/vulkan/swapchain:image_pipe_swapchain_layer", + "//src/lib/vulkan/validation_layers", + ] + + manifest = "fuchsia/meta/vkcube-on-scenic.cml" +} + +fuchsia_package("vkcube-on-scenic") { + deps = [ ":vkcube-on-scenic-component" ] +} + +config("cube_config") { + cube_frag_inc = "$target_gen_dir/cube.frag.inc" + cube_vert_inc = "$target_gen_dir/cube.vert.inc" + + cube_frag_inc_rebased = rebase_path(cube_frag_inc, root_build_dir) + cube_vert_inc_rebased = rebase_path(cube_vert_inc, root_build_dir) + + inputs = [ + cube_frag_inc, + cube_vert_inc, + ] + + defines = [ + "CUBE_FRAG_INC=\"$cube_frag_inc_rebased\"", + "CUBE_VERT_INC=\"$cube_vert_inc_rebased\"", + "VULKAN_HPP_NO_NODISCARD_WARNINGS", + ] + + include_dirs = [ root_build_dir ] + + cflags = [ "-Wno-implicit-int-float-conversion" ] +} + +executable("vkcube") { + defines = [] + if (defined(texture_ppm_h)) { + path = rebase_path(texture_ppm_h, root_build_dir) + defines += [ "TEXTURE_PPM_H=\"$path\"" ] + } + + sources = [ + "cube.cpp", + "fuchsia/flatland_view.cpp", + "fuchsia/flatland_view.h", + ] + + configs += [ + ":cube_config", + "${volk_dir}:volk_config", + ] + + deps = [ + ":glslang_frag", + ":glslang_vert", + "//sdk/fidl/fuchsia.ui.app:fuchsia.ui.app_cpp", + "//sdk/fidl/fuchsia.ui.composition:fuchsia.ui.composition_cpp", + "//sdk/fidl/fuchsia.ui.views:fuchsia.ui.views_cpp", + "//sdk/lib/async-loop:async-loop-cpp", + "//sdk/lib/component/incoming/cpp", + "//sdk/lib/component/outgoing/cpp", + "//sdk/lib/syslog/cpp", + "//sdk/lib/ui/scenic/cpp", + "//third_party/Vulkan-Headers/src:vulkan_headers", + "//third_party/Vulkan-Loader:libvulkan", + "//zircon/system/ulib/syslog", + ] +} + +compiled_action("glslang_frag") { + tool = "//third_party/glslang:glslang_validator" + outputs = [ "$target_gen_dir/cube.frag.inc" ] + + sources = [ "cube.frag" ] + + args = [ + "--quiet", + "-V", + "-x", + "-o", + rebase_path("$target_gen_dir/cube.frag.inc", root_build_dir), + rebase_path("cube.frag", root_build_dir), + ] +} + +compiled_action("glslang_vert") { + tool = "//third_party/glslang:glslang_validator" + outputs = [ "$target_gen_dir/cube.vert.inc" ] + + sources = [ "cube.vert" ] + + args = [ + "--quiet", + "-V", + "-x", + "-o", + rebase_path("$target_gen_dir/cube.vert.inc", root_build_dir), + rebase_path("cube.vert", root_build_dir), + ] +} diff --git a/cube/cube.cpp b/cube/cube.cpp index 45c3afd0..12cc2dfb 100644 --- a/cube/cube.cpp +++ b/cube/cube.cpp @@ -2,6 +2,7 @@ * Copyright (c) 2015-2019 The Khronos Group Inc. * Copyright (c) 2015-2019 Valve Corporation * Copyright (c) 2015-2019 LunarG, Inc. + * Copyright (c) 2025 The Fuchsia Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,6 +20,7 @@ * Author: Charles Giessen <charles@lunarg.com> */ +#include <algorithm> #include <cassert> #include <cinttypes> #include <cstdio> @@ -41,6 +43,15 @@ #include <linux/input.h> #include "wayland_loader.h" #endif +#if defined(VK_USE_PLATFORM_FUCHSIA) +#include <fidl/fuchsia.ui.app/cpp/fidl.h> +#include <lib/async-loop/cpp/loop.h> +#include <lib/component/incoming/cpp/protocol.h> +#include <lib/component/outgoing/cpp/outgoing_directory.h> +#include <lib/zx/result.h> + +#include "fuchsia/flatland_view.h" +#endif #define VULKAN_HPP_DISPATCH_LOADER_DYNAMIC 1 #define VULKAN_HPP_NO_EXCEPTIONS @@ -212,6 +223,8 @@ enum class WsiPlatform { wayland, directfb, display, + fuchsia_display, + fuchsia_scenic, invalid, // Sentinel just to indicate invalid user input }; @@ -244,6 +257,10 @@ WsiPlatform wsi_from_string(std::string const &str) { #if defined(VK_USE_PLATFORM_DISPLAY_KHR) if (str == "display") return WsiPlatform::display; #endif +#if defined(VK_USE_PLATFORM_FUCHSIA) + if (str == "fuchsia_display") return WsiPlatform::fuchsia_display; + if (str == "fuchsia_scenic") return WsiPlatform::fuchsia_scenic; +#endif return WsiPlatform::invalid; }; @@ -287,6 +304,12 @@ const char *wsi_to_string(WsiPlatform wsi_platform) { case (WsiPlatform::display): return "display"; #endif +#if defined(VK_USE_PLATFORM_FUCHSIA) + case (WsiPlatform::fuchsia_display): + return "fuchsia_display"; + case (WsiPlatform::fuchsia_scenic): + return "fuchsia_scenic"; +#endif default: return "unknown"; } @@ -389,6 +412,12 @@ struct Demo { void run(); void create_window(); #endif +#if defined(VK_USE_PLATFORM_FUCHSIA) + // Returns a layer name of static storage duration. + const char *get_fuchsia_image_pipe_layer() const; + void create_flatland_view(); + void run(); +#endif std::string name = "vkcubepp"; // Name to put on the window/icon #if defined(VK_USE_PLATFORM_WIN32_KHR) @@ -438,6 +467,14 @@ struct Demo { screen_window_t screen_window = nullptr; screen_event_t screen_event = nullptr; #endif +#if defined(VK_USE_PLATFORM_FUCHSIA) + fuchsia_ui_views::ViewCreationToken view_creation_token; + async::Loop loop{&kAsyncLoopConfigNeverAttachToThread}; + fidl::ClientEnd<fuchsia_io::Directory> incoming; + std::unique_ptr<component::OutgoingDirectory> outgoing; + std::unique_ptr<FlatlandViewProviderService> view_provider_service; + std::unique_ptr<FlatlandView> flatland_view; +#endif WsiPlatform wsi_platform = WsiPlatform::auto_; vk::SurfaceKHR surface; bool prepared = false; @@ -1116,6 +1153,12 @@ void Demo::init(int argc, char **argv) { if (!wsi_platforms.empty()) wsi_platforms.append("|"); wsi_platforms.append("qnx"); #endif +#if defined(VK_USE_PLATFORM_FUCHSIA) + if (!wsi_platforms.empty()) wsi_platforms.append("|"); + wsi_platforms.append("fuchsia_display"); + if (!wsi_platforms.empty()) wsi_platforms.append("|"); + wsi_platforms.append("fuchsia_scenic"); +#endif std::stringstream usage; usage << "Usage:\n " << APP_SHORT_NAME << "\t[--use_staging] [--validate]\n" << "\t[--break] [--c <framecount>] [--suppress_popups]\n" @@ -1327,6 +1370,11 @@ void Demo::check_and_set_wsi_platform() { } } #endif +#if defined(VK_USE_PLATFORM_FUCHSIA) + if (wsi_platform == WsiPlatform::auto_) { + ERR_EXIT("auto WSI platform is not supported on Fuchsia", "check_and_set_wsi_platform error"); + } +#endif } #if defined(VK_USE_PLATFORM_DISPLAY_KHR) int find_display_gpu(int gpu_number, const std::vector<vk::PhysicalDevice> &physical_devices) { @@ -1479,7 +1527,19 @@ void Demo::init_vk() { vk::Bool32 platformSurfaceExtFound = VK_FALSE; bool portabilityEnumerationActive = false; - auto instance_extensions_return = vk::enumerateInstanceExtensionProperties(); + // Some platforms (for example, Fuchsia) may have their WSI instance + // extension in layers and require the client to manually specify the + // instance extension layer. + vk::Optional<const std::string> instance_extension_layer = nullptr; +#if VK_USE_PLATFORM_FUCHSIA + const char *image_pipe_layer_name = get_fuchsia_image_pipe_layer(); + enabled_layers.push_back(image_pipe_layer_name); + + const std::string image_pipe_layer_name_str = image_pipe_layer_name; + instance_extension_layer = image_pipe_layer_name_str; +#endif // VK_USE_PLATFORM_FUCHSIA + + auto instance_extensions_return = vk::enumerateInstanceExtensionProperties(instance_extension_layer); VERIFY(instance_extensions_return.result == vk::Result::eSuccess); for (const auto &extension : instance_extensions_return.value) { @@ -1553,6 +1613,12 @@ void Demo::init_vk() { enabled_instance_extensions.push_back(VK_QNX_SCREEN_SURFACE_EXTENSION_NAME); } #endif +#if defined(VK_USE_PLATFORM_FUCHSIA) + else if (!strcmp(VK_FUCHSIA_IMAGEPIPE_SURFACE_EXTENSION_NAME, extension.extensionName)) { + platformSurfaceExtFound = 1; + enabled_instance_extensions.push_back(VK_FUCHSIA_IMAGEPIPE_SURFACE_EXTENSION_NAME); + } +#endif } if (!surfaceExtFound) { @@ -1638,6 +1704,16 @@ void Demo::init_vk() { "vkCreateInstance Failure"); } #endif +#if defined(VK_USE_PLATFORM_FUCHSIA) + if (wsi_platform == WsiPlatform::fuchsia_display || wsi_platform == WsiPlatform::fuchsia_scenic) { + ERR_EXIT("vkEnumerateInstanceExtensionProperties failed to find the " VK_FUCHSIA_IMAGEPIPE_SURFACE_EXTENSION_NAME + " extension.\n\nDo you have a compatible " + "Vulkan installable client driver (ICD) installed?\nPlease " + "look at the Getting Started guide for additional " + "information.\n", + "vkCreateInstance Failure"); + } +#endif ERR_EXIT( "vkEnumerateInstanceExtensionProperties failed to find any supported WSI surface extension.\n\n" "Do you have a compatible Vulkan installable client driver (ICD) installed?\n" @@ -1780,7 +1856,22 @@ void Demo::select_physical_device() { /* Look for device extensions */ vk::Bool32 swapchainExtFound = VK_FALSE; - auto device_extension_return = gpu.enumerateDeviceExtensionProperties(); + // Some platforms (for example, Fuchsia) may have their device swapchain + // extension in layers and require the client to manually specify the + // device swapchain extension layer. + vk::Optional<const std::string> device_extension_layer = nullptr; +#if VK_USE_PLATFORM_FUCHSIA + const std::string image_pipe_layer_name = get_fuchsia_image_pipe_layer(); + device_extension_layer = image_pipe_layer_name; +#endif // VK_USE_PLATFORM_FUCHSIA + + // Currently we assume that the device swapchain extension layer is always + // enabled when the instance is initialized. + assert((!device_extension_layer) || (std::any_of(enabled_layers.begin(), enabled_layers.end(), [&](const char *layer_name) { + return *device_extension_layer == layer_name; + }))); + + auto device_extension_return = gpu.enumerateDeviceExtensionProperties(device_extension_layer); VERIFY(device_extension_return.result == vk::Result::eSuccess); for (const auto &extension : device_extension_return.value) { @@ -1879,6 +1970,21 @@ void Demo::create_surface() { VERIFY(result == vk::Result::eSuccess); } #endif +#if defined(VK_USE_PLATFORM_FUCHSIA) + if (wsi_platform == WsiPlatform::fuchsia_display) { + auto createInfo = vk::ImagePipeSurfaceCreateInfoFUCHSIA(); + auto result = inst.createImagePipeSurfaceFUCHSIA(&createInfo, nullptr, &surface); + VERIFY(result == vk::Result::eSuccess); + } + if (wsi_platform == WsiPlatform::fuchsia_scenic) { + auto createInfo = vk::ImagePipeSurfaceCreateInfoFUCHSIA(); + // We are using ImagePipeSurface here, but it is being migrated to Flatland and the handle parameter is Flatland's + // ViewCreationToken during this process. + createInfo.setImagePipeHandle(std::move(view_creation_token).value().release()); + auto result = inst.createImagePipeSurfaceFUCHSIA(&createInfo, nullptr, &surface); + VERIFY(result == vk::Result::eSuccess); + } +#endif } void Demo::init_vk_swapchain() { @@ -2394,7 +2500,11 @@ void Demo::prepare_framebuffers() { vk::ShaderModule Demo::prepare_fs() { const uint32_t fragShaderCode[] = { +#ifdef CUBE_FRAG_INC +#include CUBE_FRAG_INC +#else #include "cube.frag.inc" +#endif }; frag_shader_module = prepare_shader_module(fragShaderCode, sizeof(fragShaderCode)); @@ -2725,7 +2835,11 @@ void Demo::prepare_textures() { vk::ShaderModule Demo::prepare_vs() { const uint32_t vertShaderCode[] = { +#ifdef CUBE_VERT_INC +#include CUBE_VERT_INC +#else #include "cube.vert.inc" +#endif }; vert_shader_module = prepare_shader_module(vertShaderCode, sizeof(vertShaderCode)); @@ -2857,7 +2971,11 @@ void Demo::update_data_buffer() { } /* Convert ppm image data from header file into RGBA texture image */ +#ifdef TEXTURE_PPM_H +#include TEXTURE_PPM_H +#else #include "lunarg.ppm.h" +#endif bool Demo::loadTexture(const char *filename, uint8_t *rgba_data, vk::SubresourceLayout &layout, uint32_t &width, uint32_t &height) { (void)filename; char *cPtr; @@ -3637,6 +3755,115 @@ void Demo::create_window() { } } #endif +#if defined(VK_USE_PLATFORM_FUCHSIA) + +const char *Demo::get_fuchsia_image_pipe_layer() const { + switch (wsi_platform) { + case (WsiPlatform::fuchsia_display): + return "VK_LAYER_FUCHSIA_imagepipe_swapchain_fb"; + case (WsiPlatform::fuchsia_scenic): + return "VK_LAYER_FUCHSIA_imagepipe_swapchain"; + case (WsiPlatform::auto_): + ERR_EXIT("auto WSI platform is not supported on Fuchsia", "get_fuchsia_image_pipe_layer failure"); + default: + ERR_EXIT("Invalid WSI platform", "get_fuchsia_image_pipe_layer failure"); + } +} + +void Demo::create_flatland_view() { + if (flatland_view) return; + + zx::result<fidl::ClientEnd<fuchsia_io::Directory> > incoming_result = component::OpenServiceRoot(); + if (incoming_result.is_error()) { + printf("Failed to open incoming directory: %s\n", incoming_result.status_string()); + ERR_EXIT("Failed to open incoming directory", "create_flatland_view failure"); + } + incoming = std::move(incoming_result).value(); + + outgoing = std::make_unique<component::OutgoingDirectory>(loop.dispatcher()); + + FlatlandViewProviderService::CreateView2Callback create_view_callback = [this](fuchsia_ui_app::CreateView2Args args) { + auto resize_callback = [this](uint32_t width, uint32_t height) { + this->width = width; + this->height = height; + if (prepared) { + resize(); + } + }; + + flatland_view = + FlatlandView::Create(incoming.borrow(), std::move(*args.view_creation_token()), resize_callback, loop.dispatcher()); + if (!flatland_view) ERR_EXIT("Failed to created FlatlandView", "create_flatland_view failure"); + + view_creation_token = flatland_view->TakeChildViewCreationToken(); + }; + + view_provider_service = std::make_unique<FlatlandViewProviderService>(std::move(create_view_callback), loop.dispatcher()); + + zx::result<> add_protocol_result = outgoing->AddUnmanagedProtocol<fuchsia_ui_app::ViewProvider>( + [view_provider_service = view_provider_service.get()](fidl::ServerEnd<fuchsia_ui_app::ViewProvider> server_end) { + view_provider_service->HandleViewProviderRequest(std::move(server_end)); + }); + if (add_protocol_result.is_error()) { + printf("Failed to add protocol to outgoing directory: %s\n", add_protocol_result.status_string()); + ERR_EXIT("Failed to add protocol to outgoing directory", "create_flatland_view failure"); + } + + zx::result<> serve_result = outgoing->ServeFromStartupInfo(); + if (serve_result.is_error()) { + printf("Failed to serve outgoing directory: %s\n", serve_result.status_string()); + ERR_EXIT("Failed to serve outgoing directory", "create_flatland_view failure"); + } + + zx_status_t loop_status = ZX_OK; + + // Run message loop until view has been created. + while (!quit && !flatland_view && loop_status == ZX_OK) { + loop_status = loop.RunUntilIdle(); + } +} + +void Demo::run() { + uint32_t num_frames = 60; + uint32_t elapsed_frames = 0; + static const float kMsPerSec = 1000; + + double total_ms = 0; + auto t0 = std::chrono::high_resolution_clock::now(); + + while (!quit) { + if (wsi_platform == WsiPlatform::fuchsia_scenic) { + if (loop.RunUntilIdle() != ZX_OK) { + break; + } + } + auto t1 = std::chrono::high_resolution_clock::now(); + std::chrono::duration<double, std::milli> elapsed = t1 - t0; + total_ms += elapsed.count(); + t0 = t1; + + if (elapsed_frames && (elapsed_frames % num_frames) == 0) { + float fps = static_cast<float>(num_frames / (total_ms / kMsPerSec)); + printf("Framerate average for last %u frames: %f frames per second\n", num_frames, fps); + fflush(stdout); + total_ms = 0; + // attempt to log once per second + num_frames = static_cast<uint32_t>(fps); + elapsed_frames = 0; + } + + draw(); + + curFrame++; + elapsed_frames++; + + if (frameCount != UINT32_MAX && curFrame == frameCount) { + quit = true; + } + } +} + +#endif #if _WIN32 // Include header required for parsing the command line options. @@ -3784,7 +4011,8 @@ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR pCmdLine, return static_cast<int>(msg.wParam); } -#elif defined(__linux__) || defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__QNX__) || defined(__GNU__) +#elif defined(__linux__) || defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__QNX__) || defined(__GNU__) || \ + defined(__Fuchsia__) int main(int argc, char **argv) { Demo demo; @@ -3829,6 +4057,14 @@ int main(int argc, char **argv) { // nothing to do here break; #endif +#if defined(VK_USE_PLATFORM_FUCHSIA) + case (WsiPlatform::fuchsia_display): + // nothing to do here + break; + case (WsiPlatform::fuchsia_scenic): + demo.create_flatland_view(); + break; +#endif } demo.create_surface(); @@ -3877,6 +4113,12 @@ int main(int argc, char **argv) { demo.run(); break; #endif +#if defined(VK_USE_PLATFORM_FUCHSIA) + case (WsiPlatform::fuchsia_display): + case (WsiPlatform::fuchsia_scenic): + demo.run(); + break; +#endif } demo.cleanup(); diff --git a/cube/fuchsia/flatland_view.cpp b/cube/fuchsia/flatland_view.cpp new file mode 100644 index 00000000..029c07d3 --- /dev/null +++ b/cube/fuchsia/flatland_view.cpp @@ -0,0 +1,210 @@ +// Copyright (c) 2025 The Fuchsia Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "flatland_view.h" + +#include <fidl/fuchsia.ui.app/cpp/fidl.h> +#include <fidl/fuchsia.ui.composition/cpp/fidl.h> +#include <fidl/fuchsia.ui.views/cpp/fidl.h> +#include <lib/component/incoming/cpp/protocol.h> +#include <lib/component/outgoing/cpp/outgoing_directory.h> +#include <lib/syslog/cpp/macros.h> +#include <lib/syslog/global.h> +#include <lib/ui/scenic/cpp/view_creation_tokens.h> +#include <lib/ui/scenic/cpp/view_identity.h> + +#include <cinttypes> + +namespace { + +const char* const kTag = "FlatlandView"; + +const fuchsia_ui_composition::TransformId kRootTransform = {1}; +const fuchsia_ui_composition::ContentId kViewport = {1}; + +} // namespace + +// static +std::unique_ptr<FlatlandView> FlatlandView::Create(fidl::UnownedClientEnd<fuchsia_io::Directory> service_directory, + fuchsia_ui_views::ViewCreationToken view_creation_token, + ResizeCallback resize_callback, async_dispatcher_t* dispatcher) { + ZX_DEBUG_ASSERT(dispatcher != nullptr); + auto view = std::make_unique<FlatlandView>(std::move(resize_callback), dispatcher); + if (!view) return nullptr; + if (!view->Init(service_directory, std::move(view_creation_token))) return nullptr; + return view; +} + +FlatlandView::FlatlandView(ResizeCallback resize_callback, async_dispatcher_t* dispatcher) + : resize_callback_(std::move(resize_callback)), dispatcher_(dispatcher) { + ZX_DEBUG_ASSERT(dispatcher != nullptr); +} + +bool FlatlandView::Init(fidl::UnownedClientEnd<fuchsia_io::Directory> service_directory, + fuchsia_ui_views::ViewCreationToken view_creation_token) { + zx::result<fidl::ClientEnd<fuchsia_ui_composition::Flatland>> connect_result = + component::ConnectAt<fuchsia_ui_composition::Flatland>(service_directory); + if (connect_result.is_error()) { + FX_LOGS(ERROR) << "Failed to connect to Flatland: " << connect_result.status_string(); + return false; + } + fidl::ClientEnd<fuchsia_ui_composition::Flatland> flatland_client = std::move(connect_result).value(); + flatland_.Bind(std::move(flatland_client), dispatcher_, /*event_handler=*/this); + fit::result<fidl::OneWayError> set_debug_name_result = flatland_->SetDebugName({{.name = kTag}}); + if (set_debug_name_result.is_error()) { + FX_LOGS(ERROR) << "Failed to set debug name: " << set_debug_name_result.error_value().FormatDescription(); + } + + fit::result<fidl::OneWayError> create_transform_result = flatland_->CreateTransform(kRootTransform); + if (create_transform_result.is_error()) { + FX_LOGS(ERROR) << "Failed to call CreateTransform: " << create_transform_result.error_value().FormatDescription(); + return false; + } + + fit::result<fidl::OneWayError> set_root_transform_result = flatland_->SetRootTransform(kRootTransform); + if (set_root_transform_result.is_error()) { + FX_LOGS(ERROR) << "Failed to call SetRootTransform: " << set_root_transform_result.error_value().FormatDescription(); + return false; + } + + auto [parent_viewport_watcher_client, parent_viewport_watcher_server] = + fidl::Endpoints<fuchsia_ui_composition::ParentViewportWatcher>::Create(); + fit::result<fidl::OneWayError> create_view2_result = flatland_->CreateView2({{ + .token = std::move(view_creation_token), + .view_identity = scenic::cpp::NewViewIdentityOnCreation(), + .protocols = {}, + .parent_viewport_watcher = std::move(parent_viewport_watcher_server), + }}); + if (create_view2_result.is_error()) { + FX_LOGS(ERROR) << "Failed to call CreateView2: " << set_root_transform_result.error_value().FormatDescription(); + return false; + } + + parent_viewport_watcher_.Bind(std::move(parent_viewport_watcher_client), dispatcher_); + parent_viewport_watcher_->GetLayout().Then( + [this](fidl::Result<fuchsia_ui_composition::ParentViewportWatcher::GetLayout>& result) { + if (result.is_error()) { + FX_LOGS(ERROR) << "GetLayout() failed: " << result.error_value().FormatDescription(); + return; + } + OnGetLayout(std::move(result.value().info())); + }); + + zx::channel::create(0, &viewport_creation_token_.value(), &child_view_creation_token_.value()); + + return true; +} + +void FlatlandView::OnGetLayout(fuchsia_ui_composition::LayoutInfo info) { + ZX_DEBUG_ASSERT(info.logical_size().has_value()); + const fuchsia_math::SizeU& logical_size = info.logical_size().value(); + resize_callback_(logical_size.width(), logical_size.height()); + + fuchsia_ui_composition::ViewportProperties properties = {{.logical_size = logical_size}}; + if (viewport_creation_token_.value().is_valid()) { + // The first time that we receive layout information, create a viewport using the token that was stashed during Init(). + // External code will attach a view to this viewport via the token obtained from TakeChildViewCreationToken(). + auto [child_view_watcher_client, child_view_watcher_server] = + fidl::Endpoints<fuchsia_ui_composition::ChildViewWatcher>::Create(); + fit::result<fidl::OneWayError> create_viewport_result = flatland_->CreateViewport({{ + .viewport_id = kViewport, + .token = std::move(viewport_creation_token_), + .properties = std::move(properties), + .child_view_watcher = std::move(child_view_watcher_server), + }}); + if (create_viewport_result.is_error()) { + FX_LOGS(ERROR) << "Failed to call CreateViewport(): " << create_viewport_result.error_value().FormatDescription(); + return; + } + + fit::result<fidl::OneWayError> set_content_result = flatland_->SetContent({{ + .transform_id = kRootTransform, + .content_id = kViewport, + }}); + if (set_content_result.is_error()) { + FX_LOGS(ERROR) << "Failed to call SetContent(): " << set_content_result.error_value().FormatDescription(); + return; + } + } else { + auto set_viewport_properties_result = flatland_->SetViewportProperties({{ + .viewport_id = kViewport, + .properties = std::move(properties), + }}); + if (set_viewport_properties_result.is_error()) { + FX_LOGS(ERROR) << "Failed to call SetViewportProperties(): " + << set_viewport_properties_result.error_value().FormatDescription(); + return; + } + } + + Present(); + parent_viewport_watcher_->GetLayout().Then( + [this](fidl::Result<fuchsia_ui_composition::ParentViewportWatcher::GetLayout>& result) { + if (result.is_error()) { + FX_LOGS(ERROR) << "GetLayout() failed: " << result.error_value().FormatDescription(); + return; + } + OnGetLayout(std::move(result.value().info())); + }); +} + +void FlatlandView::OnError(fidl::Event<fuchsia_ui_composition::Flatland::OnError>& event) { + FX_LOGF(ERROR, kTag, "OnFlatlandError: %" PRIu32, static_cast<uint32_t>(event.error())); +} + +void FlatlandView::Present() { + if (present_credits_ == 0) { + pending_present_ = true; + return; + } + --present_credits_; + fuchsia_ui_composition::PresentArgs present_args = {{ + .requested_presentation_time = 0, + .acquire_fences = {}, + .release_fences = {}, + .unsquashable = false, + }}; + fit::result<fidl::OneWayError> present_result = flatland_->Present(std::move(present_args)); + if (present_result.is_error()) { + FX_LOGS(ERROR) << "Failed to call Present(): " << present_result.error_value().FormatDescription(); + } +} + +void FlatlandView::OnFramePresented(fidl::Event<fuchsia_ui_composition::Flatland::OnFramePresented>& event) {} + +void FlatlandView::OnNextFrameBegin(fidl::Event<fuchsia_ui_composition::Flatland::OnNextFrameBegin>& event) { + present_credits_ += event.values().additional_present_credits().value_or(0); + if (present_credits_ > 0 && pending_present_) { + Present(); + pending_present_ = false; + } +} + +FlatlandViewProviderService::FlatlandViewProviderService(CreateView2Callback create_view_callback, async_dispatcher_t* dispatcher) + : create_view_callback_(std::move(create_view_callback)), dispatcher_(dispatcher) { + ZX_DEBUG_ASSERT(dispatcher != nullptr); +} + +void FlatlandViewProviderService::CreateViewWithViewRef(CreateViewWithViewRefRequest& request, + CreateViewWithViewRefCompleter::Sync& completer) { + FX_NOTIMPLEMENTED() << "Only Flatland is supported. This is a Gfx ViewProvider method."; +} + +void FlatlandViewProviderService::CreateView2(CreateView2Request& request, CreateView2Completer::Sync& completer) { + create_view_callback_(std::move(request.args())); +} + +void FlatlandViewProviderService::HandleViewProviderRequest(fidl::ServerEnd<fuchsia_ui_app::ViewProvider> server_end) { + bindings_.AddBinding(dispatcher_, std::move(server_end), this, fidl::kIgnoreBindingClosure); +} diff --git a/cube/fuchsia/flatland_view.h b/cube/fuchsia/flatland_view.h new file mode 100644 index 00000000..01a8c6f9 --- /dev/null +++ b/cube/fuchsia/flatland_view.h @@ -0,0 +1,102 @@ +// Copyright (c) 2025 The Fuchsia Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef FUCHSIA_FLATLAND_VIEW_H_ +#define FUCHSIA_FLATLAND_VIEW_H_ + +#include <fidl/fuchsia.io/cpp/fidl.h> +#include <fidl/fuchsia.ui.app/cpp/fidl.h> +#include <fidl/fuchsia.ui.composition/cpp/fidl.h> +#include <fidl/fuchsia.ui.views/cpp/fidl.h> +#include <lib/fit/function.h> +#include <lib/async/dispatcher.h> +#include <lib/syslog/cpp/macros.h> + +#include <cstdint> +#include <utility> + +#include <memory> + +// This class provides a convenient wrapper around the scenic fidl apis needed to get a Flatland +// View. Only SDK available constructs should be used so that this code may be leveraged by Vulkan +// applications built with the SDK. +class FlatlandView : public fidl::AsyncEventHandler<fuchsia_ui_composition::Flatland> { + public: + using ResizeCallback = fit::function<void(uint32_t width, uint32_t height)>; + + // Creates and initializes a `FlatlandView` instance. + // + // `dispatcher` must be non-null. + static std::unique_ptr<FlatlandView> Create(fidl::UnownedClientEnd<fuchsia_io::Directory> service_directory, + fuchsia_ui_views::ViewCreationToken view_creation_token, + ResizeCallback resize_callback, async_dispatcher_t* dispatcher); + + // Production code must use the `Create()` factory method. + // + // `dispatcher` must be non-null. + explicit FlatlandView(ResizeCallback resize_callback, async_dispatcher_t* dispatcher); + + fuchsia_ui_views::ViewCreationToken TakeChildViewCreationToken() { + FX_DCHECK(child_view_creation_token_.value().is_valid()); + return std::move(child_view_creation_token_); + } + + bool Init(fidl::UnownedClientEnd<fuchsia_io::Directory> service_directory, + fuchsia_ui_views::ViewCreationToken view_creation_token); + + private: + void OnNextFrameBegin(fidl::Event<fuchsia_ui_composition::Flatland::OnNextFrameBegin>& event) override; + void OnFramePresented(fidl::Event<fuchsia_ui_composition::Flatland::OnFramePresented>& event) override; + void OnError(fidl::Event<fuchsia_ui_composition::Flatland::OnError>& event) override; + + void OnGetLayout(fuchsia_ui_composition::LayoutInfo info); + void OnFlatlandError(fuchsia_ui_composition::FlatlandError error); + void Present(); + + fidl::SharedClient<fuchsia_ui_composition::Flatland> flatland_; + fidl::SharedClient<fuchsia_ui_composition::ParentViewportWatcher> parent_viewport_watcher_; + + fuchsia_ui_views::ViewportCreationToken viewport_creation_token_; + fuchsia_ui_views::ViewCreationToken child_view_creation_token_; + ResizeCallback resize_callback_; + + int64_t present_credits_ = 1; + bool pending_present_ = false; + + async_dispatcher_t* const dispatcher_; + + friend class FlatlandViewTest; +}; + +class FlatlandViewProviderService : public fidl::Server<fuchsia_ui_app::ViewProvider> { + public: + using CreateView2Callback = fit::function<void(fuchsia_ui_app::CreateView2Args args)>; + + // `dispatcher` must be non-null. + explicit FlatlandViewProviderService(CreateView2Callback create_view_callback, async_dispatcher_t* dispatcher); + + // fuchsia::ui::app::ViewProvider methods. + void CreateViewWithViewRef(CreateViewWithViewRefRequest& request, CreateViewWithViewRefCompleter::Sync& completer) override; + void CreateView2(CreateView2Request& request, CreateView2Completer::Sync& completer) override; + + void HandleViewProviderRequest(fidl::ServerEnd<fuchsia_ui_app::ViewProvider> server_end); + + private: + CreateView2Callback create_view_callback_; + fidl::ServerBindingGroup<fuchsia_ui_app::ViewProvider> bindings_; + + async_dispatcher_t* const dispatcher_; +}; + +#endif // FUCHSIA_FLATLAND_VIEW_H_ diff --git a/cube/fuchsia/meta/vkcube-on-fb.cml b/cube/fuchsia/meta/vkcube-on-fb.cml new file mode 100644 index 00000000..eaa78aae --- /dev/null +++ b/cube/fuchsia/meta/vkcube-on-fb.cml @@ -0,0 +1,26 @@ +// Copyright (c) 2025 The Fuchsia Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +{ + include: [ + "//src/lib/vulkan/swapchain/VkLayer_image_pipe_swapchain_fb.shard.cml", + "sys/testing/elf_test_runner.shard.cml", + ], + program: { + binary: "bin/vkcube", + args: [ + "--wsi", + "fuchsia_display", + ], + }, +} diff --git a/cube/fuchsia/meta/vkcube-on-scenic.cml b/cube/fuchsia/meta/vkcube-on-scenic.cml new file mode 100644 index 00000000..cf940b5b --- /dev/null +++ b/cube/fuchsia/meta/vkcube-on-scenic.cml @@ -0,0 +1,36 @@ +// Copyright (c) 2025 The Fuchsia Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +{ + include: [ "//src/lib/vulkan/swapchain/VkLayer_image_pipe_swapchain.shard.cml" ], + program: { + runner: "elf", + binary: "bin/vkcube", + args: [ + "--wsi", + "fuchsia_scenic", + ], + }, + capabilities: [ + { + protocol: [ "fuchsia.ui.app.ViewProvider" ], + }, + ], + expose: [ + { + protocol: [ "fuchsia.ui.app.ViewProvider" ], + from: "self", + to: "parent", + }, + ], +} |
