aboutsummaryrefslogtreecommitdiff
path: root/cube
diff options
context:
space:
mode:
authorYilong Li <liyl@google.com>2025-02-16 01:13:24 -0800
committerCharles Giessen <46324611+charles-lunarg@users.noreply.github.com>2025-02-18 04:02:55 -0600
commitdbe142e8f3a7f11478c2e4741c0d4c4b748fce4b (patch)
treebcc7e9fba1b8eb73d01270bb2e2896a65495a441 /cube
parent8a7c2760f69a13ca333eea3066f5e29423557be0 (diff)
downloadusermoji-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.gn152
-rw-r--r--cube/cube.cpp248
-rw-r--r--cube/fuchsia/flatland_view.cpp210
-rw-r--r--cube/fuchsia/flatland_view.h102
-rw-r--r--cube/fuchsia/meta/vkcube-on-fb.cml26
-rw-r--r--cube/fuchsia/meta/vkcube-on-scenic.cml36
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",
+ },
+ ],
+}