Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Add service protocol method to facilitate getting snapshots #32628

Merged
merged 2 commits into from
Apr 13, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions runtime/service_protocol.cc
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@ const std::string_view ServiceProtocol::kGetSkSLsExtensionName =
const std::string_view
ServiceProtocol::kEstimateRasterCacheMemoryExtensionName =
"_flutter.estimateRasterCacheMemory";
const std::string_view
ServiceProtocol::kRenderFrameWithRasterStatsExtensionName =
"_flutter.renderFrameWithRasterStats";

static constexpr std::string_view kViewIdPrefx = "_flutterView/";
static constexpr std::string_view kListViewsExtensionName =
Expand All @@ -56,6 +59,7 @@ ServiceProtocol::ServiceProtocol()
kGetDisplayRefreshRateExtensionName,
kGetSkSLsExtensionName,
kEstimateRasterCacheMemoryExtensionName,
kRenderFrameWithRasterStatsExtensionName,
}),
handlers_mutex_(fml::SharedMutex::Create()) {}

Expand Down
1 change: 1 addition & 0 deletions runtime/service_protocol.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ class ServiceProtocol {
static const std::string_view kGetDisplayRefreshRateExtensionName;
static const std::string_view kGetSkSLsExtensionName;
static const std::string_view kEstimateRasterCacheMemoryExtensionName;
static const std::string_view kRenderFrameWithRasterStatsExtensionName;

class Handler {
public:
Expand Down
22 changes: 22 additions & 0 deletions shell/common/fixtures/shell_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -299,3 +299,25 @@ void frameCallback(_Image, int) {
// 'ItDoesNotCrashThatSkiaUnrefQueueDrainAfterIOManagerReset'.
// The test is a regression test and doesn't care about images, so it is empty.
}

Picture CreateRedBox(Size size) {
Paint paint = Paint()
..color = Color.fromARGB(255, 255, 0, 0)
..style = PaintingStyle.fill;
PictureRecorder baseRecorder = PictureRecorder();
Canvas canvas = Canvas(baseRecorder);
canvas.drawRect(Rect.fromLTRB(0.0, 0.0, size.width, size.height), paint);
return baseRecorder.endRecording();
}

@pragma('vm:entry-point')
void scene_with_red_box() {
PlatformDispatcher.instance.onBeginFrame = (Duration duration) {
SceneBuilder builder = SceneBuilder();
builder.pushOffset(0.0, 0.0);
builder.addPicture(Offset(0.0, 0.0), CreateRedBox(Size(2.0, 2.0)));
builder.pop();
PlatformDispatcher.instance.views.first.render(builder.build());
};
PlatformDispatcher.instance.scheduleFrame();
}
74 changes: 74 additions & 0 deletions shell/common/shell.cc
Original file line number Diff line number Diff line change
Expand Up @@ -449,6 +449,11 @@ Shell::Shell(DartVMRef vm,
task_runners_.GetRasterTaskRunner(),
std::bind(&Shell::OnServiceProtocolEstimateRasterCacheMemory, this,
std::placeholders::_1, std::placeholders::_2)};
service_protocol_handlers_
[ServiceProtocol::kRenderFrameWithRasterStatsExtensionName] = {
task_runners_.GetRasterTaskRunner(),
std::bind(&Shell::OnServiceProtocolRenderFrameWithRasterStats, this,
std::placeholders::_1, std::placeholders::_2)};
}

Shell::~Shell() {
Expand Down Expand Up @@ -1812,6 +1817,75 @@ bool Shell::OnServiceProtocolSetAssetBundlePath(
return false;
}

static rapidjson::Value SerializeLayerSnapshot(
const LayerSnapshotData& snapshot,
rapidjson::Document* response) {
auto& allocator = response->GetAllocator();
rapidjson::Value result;
result.SetObject();
result.AddMember("layer_unique_id", snapshot.GetLayerUniqueId(), allocator);
result.AddMember("duration_micros", snapshot.GetDuration().ToMicroseconds(),
allocator);
sk_sp<SkData> snapshot_bytes = snapshot.GetSnapshot();
if (snapshot_bytes) {
rapidjson::Value image;
image.SetArray();
const uint8_t* data =
reinterpret_cast<const uint8_t*>(snapshot_bytes->data());
for (size_t i = 0; i < snapshot_bytes->size(); i++) {
image.PushBack(data[i], allocator);
}
result.AddMember("snapshot", image, allocator);
}
return result;
}

bool Shell::OnServiceProtocolRenderFrameWithRasterStats(
const ServiceProtocol::Handler::ServiceProtocolMap& params,
rapidjson::Document* response) {
FML_DCHECK(task_runners_.GetRasterTaskRunner()->RunsTasksOnCurrentThread());

if (auto last_layer_tree = rasterizer_->GetLastLayerTree()) {
auto& allocator = response->GetAllocator();
response->SetObject();
response->AddMember("type", "RenderFrameWithRasterStats", allocator);

// When rendering the last layer tree, we do not need to build a frame,
// invariants in FrameTimingRecorder enforce that raster timings can not be
// set before build-end.
auto frame_timings_recorder = std::make_unique<FrameTimingsRecorder>();
const auto now = fml::TimePoint::Now();
frame_timings_recorder->RecordVsync(now, now);
frame_timings_recorder->RecordBuildStart(now);
frame_timings_recorder->RecordBuildEnd(now);

last_layer_tree->enable_leaf_layer_tracing(true);
rasterizer_->DrawLastLayerTree(std::move(frame_timings_recorder));
last_layer_tree->enable_leaf_layer_tracing(false);

rapidjson::Value snapshots;
snapshots.SetArray();

LayerSnapshotStore& store =
rasterizer_->compositor_context()->snapshot_store();
for (const LayerSnapshotData& data : store) {
snapshots.PushBack(SerializeLayerSnapshot(data, response), allocator);
}

response->AddMember("snapshots", snapshots, allocator);
return true;
} else {
const char* error =
"Failed to render the last frame with raster stats."
" Rasterizer does not hold a valid last layer tree."
" This could happen if this method was invoked before a frame was "
"rendered";
FML_DLOG(ERROR) << error;
ServiceProtocolFailureError(response, error);
return false;
}
}

Rasterizer::Screenshot Shell::Screenshot(
Rasterizer::ScreenshotType screenshot_type,
bool base64_encode) {
Expand Down
9 changes: 9 additions & 0 deletions shell/common/shell.h
Original file line number Diff line number Diff line change
Expand Up @@ -706,6 +706,15 @@ class Shell final : public PlatformView::Delegate,
const ServiceProtocol::Handler::ServiceProtocolMap& params,
rapidjson::Document* response);

// Service protocol handler
//
// Renders a frame and responds with various statistics pertaining to the
// raster call. These include time taken to raster every leaf layer and also
// leaf layer snapshots.
bool OnServiceProtocolRenderFrameWithRasterStats(
const ServiceProtocol::Handler::ServiceProtocolMap& params,
rapidjson::Document* response);

// |ResourceCacheLimitItem|
size_t GetResourceCacheLimit() override { return resource_cache_limit_; };

Expand Down
39 changes: 21 additions & 18 deletions shell/common/shell_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -256,24 +256,27 @@ void ShellTest::OnServiceProtocol(
const ServiceProtocol::Handler::ServiceProtocolMap& params,
rapidjson::Document* response) {
std::promise<bool> finished;
fml::TaskRunner::RunNowOrPostTask(
task_runner, [shell, some_protocol, params, response, &finished]() {
switch (some_protocol) {
case ServiceProtocolEnum::kGetSkSLs:
shell->OnServiceProtocolGetSkSLs(params, response);
break;
case ServiceProtocolEnum::kEstimateRasterCacheMemory:
shell->OnServiceProtocolEstimateRasterCacheMemory(params, response);
break;
case ServiceProtocolEnum::kSetAssetBundlePath:
shell->OnServiceProtocolSetAssetBundlePath(params, response);
break;
case ServiceProtocolEnum::kRunInView:
shell->OnServiceProtocolRunInView(params, response);
break;
}
finished.set_value(true);
});
fml::TaskRunner::RunNowOrPostTask(task_runner, [shell, some_protocol, params,
response, &finished]() {
switch (some_protocol) {
case ServiceProtocolEnum::kGetSkSLs:
shell->OnServiceProtocolGetSkSLs(params, response);
break;
case ServiceProtocolEnum::kEstimateRasterCacheMemory:
shell->OnServiceProtocolEstimateRasterCacheMemory(params, response);
break;
case ServiceProtocolEnum::kSetAssetBundlePath:
shell->OnServiceProtocolSetAssetBundlePath(params, response);
break;
case ServiceProtocolEnum::kRunInView:
shell->OnServiceProtocolRunInView(params, response);
break;
case ServiceProtocolEnum::kRenderFrameWithRasterStats:
shell->OnServiceProtocolRenderFrameWithRasterStats(params, response);
break;
}
finished.set_value(true);
});
finished.get_future().wait();
}

Expand Down
1 change: 1 addition & 0 deletions shell/common/shell_test.h
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ class ShellTest : public FixtureTest {
kEstimateRasterCacheMemory,
kSetAssetBundlePath,
kRunInView,
kRenderFrameWithRasterStats,
};

// Helper method to test private method Shell::OnServiceProtocolGetSkSLs.
Expand Down
52 changes: 48 additions & 4 deletions shell/common/shell_unittests.cc
Original file line number Diff line number Diff line change
Expand Up @@ -2524,6 +2524,49 @@ TEST_F(ShellTest, OnServiceProtocolEstimateRasterCacheMemoryWorks) {
DestroyShell(std::move(shell));
}

// ktz
TEST_F(ShellTest, OnServiceProtocolRenderFrameWithRasterStatsWorks) {
auto settings = CreateSettingsForFixture();
std::unique_ptr<Shell> shell = CreateShell(settings);

// Create the surface needed by rasterizer
PlatformViewNotifyCreated(shell.get());

auto configuration = RunConfiguration::InferFromSettings(settings);
configuration.SetEntrypoint("scene_with_red_box");

RunEngine(shell.get(), std::move(configuration));
PumpOneFrame(shell.get());

ServiceProtocol::Handler::ServiceProtocolMap empty_params;
rapidjson::Document document;
OnServiceProtocol(
shell.get(), ServiceProtocolEnum::kRenderFrameWithRasterStats,
shell->GetTaskRunners().GetRasterTaskRunner(), empty_params, &document);
rapidjson::StringBuffer buffer;
rapidjson::Writer<rapidjson::StringBuffer> writer(buffer);
document.Accept(writer);

// It would be better to parse out the json and check for the validity of
// fields. Below checks approximate what needs to be checked, this can not be
// an exact check since duration will not exactly match.
std::string expected_json =
"\"snapshot\":[137,80,78,71,13,10,26,10,0,"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

consider adding a doc comment explaining how this was generated and what might cause it to change (and what an acceptable change would be).

"0,0,13,73,72,68,82,0,0,0,1,0,0,0,1,8,6,0,0,0,31,21,196,137,0,0,0,1,115,"
"82,71,66,0,174,206,28,233,0,0,0,4,115,66,73,84,8,8,8,8,124,8,100,136,0,"
"0,0,13,73,68,65,84,8,153,99,248,207,192,240,31,0,5,0,1,255,171,206,54,"
"137,0,0,0,0,73,69,78,68,174,66,96,130]";
std::string actual_json = buffer.GetString();

EXPECT_THAT(actual_json, ::testing::HasSubstr(expected_json));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are there JSON methods we could be using instead to validate fields? e.g. validate that snapshot is a list of integers, and validate that the timing field is present?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I could maybe parse it out with rapidjson and validate that the timing field exists. I was being lazy and using HasSubstring. Let me change it to do real parsing instead.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(I'm mainly worried that this test will break if we move to a different rendering backend or the rendering backend slightly changes things and it'll take more work to validate)

EXPECT_THAT(actual_json,
::testing::HasSubstr("{\"type\":\"RenderFrameWithRasterStats\""));
EXPECT_THAT(actual_json, ::testing::HasSubstr("\"duration_micros\""));

PlatformViewNotifyDestroyed(shell.get());
DestroyShell(std::move(shell));
}

// TODO(https://github.com/flutter/flutter/issues/100273): Disabled due to
// flakiness.
// TODO(https://github.com/flutter/flutter/issues/100299): Fix it when
Expand Down Expand Up @@ -2913,8 +2956,8 @@ TEST_F(ShellTest, AssetManagerMultiSubdir) {

std::vector<std::string> filenames = {
"bad0",
"notgood", // this is to make sure the pattern (.*)good(.*) only matches
// things in the subdirectory
"notgood", // this is to make sure the pattern (.*)good(.*) only
// matches things in the subdirectory
};

std::vector<std::string> subdir_filenames = {
Expand Down Expand Up @@ -2989,8 +3032,9 @@ TEST_F(ShellTest, Spawn) {
// Fulfill native function for the second Shell's entrypoint.
fml::CountDownLatch second_latch(2);
AddNativeCallback(
// The Dart native function names aren't very consistent but this is just
// the native function name of the second vm entrypoint in the fixture.
// The Dart native function names aren't very consistent but this is
// just the native function name of the second vm entrypoint in the
// fixture.
"NotifyNative",
CREATE_NATIVE_ENTRY([&](auto args) { second_latch.CountDown(); }));

Expand Down