protovm: surface flinger test Change-Id: Id8c3c8cbf5d102c6891e390b183381546a6a6964
diff --git a/Android.bp b/Android.bp index 613f562..89457aa 100644 --- a/Android.bp +++ b/Android.bp
@@ -14391,6 +14391,7 @@ "src/protovm/test/rw_proto_unittest.cc", "src/protovm/test/slab_allocator_unittest.cc", "src/protovm/test/utils.cc", + "src/protovm/test/vm_surfaceflinger_test.cc", "src/protovm/test/vm_unittest.cc", ], }
diff --git a/src/protovm/BUILD.gn b/src/protovm/BUILD.gn index 91a55e2e..b218407 100644 --- a/src/protovm/BUILD.gn +++ b/src/protovm/BUILD.gn
@@ -60,6 +60,8 @@ deps = [ ":protovm", ":test_messages_lite", + "../../protos/perfetto/trace/android:winscope_regular_lite", + "../../protos/perfetto/trace/android:winscope_regular_zero", "../../gn:default_deps", "../../gn:gtest_and_gmock", "../../protos/perfetto/protovm:lite", @@ -80,5 +82,6 @@ "test/utils.cc", "test/utils.h", "test/vm_unittest.cc", + "test/vm_surfaceflinger_test.cc", ] }
diff --git a/src/protovm/test/vm_surfaceflinger_test.cc b/src/protovm/test/vm_surfaceflinger_test.cc new file mode 100644 index 0000000..90c28fb --- /dev/null +++ b/src/protovm/test/vm_surfaceflinger_test.cc
@@ -0,0 +1,465 @@ +#include <cstddef> +#include <cstdint> +#include <cstdio> +#include <optional> +#include <vector> + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-redundant-constexpr-static-def" +#include "gtest/gtest.h" +#include "test/gtest_and_gmock.h" +#pragma clang diagnostic pop + +#include "perfetto/protozero/scattered_heap_buffer.h" +#include "protos/perfetto/protovm/vm_program.pb.h" +#include "protos/perfetto/trace/android/surfaceflinger_common.pb.h" +#include "protos/perfetto/trace/android/surfaceflinger_layers.pb.h" +#include "src/protovm/test/utils.h" +#include "src/protovm/vm.h" + +namespace perfetto { +namespace protovm { +namespace test { + +struct FieldMapping { + std::vector<uint32_t> src_path; + std::vector<uint32_t> dst_path; +}; + +struct NestedMergeNode { + uint32_t field_id; + std::vector<NestedMergeNode> children; +}; + +struct RepeatedMessageMapping { + uint32_t src_list_field; + std::vector<uint32_t> dst_path; + uint32_t key_field; + NestedMergeNode nested_merges; +}; + +struct TestLayer { + int32_t id; + std::optional<std::string> name; +}; + +struct TestDisplay { + uint64_t id; + std::optional<std::string> name; +}; + +static void FillTestLayer(protos::LayerProto* l, const TestLayer& layer) { + l->set_id(layer.id); + if (layer.name) { + l->set_name(*layer.name); + } +} + +static void FillTestDisplay(protos::DisplayProto* d, + const TestDisplay& display) { + d->set_id(display.id); + if (display.name) { + d->set_name(*display.name); + } +} + +static void AddTestLayer(protos::LayersSnapshotProto* snapshot, + const TestLayer& layer) { + FillTestLayer(snapshot->mutable_layers()->add_layers(), layer); +} + +static void AddTestLayer(protos::LayersPatchProto* patch, + const TestLayer& layer) { + FillTestLayer(patch->add_layers(), layer); +} + +static void AddTestDisplay(protos::LayersSnapshotProto* snapshot, + const TestDisplay& display) { + FillTestDisplay(snapshot->add_displays(), display); +} + +static void AddTestDisplay(protos::LayersPatchProto* patch, + const TestDisplay& display) { + FillTestDisplay(patch->add_displays(), display); +} + +static void VerifyLayer(const protos::LayersSnapshotProto& snapshot, + const TestLayer& expected) { + bool found = false; + for (const auto& layer : snapshot.layers().layers()) { + if (layer.id() == expected.id) { + found = true; + EXPECT_EQ(layer.id(), expected.id); + if (expected.name) { + EXPECT_EQ(layer.name(), *expected.name); + } + break; + } + } + ASSERT_TRUE(found) << "Layer with ID " << expected.id << " not found"; +} + +static void VerifyDisplay(const protos::LayersSnapshotProto& snapshot, + const TestDisplay& expected) { + bool found = false; + for (const auto& display : snapshot.displays()) { + if (display.id() == expected.id) { + found = true; + EXPECT_EQ(display.id(), expected.id); + if (expected.name) { + EXPECT_EQ(display.name(), *expected.name); + } + break; + } + } + ASSERT_TRUE(found) << "Display with ID " << expected.id << " not found"; +} + +static void VerifyLayerNotFound(const protos::LayersSnapshotProto& snapshot, + int32_t id) { + for (const auto& layer : snapshot.layers().layers()) { + EXPECT_NE(layer.id(), id) + << "Layer with ID " << id << " should not be present"; + } +} + +static void VerifyDisplayNotFound(const protos::LayersSnapshotProto& snapshot, + uint64_t id) { + for (const auto& display : snapshot.displays()) { + EXPECT_NE(display.id(), id) + << "Display with ID " << id << " should not be present"; + } +} + +static void FillSelect(protos::VmInstruction* instr, + protos::VmCursorEnum cursor, + const std::vector<uint32_t>& path, + bool create_if_not_exist) { + auto* sel = instr->mutable_select(); + sel->set_cursor(cursor); + sel->set_create_if_not_exist(create_if_not_exist); + for (uint32_t fid : path) { + sel->add_relative_path()->set_field_id(fid); + } +} + +static protos::VmInstruction* AddSelect(protos::VmProgram& program, + protos::VmCursorEnum cursor, + const std::vector<uint32_t>& path, + bool create_if_not_exist = false) { + auto* instr = program.add_instructions(); + FillSelect(instr, cursor, path, create_if_not_exist); + return instr; +} + +static protos::VmInstruction* AddNestedSelect( + protos::VmInstruction* parent, + protos::VmCursorEnum cursor, + const std::vector<uint32_t>& path, + bool create_if_not_exist = false) { + auto* instr = parent->add_nested_instructions(); + FillSelect(instr, cursor, path, create_if_not_exist); + return instr; +} + +static protos::VmInstruction* AddNestedMapSelect( + protos::VmInstruction* parent, + const std::vector<uint32_t>& path, + uint32_t key_field_id, + uint32_t register_to_match, + bool create_if_not_exist = false) { + auto* instr = AddNestedSelect(parent, protos::VmCursorEnum::VM_CURSOR_DST, + path, create_if_not_exist); + auto* sel = instr->mutable_select(); + auto* map_key_comp = sel->add_relative_path(); + map_key_comp->set_map_key_field_id(key_field_id); + map_key_comp->set_register_to_match(register_to_match); + return instr; +} + +static protos::VmInstruction* AddSelectRepeated(protos::VmProgram& program, + protos::VmCursorEnum cursor, + uint32_t field_id) { + auto* instr = program.add_instructions(); + auto* sel = instr->mutable_select(); + sel->set_cursor(cursor); + auto* comp = sel->add_relative_path(); + comp->set_field_id(field_id); + comp->set_is_repeated(true); + return instr; +} + +static void AddRegLoad(protos::VmInstruction* parent, uint32_t dst_register) { + parent->add_nested_instructions()->mutable_reg_load()->set_dst_register( + dst_register); +} + +static void AddSet(protos::VmInstruction* parent) { + parent->add_nested_instructions()->mutable_set(); +} + +static void AddDel(protos::VmInstruction* parent) { + parent->add_nested_instructions()->mutable_del(); +} + +static void AddMerge(protos::VmInstruction* parent, + bool skip_submessages = false) { + parent->add_nested_instructions()->mutable_merge()->set_skip_submessages( + skip_submessages); +} + +static void AddDeleteByKey(protos::VmProgram& program, + uint32_t src_list_field, + const std::vector<uint32_t>& dst_path, + uint32_t key_field_id) { + auto* instr_src_sel = AddSelectRepeated( + program, protos::VmCursorEnum::VM_CURSOR_SRC, src_list_field); + AddRegLoad(instr_src_sel, 1); + + auto* instr_dst_sel = + AddNestedMapSelect(instr_src_sel, dst_path, key_field_id, 1); + AddDel(instr_dst_sel); +} + +static void AddPrimitiveMapping(protos::VmProgram& program, + const std::vector<uint32_t>& src_path, + const std::vector<uint32_t>& dst_path) { + auto* src_instr = + AddSelect(program, protos::VmCursorEnum::VM_CURSOR_SRC, src_path); + auto* dst_instr = + AddNestedSelect(src_instr, protos::VmCursorEnum::VM_CURSOR_DST, dst_path); + AddSet(dst_instr); +} + +static void AddRecursiveMerge(protos::VmInstruction* current_instr, + const NestedMergeNode& node) { + for (const auto& child : node.children) { + auto* src_sub = AddNestedSelect( + current_instr, protos::VmCursorEnum::VM_CURSOR_SRC, {child.field_id}); + src_sub->set_abort_level( + ::perfetto::protos::VmInstruction_AbortLevel_SKIP_CURRENT_INSTRUCTION); + + auto* dst_sub = + AddNestedSelect(src_sub, protos::VmCursorEnum::VM_CURSOR_DST, + {child.field_id}, true /* create_if_not_exist */); + AddRecursiveMerge(dst_sub, child); + } + + AddMerge(current_instr, true /* skip_submessages */); +} + +static void AddMappedMessagePatch(protos::VmProgram& program, + const RepeatedMessageMapping& mapping) { + auto* instr_src_sel = AddSelectRepeated( + program, protos::VmCursorEnum::VM_CURSOR_SRC, mapping.src_list_field); + auto* instr_key_sel = AddNestedSelect( + instr_src_sel, protos::VmCursorEnum::VM_CURSOR_SRC, {mapping.key_field}); + AddRegLoad(instr_key_sel, 1); + auto* instr_dst_sel = + AddNestedMapSelect(instr_src_sel, mapping.dst_path, mapping.key_field, 1, + true /* create_if_not_exist */); + + AddRecursiveMerge(instr_dst_sel, mapping.nested_merges); +} + +static protos::VmProgram LayersProgram() { + protos::VmProgram program; + + const std::vector<FieldMapping> root_mappings = { + {{protos::LayersPatchProto::kElapsedRealtimeNanosFieldNumber}, + {protos::LayersSnapshotProto::kElapsedRealtimeNanosFieldNumber}}, + {{protos::LayersPatchProto::kWhereFieldNumber}, + {protos::LayersSnapshotProto::kWhereFieldNumber}}, + {{protos::LayersPatchProto::kHwcBlobFieldNumber}, + {protos::LayersSnapshotProto::kHwcBlobFieldNumber}}, + {{protos::LayersPatchProto::kExcludesCompositionStateFieldNumber}, + {protos::LayersSnapshotProto::kExcludesCompositionStateFieldNumber}}, + {{protos::LayersPatchProto::kMissedEntriesFieldNumber}, + {protos::LayersSnapshotProto::kMissedEntriesFieldNumber}}, + {{protos::LayersPatchProto::kVsyncIdFieldNumber}, + {protos::LayersSnapshotProto::kVsyncIdFieldNumber}}, + }; + + for (const auto& m : root_mappings) { + AddPrimitiveMapping(program, m.src_path, m.dst_path); + } + + // Process layers + RepeatedMessageMapping layer_mapping; + layer_mapping.src_list_field = protos::LayersPatchProto::kLayersFieldNumber; + layer_mapping.dst_path = {protos::LayersSnapshotProto::kLayersFieldNumber, + protos::LayersProto::kLayersFieldNumber}; + layer_mapping.key_field = protos::LayerProto::kIdFieldNumber; + layer_mapping.nested_merges = { + 0, // dummy + {{protos::LayerProto::kPositionFieldNumber, {}}, + {protos::LayerProto::kInputWindowInfoFieldNumber, + {{protos::InputWindowInfoProto::kTouchableRegionCropFieldNumber, + {}}}}}}; + AddMappedMessagePatch(program, layer_mapping); + + // Process displays + RepeatedMessageMapping display_mapping; + display_mapping.src_list_field = + protos::LayersPatchProto::kDisplaysFieldNumber; + display_mapping.dst_path = { + protos::LayersSnapshotProto::kDisplaysFieldNumber}; + display_mapping.key_field = protos::DisplayProto::kIdFieldNumber; + display_mapping.nested_merges = {0, {}}; // Empty nested merges + AddMappedMessagePatch(program, display_mapping); + + // Process deleted_layer_ids + AddDeleteByKey(program, protos::LayersPatchProto::kDeletedLayerIdsFieldNumber, + {protos::LayersSnapshotProto::kLayersFieldNumber, + protos::LayersProto::kLayersFieldNumber}, + protos::LayerProto::kIdFieldNumber); + + // Process deleted_display_ids + AddDeleteByKey(program, + protos::LayersPatchProto::kDeletedDisplayIdsFieldNumber, + {protos::LayersSnapshotProto::kDisplaysFieldNumber}, + protos::DisplayProto::kIdFieldNumber); + + return program; +} + +class VmSurfaceFlingerTest : public ::testing::Test { + protected: + static constexpr size_t MEMORY_LIMIT_BYTES = + static_cast<const size_t>(10 * 1024 * 1024); + + std::string SerializeIncrementalStateAsString(const Vm& vm) const { + protozero::HeapBuffered<protozero::Message> proto; + vm.SerializeIncrementalState(proto.get()); + return proto.SerializeAsString(); + } +}; + +TEST_F(VmSurfaceFlingerTest, Full) { + auto program = LayersProgram().SerializeAsString(); + + protos::LayersSnapshotProto initial_state; + initial_state.set_elapsed_realtime_nanos(123456L); + initial_state.set_where("visibleRegionsDirty"); + initial_state.set_hwc_blob("maxDownScale: 4"); + initial_state.set_excludes_composition_state(false); + initial_state.set_missed_entries(1); + initial_state.set_vsync_id(1); + + // Add initial layers: 1, 2, 3 + AddTestLayer(&initial_state, {1, "Layer1"}); + AddTestLayer(&initial_state, {2, "Layer2"}); + AddTestLayer(&initial_state, {3, "Layer3"}); + + // Set position, input_window_info, children, and metadata for Layer 3 in + // initial state + auto* l3 = initial_state.mutable_layers()->mutable_layers(2); + l3->mutable_position()->set_x(10.0f); + l3->mutable_position()->set_y(20.0f); + + auto* win_info = l3->mutable_input_window_info(); + win_info->mutable_touchable_region_crop()->set_left(5); + win_info->mutable_touchable_region_crop()->set_top(5); + win_info->mutable_touchable_region_crop()->set_right(10); + win_info->mutable_touchable_region_crop()->set_bottom(10); + + l3->add_children(10); + l3->add_children(20); + (*l3->mutable_metadata())[1] = "v1"; + + // Add initial displays: 1, 2, 3 + AddTestDisplay(&initial_state, {1, "Display1"}); + AddTestDisplay(&initial_state, {2, "Display2"}); + AddTestDisplay(&initial_state, {3, "Display3"}); + + Vm vm{AsConstBytes(program), MEMORY_LIMIT_BYTES, + AsConstBytes(initial_state.SerializeAsString())}; + + protos::LayersPatchProto patch; + patch.set_elapsed_realtime_nanos(999999L); + patch.set_where("bufferLatched"); + patch.set_hwc_blob("maxDownScale: 10"); + patch.set_excludes_composition_state(true); + patch.set_missed_entries(2); + patch.set_vsync_id(2); + + // Patch layers: 1 (no change), 3 (updated), 4 (new) + AddTestLayer(&patch, {3, "Layer3_updated"}); + + // Update position, input_window_info, children, and metadata for Layer 3 in + // patch + auto* patch_l3 = patch.mutable_layers(0); + patch_l3->mutable_position()->set_y(30.0f); + patch_l3->mutable_input_window_info() + ->mutable_touchable_region_crop() + ->set_left(2); + + patch_l3->add_children(30); + (*patch_l3->mutable_metadata())[2] = "v2"; + + AddTestLayer(&patch, {4, "Layer4"}); + + // Patch displays: 1 (no change), 3 (updated), 4 (new) + AddTestDisplay(&patch, {3, "Display3_updated"}); + AddTestDisplay(&patch, {4, "Display4"}); + + // Deletions + patch.add_deleted_layer_ids(2); + patch.add_deleted_display_ids(2); + + vm.ApplyPatch(AsConstBytes(patch.SerializeAsString())); + + protos::LayersSnapshotProto updated_state; + updated_state.ParseFromString(SerializeIncrementalStateAsString(vm)); + + ASSERT_EQ(updated_state.elapsed_realtime_nanos(), 999999L); + ASSERT_EQ(updated_state.where(), "bufferLatched"); + ASSERT_EQ(updated_state.hwc_blob(), "maxDownScale: 10"); + ASSERT_EQ(updated_state.excludes_composition_state(), true); + ASSERT_EQ(updated_state.missed_entries(), 2U); + ASSERT_EQ(updated_state.vsync_id(), 2); + + VerifyLayer(updated_state, {1, "Layer1"}); + VerifyLayer(updated_state, {3, "Layer3_updated"}); + VerifyLayer(updated_state, {4, "Layer4"}); + VerifyLayerNotFound(updated_state, 2); + + // Verify position and input_window_info for Layer 3 + const protos::LayerProto* updated_l3 = nullptr; + for (const auto& layer : updated_state.layers().layers()) { + if (layer.id() == 3) { + updated_l3 = &layer; + break; + } + } + ASSERT_NE(updated_l3, nullptr); + ASSERT_TRUE(updated_l3->has_position()); + EXPECT_FLOAT_EQ(updated_l3->position().x(), 10.0f); + EXPECT_FLOAT_EQ(updated_l3->position().y(), 30.0f); + + ASSERT_TRUE(updated_l3->has_input_window_info()); + ASSERT_TRUE(updated_l3->input_window_info().has_touchable_region_crop()); + EXPECT_EQ(updated_l3->input_window_info().touchable_region_crop().left(), 2); + EXPECT_EQ(updated_l3->input_window_info().touchable_region_crop().top(), 5); + EXPECT_EQ(updated_l3->input_window_info().touchable_region_crop().right(), + 10); + EXPECT_EQ(updated_l3->input_window_info().touchable_region_crop().bottom(), + 10); + + // Verify children + ASSERT_EQ(updated_l3->children_size(), 1); + EXPECT_EQ(updated_l3->children(0), 30); + + // Verify metadata + ASSERT_EQ(updated_l3->metadata_size(), 1); + EXPECT_EQ(updated_l3->metadata().at(2), "v2"); + + VerifyDisplay(updated_state, {1, "Display1"}); + VerifyDisplay(updated_state, {3, "Display3_updated"}); + VerifyDisplay(updated_state, {4, "Display4"}); + VerifyDisplayNotFound(updated_state, 2); +} + +} // namespace test +} // namespace protovm +} // namespace perfetto