blob: 269530413b749b54017cc1ed057f65dcc1637f6a [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"
#include "src/embedder/software_surface_producer.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;
/// 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);
if (embedder->software_surface_producer == nullptr) {
FX_LOG(ERROR, kLogTag, "Software surface producer has not been initialized.");
}
if (!embedder->software_surface_producer->IsValid()) {
// TODO(akbiggs): Maybe recover from this state?
FX_LOGF(ERROR, kLogTag, "Software surface producer has become invalid.");
return false;
}
auto surface = embedder->software_surface_producer->ProduceSurface(
{.width = static_cast<uint32_t>(width), .height = static_cast<uint32_t>(height)});
if (surface == nullptr) {
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 (surface->GetImageId() == 0) {
auto image_id = embedder->flatland_connection->NextContentId().value;
const auto surface_size = 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}, surface->GetBufferCollectionImportToken(), 0, std::move(image_properties));
surface->SetImageId(image_id);
surface->SetReleaseImageCallback([flatland = embedder->flatland_connection.get(), image_id]() {
flatland->flatland()->ReleaseImage({image_id});
});
}
// Enqueue fences for the next present.
embedder->flatland_connection->EnqueueAcquireFence(surface->GetAcquireFence());
embedder->flatland_connection->EnqueueReleaseFence(surface->GetReleaseFence());
// Preserve the surface for when we need to present it.
embedder->software_surface = std::move(surface);
*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();
// Submit surfaces that are being rendered so that we signal their fences.
std::vector<std::unique_ptr<SoftwareSurface>> surfaces_to_submit;
surfaces_to_submit.push_back(std::move(embedder->software_surface));
embedder->software_surface = nullptr;
embedder->software_surface_producer->SubmitSurfaces(std::move(surfaces_to_submit));
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(),
};
// Note: 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;
}
// 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));
}
// 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.
async::Loop loop(&kAsyncLoopConfigAttachToCurrentThread);
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));
}
// The loop must be configured before we create the SoftwareSurfaceProducer
// which connects to services.
// TODO(akbiggs): Inject these into the constructor instead.
embedder.software_surface_producer = std::make_unique<embedder::SoftwareSurfaceProducer>();
// Loop until we're done.
loop.Run();
FX_LOG(INFO, embedder::kLogTag, "Done looping.");
return EXIT_SUCCESS;
}