blob: bcf9228b2e6bc62387606f8520ea5af2898d0f3c [file] [log] [blame]
// Copyright 2022 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//
// Entrypoint for running a Flutter app on Fuchsia.
//
// Usage: ./main <path_to_flutter_asset_bundle>
#include <fuchsia/fonts/cpp/fidl.h>
#include <fuchsia/scenic/scheduling/cpp/fidl.h>
#include <fuchsia/sysmem/cpp/fidl.h>
#include <fuchsia/ui/app/cpp/fidl.h>
#include <fuchsia/ui/composition/cpp/fidl.h>
#include <lib/async-loop/cpp/loop.h>
#include <lib/async-loop/default.h>
#include <lib/async/default.h>
#include <lib/fdio/directory.h>
#include <lib/fidl/cpp/binding_set.h>
#include <lib/fidl/cpp/interface_request.h>
#include <lib/sys/cpp/component_context.h>
#include <lib/syslog/global.h>
#include <lib/ui/scenic/cpp/view_identity.h>
#include <zircon/status.h>
#include <string>
#include "src/embedder/embedder_state.h"
#include "src/embedder/engine/embedder.h"
#include "src/embedder/flatland_ids.h"
#include "src/embedder/flatland_view_provider.h"
#include "src/embedder/logging.h"
#include "src/embedder/software_surface.h"
namespace embedder {
namespace {
// TODO(akbiggs): Don't hardcode this screen size, get it from
// an event instead.
constexpr int kScreenWidth = 1280;
constexpr int kScreenHeight = 800;
/// Gets the current name of the Fuchsia process that
/// is running the embedder.
std::string GetCurrentProcessName() {
char name[ZX_MAX_NAME_LEN];
zx_status_t status = zx::process::self()->get_property(ZX_PROP_NAME, name, sizeof(name));
if (status != ZX_OK) {
FX_LOG(ERROR, kLogTag, "Failed to get process name for sysmem; using \"\".");
return std::string();
}
return std::string(name);
}
/// Gets the current process ID of the Fuchsia process that
/// is running the embedder.
zx_koid_t GetCurrentProcessId() {
zx_info_handle_basic_t info;
zx_status_t status =
zx::process::self()->get_info(ZX_INFO_HANDLE_BASIC, &info, sizeof(info),
nullptr /* actual_count */, nullptr /* avail_count */);
if (status != ZX_OK) {
FX_LOG(ERROR, kLogTag, "Failed to get process ID for sysmem; using ZX_KOID_INVALID.");
return ZX_KOID_INVALID;
}
return info.koid;
}
/// Fuchsia implementation of the Flutter Engine's |vsync_callback|.
void FuchsiaVsync(void* user_data, intptr_t baton) {
EmbedderState* embedder = static_cast<EmbedderState*>(user_data);
// Encourage the Engine to vsync for a 60Hz display.
const uint64_t now_nanos = FlutterEngineGetCurrentTime();
const uint64_t target_vsync_time = now_nanos + 16.6 * 1e6;
FlutterEngineOnVsync(embedder->engine, baton, now_nanos, target_vsync_time);
}
/// Fuchsia implementation of the Flutter Engine's |log_message_callback|.
void FuchsiaLogMessage(const char* tag, const char* message, void* user_data) {
FX_LOG(INFO, tag, message);
}
/// Fuchsia implementation of the Flutter Engine's software rendering
/// |surface_acquire_callback|.
bool FuchsiaAcquireSoftwareSurface(void* user_data, size_t width, size_t height,
uint8_t** allocation, size_t* stride) {
EmbedderState* embedder = static_cast<EmbedderState*>(user_data);
// TODO(akbiggs): Anything we need to do to clean up the old software surface
// explicitly before reassigning here?
embedder->software_surface = std::make_unique<SoftwareSurface>(
embedder->sysmem_allocator, embedder->flatland_allocator,
Size{.width = static_cast<uint32_t>(width), .height = static_cast<uint32_t>(height)});
if (!embedder->software_surface->IsValid()) {
FX_LOGF(ERROR, kLogTag, "Failed to acquire software surface of size (%lu, %lu).", width,
height);
return false;
}
// If we receive an unitialized surface, we need to create a Flatland
// image and associate it with the surface.
if (embedder->software_surface->GetImageId() == 0) {
auto image_id = embedder->flatland_connection->NextContentId().value;
const auto surface_size = embedder->software_surface->GetSize();
fuchsia::math::SizeU image_size;
image_size.width = surface_size.width;
image_size.height = surface_size.height;
fuchsia::ui::composition::ImageProperties image_properties;
image_properties.set_size(image_size);
embedder->flatland_connection->flatland()->CreateImage(
{image_id}, embedder->software_surface->GetBufferCollectionImportToken(), 0,
std::move(image_properties));
embedder->software_surface->SetImageId(image_id);
embedder->software_surface->SetReleaseImageCallback(
[flatland = embedder->flatland_connection.get(), image_id]() {
flatland->flatland()->ReleaseImage({image_id});
});
}
// Enqueue fences for the next present.
embedder->flatland_connection->EnqueueAcquireFence(embedder->software_surface->GetAcquireFence());
embedder->flatland_connection->EnqueueReleaseFence(embedder->software_surface->GetReleaseFence());
// Communicate the location of the allocated memory to Flutter so
// Flutter can render into it.
*allocation = embedder->software_surface->GetAllocation();
*stride = embedder->software_surface->GetBytesPerRow();
return true;
}
/// Fuchsia implementation of the Flutter Engine's software rendering
/// |surface_present_callback|.
bool FuchsiaPresentSoftwareSurface(void* user_data, const void* allocation, size_t row_bytes,
size_t height) {
EmbedderState* embedder = static_cast<EmbedderState*>(user_data);
if (embedder->software_surface == nullptr) {
// We have no surface acquired to present.
return false;
}
// TODO(akbiggs): Replace with composition logic matching
// https://github.com/flutter/engine/blob/main/shell/platform/fuchsia/flutter/flatland_external_view_embedder.cc#L108.
auto image_id = embedder->software_surface->GetImageId();
embedder->flatland_connection->flatland()->SetContent({kRootTransformId}, {image_id});
// TODO(akbiggs): Remove SetImageDestinationSize call once
// https://fuchsia-review.googlesource.com/c/fuchsia/+/696647 lands.
const auto& surface_size = embedder->software_surface->GetSize();
fuchsia::math::SizeU image_size;
image_size.width = surface_size.width;
image_size.height = surface_size.height;
embedder->flatland_connection->flatland()->SetImageDestinationSize({image_id}, image_size);
embedder->flatland_connection->Present();
// Signal to the rendered surface that we've finished writing.
embedder->software_surface->SignalWritesFinished(/* on_read_finished = */ []() {
// TODO(akbiggs): Anything we need to do after the read finishes?
});
return true;
}
/// Runs the Flutter app whose assets (including snapshots) are located
/// at |assets_path| in the component's package.
///
/// The Engine instance will be stored in |embedder| and |embedder| will be
/// the user data for all callbacks from the Flutter Engine.
bool RunFlutterApp(const char* assets_path, EmbedderState* embedder) {
FlutterRendererConfig renderer_config = {
.type = kSoftware,
.software =
{
.struct_size = sizeof(FlutterSoftwareRendererConfig),
.surface_present_callback = FuchsiaPresentSoftwareSurface,
.surface_acquire_callback = FuchsiaAcquireSoftwareSurface,
},
};
// Connect and set up the system font provider.
fuchsia::fonts::ProviderSyncPtr sync_font_provider;
zx_status_t status = embedder->component_context->svc()->Connect<fuchsia::fonts::Provider>(
sync_font_provider.NewRequest());
if (status != ZX_OK) {
FX_LOGF(ERROR, embedder::kLogTag, "Failed to connect to fonts provider service: %s",
zx_status_get_string(status));
}
FlutterProjectArgs project_args = {
.struct_size = sizeof(FlutterProjectArgs),
.assets_path = assets_path,
.vsync_callback = FuchsiaVsync,
.log_message_callback = FuchsiaLogMessage,
.log_tag = "flutter_app",
.font_initialization_data = sync_font_provider.Unbind().TakeChannel().release(),
};
FlutterEngineResult result = FlutterEngineRun(FLUTTER_ENGINE_VERSION, &renderer_config,
&project_args, embedder, &embedder->engine);
if (result != kSuccess || embedder->engine == nullptr) {
FX_LOGF(ERROR, kLogTag, "Could not run the Flutter Engine. Error code: %d",
static_cast<int>(result));
return false;
}
return true;
}
} // namespace
} // namespace embedder
int main(int argc, const char* argv[]) {
if (argc != 2) {
FX_LOG(ERROR, embedder::kLogTag, "usage: executable <path to flutter bundle>");
return EXIT_FAILURE;
}
const char* assets_path = argv[1];
embedder::EmbedderState embedder = {
// This must be initialized before calling RunFlutterApp, as
// this hack https://github.com/flutter/engine/pull/33472 relies
// on it.
.component_context = sys::ComponentContext::Create(),
};
// Serving the component context must happen after RunFlutterApp
// because the embedder platform has a hack that adds inspect data into the
// Dart VM using the component context
// (https://github.com/flutter/engine/pull/33472).
if (!embedder::RunFlutterApp(assets_path, &embedder)) {
return EXIT_FAILURE;
}
// Attaching the async loop to the current thread must happen
// immediately after RunFlutterApp to ensure that bindings in the embedder
// use the embedder's dispatcher to send requests instead of the Dart VM's
// dispatcher.
//
// Bindings created in the embedder before this line will never get fired.
//
// TODO(https://fxbug.dev/75282): Remove the logic in the Dart VM that sets its
// own default dispatcher to reduce the amount of global state.
async::Loop loop(&kAsyncLoopConfigAttachToCurrentThread);
// Necessary to get Flutter to render frames.
FlutterWindowMetricsEvent window_metrics_event = {
.struct_size = sizeof(FlutterWindowMetricsEvent),
// TODO(akbiggs): Don't hardcode these.
.width = embedder::kScreenWidth,
.height = embedder::kScreenHeight,
.pixel_ratio = 1,
.left = 0,
.top = 0,
.physical_view_inset_top = 0,
.physical_view_inset_right = 0,
.physical_view_inset_bottom = 0,
.physical_view_inset_left = 0,
};
FlutterEngineSendWindowMetricsEvent(embedder.engine, &window_metrics_event);
// Connect to Flatland.
fuchsia::ui::composition::FlatlandHandle flatland_handle;
zx_status_t status =
embedder.component_context->svc()->Connect<fuchsia::ui::composition::Flatland>(
flatland_handle.NewRequest());
if (status != ZX_OK) {
FX_LOGF(ERROR, embedder::kLogTag, "Failed to connect to Flatland: %s",
zx_status_get_string(status));
return EXIT_FAILURE;
}
// TODO(akbiggs): Create this on the raster thread per FlatlandConnection's
// documentation.
embedder.flatland_connection = std::make_unique<embedder::FlatlandConnection>(
// TODO(akbiggs): Pick a more appropriate debug name based on the Flutter
// app name.
"FlutterEmbedder" /* debug_name */, std::move(flatland_handle),
/* on_error_callback = */
[]() {
// TODO(akbiggs): What should we do on errors?
},
/* on_frame_presented_callback = */
[](fuchsia::scenic::scheduling::FramePresentedInfo info) {
// TODO(akbiggs): What should we do after a frame finishes presenting?
});
embedder::FlatlandViewProvider view_provider(embedder.flatland_connection.get());
fidl::BindingSet<fuchsia::ui::app::ViewProvider> view_provider_bindings;
status = embedder.component_context->outgoing()->AddPublicService<fuchsia::ui::app::ViewProvider>(
[&view_provider,
&view_provider_bindings](fidl::InterfaceRequest<fuchsia::ui::app::ViewProvider> request) {
view_provider_bindings.AddBinding(&view_provider, std::move(request));
});
if (status != ZX_OK) {
FX_LOGF(ERROR, embedder::kLogTag, "Failed to add ViewProvider service: %s",
zx_status_get_string(status));
return EXIT_FAILURE;
}
// Our run loop's dispatcher must be used to serve the component context in
// order for requests' handlers to get called while we're looping.
status = embedder.component_context->outgoing()->ServeFromStartupInfo(loop.dispatcher());
if (status != ZX_OK) {
FX_LOGF(ERROR, embedder::kLogTag, "Failed to serve component context: %s",
zx_status_get_string(status));
return EXIT_FAILURE;
}
// Set up sysmem allocator for software rendering.
status = fdio_service_connect("/svc/fuchsia.sysmem.Allocator",
embedder.sysmem_allocator.NewRequest().TakeChannel().release());
embedder.sysmem_allocator->SetDebugClientInfo(embedder::GetCurrentProcessName(),
embedder::GetCurrentProcessId());
if (status != ZX_OK) {
FX_LOGF(ERROR, embedder::kLogTag, "Failed to connect to fuchsia.sysmem.Allocator: %s",
zx_status_get_string(status));
return EXIT_FAILURE;
}
// Set up Flatland allocator for software rendering.
status = fdio_service_connect("/svc/fuchsia.ui.composition.Allocator",
embedder.flatland_allocator.NewRequest().TakeChannel().release());
if (status != ZX_OK) {
FX_LOGF(ERROR, embedder::kLogTag, "Failed to connect to fuchsia.ui.composition.Allocator: %s",
zx_status_get_string(status));
return EXIT_FAILURE;
}
// Loop until we're done.
loop.Run();
FX_LOG(INFO, embedder::kLogTag, "Done looping.");
return EXIT_SUCCESS;
}