| // Copyright 2019 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. |
| |
| #include <fuchsia/ui/app/cpp/fidl.h> |
| #include <fuchsia/ui/gfx/cpp/fidl.h> |
| #include <fuchsia/ui/input/cpp/fidl.h> |
| #include <fuchsia/ui/scenic/cpp/fidl.h> |
| #include <lib/async-loop/cpp/loop.h> |
| #include <lib/async-loop/default.h> |
| #include <lib/fidl/cpp/binding_set.h> |
| #include <lib/images/cpp/images.h> |
| #include <lib/sys/cpp/component_context.h> |
| #include <lib/syslog/cpp/macros.h> |
| #include <lib/trace-provider/provider.h> |
| #include <lib/ui/scenic/cpp/commands.h> |
| #include <lib/ui/scenic/cpp/view_token_pair.h> |
| #include <math.h> |
| |
| #include <cmath> |
| |
| namespace { |
| const float FRAMEDROP_DETECTION_FACTOR = 1.2f; |
| const size_t SIZE_OF_BGRA8 = sizeof(uint32_t); |
| } // namespace |
| |
| class View : public fuchsia::ui::scenic::SessionListener { |
| public: |
| View(sys::ComponentContext* component_context, fuchsia::ui::views::ViewToken view_token) |
| : session_listener_binding_(this) { |
| // Connect to Scenic. |
| fuchsia::ui::scenic::ScenicPtr scenic = |
| component_context->svc()->Connect<fuchsia::ui::scenic::Scenic>(); |
| |
| // Create a Scenic Session and a Scenic SessionListener. |
| scenic->CreateSession(session_.NewRequest(), session_listener_binding_.NewBinding()); |
| |
| InitializeScene(std::move(view_token)); |
| } |
| |
| private: |
| static void PushCommand(std::vector<fuchsia::ui::scenic::Command>* cmds, |
| fuchsia::ui::gfx::Command cmd) { |
| // Wrap the gfx::Command in a scenic::Command, then push it. |
| cmds->push_back(scenic::NewCommand(std::move(cmd))); |
| } |
| |
| void SetBgra8Pixels(uint8_t* vmo_base, fuchsia::images::ImageInfo info) { |
| // Set the entire texture to a random bitmap. |
| for (uint32_t i = 0; i < info.height * info.width * SIZE_OF_BGRA8; ++i) { |
| vmo_base[i] = static_cast<uint8_t>(std::rand() % std::numeric_limits<uint8_t>::max()); |
| } |
| } |
| |
| uint32_t CreateTexture(uint32_t width, uint32_t height) { |
| static const auto kFormat = fuchsia::images::PixelFormat::BGRA_8; |
| fuchsia::images::ImageInfo image_info{ |
| .width = width, |
| .height = height, |
| .stride = static_cast<uint32_t>(width * images::StrideBytesPerWidthPixel(kFormat)), |
| .pixel_format = kFormat, |
| }; |
| |
| uint64_t image_vmo_bytes = images::ImageSize(image_info); |
| |
| zx::vmo image_vmo; |
| zx_status_t status = zx::vmo::create(image_vmo_bytes, 0, &image_vmo); |
| if (status != ZX_OK) { |
| FX_LOGS(FATAL) << "::zx::vmo::create() failed"; |
| FX_NOTREACHED(); |
| } |
| |
| uint8_t* vmo_base; |
| status = zx::vmar::root_self()->map(ZX_VM_PERM_WRITE | ZX_VM_PERM_READ, 0, image_vmo, 0, |
| image_vmo_bytes, reinterpret_cast<uintptr_t*>(&vmo_base)); |
| |
| SetBgra8Pixels(vmo_base, image_info); |
| |
| std::vector<fuchsia::ui::scenic::Command> cmds; |
| |
| uint32_t memory_id = new_resource_id_++; |
| PushCommand(&cmds, scenic::NewCreateMemoryCmd(memory_id, std::move(image_vmo), image_vmo_bytes, |
| fuchsia::images::MemoryType::HOST_MEMORY)); |
| uint32_t image_id = new_resource_id_++; |
| PushCommand(&cmds, scenic::NewCreateImageCmd(image_id, memory_id, 0, image_info)); |
| |
| session_->Enqueue(std::move(cmds)); |
| return image_id; |
| } |
| |
| void InitializeScene(fuchsia::ui::views::ViewToken view_token) { |
| // Build up a list of commands we will send over our Scenic Session. |
| std::vector<fuchsia::ui::scenic::Command> cmds; |
| |
| // View: Use |view_token| to create a View in the Session. |
| PushCommand(&cmds, scenic::NewCreateViewCmd(kViewId, std::move(view_token), |
| "transparency_benchmark_view")); |
| PushCommand(&cmds, scenic::NewCreateEntityNodeCmd(kScaleId)); |
| PushCommand(&cmds, scenic::NewAddChildCmd(kViewId, kScaleId)); |
| |
| for (int i = 0; i < kFullScreenLayers; i++) { |
| int shape_id = new_resource_id_++; |
| PushCommand(&cmds, scenic::NewCreateShapeNodeCmd(shape_id)); |
| full_screen_shape_nodes_.push_back(shape_id); |
| |
| PushCommand(&cmds, scenic::NewAddChildCmd(kScaleId, shape_id)); |
| |
| // Material. |
| int material_id = new_resource_id_++; |
| PushCommand(&cmds, scenic::NewCreateMaterialCmd(material_id)); |
| PushCommand(&cmds, scenic::NewSetMaterialCmd(shape_id, material_id)); |
| shape_node_materials_.push_back(material_id); |
| } |
| |
| session_->Enqueue(std::move(cmds)); |
| |
| // Apply all the commands we've enqueued by calling Present. For this first |
| // frame we call Present with a presentation_time = 0 which means it the |
| // commands should be applied immediately. For future frames, we'll use the |
| // timing information we receive to have precise presentation times. |
| session_->Present( |
| 0, {}, {}, [this](fuchsia::images::PresentationInfo info) { OnPresent(std::move(info)); }); |
| } |
| |
| // |fuchsia::ui::scenic::SessionListener| |
| void OnScenicError(std::string error) override { FX_LOGS(INFO) << "ERROR: " << error; } |
| |
| static bool IsViewAttachedToSceneEvent(const fuchsia::ui::scenic::Event& event) { |
| return event.Which() == fuchsia::ui::scenic::Event::Tag::kGfx && |
| event.gfx().Which() == fuchsia::ui::gfx::Event::Tag::kViewAttachedToScene; |
| } |
| |
| static bool IsViewPropertiesChangedEvent(const fuchsia::ui::scenic::Event& event) { |
| return event.Which() == fuchsia::ui::scenic::Event::Tag::kGfx && |
| event.gfx().Which() == fuchsia::ui::gfx::Event::Tag::kViewPropertiesChanged; |
| } |
| |
| static bool IsPointerEvent(const fuchsia::ui::scenic::Event& event) { |
| return event.Which() == fuchsia::ui::scenic::Event::Tag::kInput && |
| event.input().Which() == fuchsia::ui::input::InputEvent::Tag::kPointer; |
| } |
| |
| static bool IsPointerDownEvent(const fuchsia::ui::scenic::Event& event) { |
| return IsPointerEvent(event) && |
| event.input().pointer().phase == fuchsia::ui::input::PointerEventPhase::DOWN; |
| } |
| |
| static bool IsPointerUpEvent(const fuchsia::ui::scenic::Event& event) { |
| return IsPointerEvent(event) && |
| event.input().pointer().phase == fuchsia::ui::input::PointerEventPhase::UP; |
| } |
| |
| bool attached_ = false; |
| bool sized_ = false; |
| |
| // |fuchsia::ui::scenic::SessionListener| |
| void OnScenicEvent(std::vector<fuchsia::ui::scenic::Event> events) override { |
| for (auto& event : events) { |
| if (IsViewAttachedToSceneEvent(event)) { |
| attached_ = true; |
| } |
| if (IsViewPropertiesChangedEvent(event)) { |
| OnViewPropertiesChanged(event.gfx().view_properties_changed().properties); |
| sized_ = true; |
| } else if (IsPointerDownEvent(event)) { |
| pointer_down_ = true; |
| pointer_id_ = event.input().pointer().pointer_id; |
| } else if (IsPointerUpEvent(event)) { |
| if (pointer_id_ == event.input().pointer().pointer_id) { |
| pointer_down_ = false; |
| } |
| } else { |
| // Unhandled event. |
| } |
| } |
| } |
| |
| void OnViewPropertiesChanged(fuchsia::ui::gfx::ViewProperties vp) { |
| view_width_ = (vp.bounding_box.max.x - vp.inset_from_max.x) - |
| (vp.bounding_box.min.x + vp.inset_from_min.x); |
| view_height_ = (vp.bounding_box.max.y - vp.inset_from_max.y) - |
| (vp.bounding_box.min.y + vp.inset_from_min.y); |
| |
| FX_LOGS(INFO) << "OnViewPropertiesChanged " << view_width_ << " " << view_height_; |
| |
| if (view_width_ == 0 || view_height_ == 0) |
| return; |
| |
| // Build up a list of commands we will send over our Scenic Session. |
| std::vector<fuchsia::ui::scenic::Command> cmds; |
| |
| for (auto shape_node_id : full_screen_shape_nodes_) { |
| int rectangle_id = new_resource_id_++; |
| PushCommand(&cmds, scenic::NewCreateRectangleCmd(rectangle_id, view_width_, view_height_)); |
| PushCommand(&cmds, scenic::NewSetShapeCmd(shape_node_id, rectangle_id)); |
| } |
| |
| for (int i = 0; i < kFullScreenLayers; i++) { |
| full_res_textures_.push_back( |
| CreateTexture(static_cast<uint32_t>(view_width_), static_cast<uint32_t>(view_height_))); |
| } |
| |
| state_ = BLANK; |
| InitBlank(&cmds); |
| session_->Enqueue(std::move(cmds)); |
| |
| // The commands won't actually get committed until Session.Present() is |
| // called. However, since we're animating every frame, in this case we can |
| // assume Present() will be called shortly. |
| } |
| |
| enum State { |
| INIT = -1, |
| BLANK = 0, |
| SOLID = 1, |
| SOLID_WITH_TEXTURE = 2, |
| ALPHA = 3, |
| ALPHA_WITH_SAME_TEXTURE = 4, |
| ALPHA_WITH_SEPARATE_TEXTURES = 5, |
| NUM_STATES = 6, |
| }; |
| |
| State state_ = INIT; |
| std::string state_names_[NUM_STATES] = {"BLANK", |
| "SOLID", |
| "SOLID_WITH_TEXTURE", |
| "ALPHA", |
| "ALPHA_WITH_SAME_TEXTURE", |
| "ALPHA_WITH_SEPARATE_TEXTURES"}; |
| |
| void DetachAll(std::vector<fuchsia::ui::scenic::Command>* cmds) { |
| for (auto id : full_screen_shape_nodes_) { |
| PushCommand(cmds, scenic::NewDetachCmd(id)); |
| } |
| } |
| |
| void Tile(std::vector<fuchsia::ui::scenic::Command>* cmds, int width, int height, int depth) { |
| // Position is relative to the View's origin system. |
| const float center_x = view_width_ * .5f; |
| const float center_y = view_height_ * .5f; |
| for (int i = 0; i < kFullScreenLayers; i++) { |
| float x = static_cast<float>(i % width); |
| float y = static_cast<float>((i / width) % height); |
| float z = static_cast<float>(i / (width * height)); |
| auto id = full_screen_shape_nodes_[i]; |
| |
| static const float PI = 3.14159f; |
| |
| PushCommand(cmds, |
| scenic::NewSetRotationCmd(id, {0, 0, std::sin(PI / 2.0f), std::cos(PI / 2.0f)})); |
| PushCommand(cmds, scenic::NewSetTranslationCmd(id, {center_x + x * view_width_, |
| center_y + y * view_height_, z * -1.0f})); |
| } |
| } |
| |
| void InitBlank(std::vector<fuchsia::ui::scenic::Command>* cmds) { DetachAll(cmds); } |
| |
| bool Blank(std::vector<fuchsia::ui::scenic::Command>* cmds, int level) { |
| return level >= kFullScreenLayers; |
| } |
| |
| void InitSolid(std::vector<fuchsia::ui::scenic::Command>* cmds) { |
| DetachAll(cmds); |
| Tile(cmds, 1, 1, kFullScreenLayers); |
| for (auto id : shape_node_materials_) { |
| PushCommand(cmds, scenic::NewSetTextureCmd(id, 0)); |
| PushCommand(cmds, scenic::NewSetColorCmd(id, 0xff, 0xff, 0xff, 0xff)); |
| } |
| } |
| |
| void InitSolidWithTexture(std::vector<fuchsia::ui::scenic::Command>* cmds) { |
| DetachAll(cmds); |
| Tile(cmds, 1, 1, kFullScreenLayers); |
| for (int i = 0; i < kFullScreenLayers; i++) { |
| PushCommand(cmds, scenic::NewSetTextureCmd(shape_node_materials_[i], full_res_textures_[i])); |
| PushCommand(cmds, scenic::NewSetColorCmd(shape_node_materials_[i], 0xff, 0xff, 0xff, 0xff)); |
| } |
| } |
| |
| void InitAlpha(std::vector<fuchsia::ui::scenic::Command>* cmds) { |
| DetachAll(cmds); |
| Tile(cmds, 1, 1, kFullScreenLayers); |
| for (auto id : shape_node_materials_) { |
| PushCommand(cmds, scenic::NewSetTextureCmd(id, 0)); |
| PushCommand(cmds, scenic::NewSetColorCmd(id, 0xff, 0xff, 0xff, 0x80)); |
| } |
| } |
| |
| void InitAlphaWithSameTexture(std::vector<fuchsia::ui::scenic::Command>* cmds) { |
| DetachAll(cmds); |
| Tile(cmds, 1, 1, kFullScreenLayers); |
| for (int i = 0; i < kFullScreenLayers; i++) { |
| PushCommand(cmds, scenic::NewSetTextureCmd(shape_node_materials_[i], full_res_textures_[0])); |
| PushCommand(cmds, scenic::NewSetColorCmd(shape_node_materials_[i], 0xff, 0xff, 0xff, 0x80)); |
| } |
| } |
| |
| void InitAlphaWithSeparateTextures(std::vector<fuchsia::ui::scenic::Command>* cmds) { |
| DetachAll(cmds); |
| Tile(cmds, 1, 1, kFullScreenLayers); |
| for (int i = 0; i < kFullScreenLayers; i++) { |
| PushCommand(cmds, scenic::NewSetTextureCmd(shape_node_materials_[i], full_res_textures_[i])); |
| PushCommand(cmds, scenic::NewSetColorCmd(shape_node_materials_[i], 0xff, 0xff, 0xff, 0x80)); |
| } |
| } |
| |
| bool LayerUpdate(std::vector<fuchsia::ui::scenic::Command>* cmds, int level) { |
| for (int i = 0; i < level; i++) { |
| auto id = full_screen_shape_nodes_[i]; |
| PushCommand(cmds, scenic::NewDetachCmd(id)); |
| PushCommand(cmds, scenic::NewAddChildCmd(kScaleId, id)); |
| } |
| |
| return level >= kFullScreenLayers; |
| } |
| |
| void OnPresent(fuchsia::images::PresentationInfo presentation_info) { |
| uint64_t presentation_time = presentation_info.presentation_time; |
| constexpr float kSecondsPerNanosecond = .000'000'001f; |
| |
| float t = |
| static_cast<float>(presentation_time - last_presentation_time_) * kSecondsPerNanosecond; |
| if (last_presentation_time_ == 0) { |
| t = 0; |
| } |
| |
| last_presentation_time_ = presentation_time; |
| |
| bool done = false; |
| std::vector<fuchsia::ui::scenic::Command> cmds; |
| switch (state_) { |
| case INIT: |
| break; |
| case BLANK: |
| done |= Blank(&cmds, level_); |
| break; |
| default: |
| done |= LayerUpdate(&cmds, level_); |
| break; |
| } |
| |
| if (sample_ > kWarmUpPeriod) { |
| avg_times_[sample_ - kWarmUpPeriod] = t; |
| } |
| |
| ++sample_; |
| |
| if (sample_ >= kWarmUpPeriod + kSamples) { |
| if (state_ != INIT) { |
| float time = 0.0f; |
| for (int i = 0; i < kSamples; i++) { |
| time += avg_times_[i]; |
| avg_times_[i] = 0.0f; |
| } |
| time = time / kSamples; |
| FX_LOGS(INFO) << "Tested " << state_names_[state_] << " " << level_ |
| << ", avg time: " << time; |
| saved_times_[state_] = time; |
| saved_levels_[state_] = level_; |
| done = saved_times_[state_] > FRAMEDROP_DETECTION_FACTOR * saved_times_[BLANK]; |
| } |
| sample_ = 0; |
| level_++; |
| } |
| |
| if (done) { |
| sample_ = 0; |
| level_ = 0; |
| |
| switch (state_) { |
| case BLANK: |
| state_ = SOLID; |
| InitSolid(&cmds); |
| break; |
| case SOLID: |
| state_ = SOLID_WITH_TEXTURE; |
| InitSolidWithTexture(&cmds); |
| break; |
| case SOLID_WITH_TEXTURE: |
| state_ = ALPHA; |
| InitAlpha(&cmds); |
| break; |
| case ALPHA: |
| state_ = ALPHA_WITH_SAME_TEXTURE; |
| InitAlphaWithSameTexture(&cmds); |
| break; |
| case ALPHA_WITH_SAME_TEXTURE: |
| state_ = ALPHA_WITH_SEPARATE_TEXTURES; |
| InitAlphaWithSeparateTextures(&cmds); |
| break; |
| case ALPHA_WITH_SEPARATE_TEXTURES: |
| PrintReport(); |
| state_ = BLANK; |
| InitBlank(&cmds); |
| break; |
| default: |
| break; |
| } |
| } |
| |
| session_->Enqueue(std::move(cmds)); |
| |
| zx_time_t next_presentation_time = presentation_info.presentation_time + 1; |
| session_->Present( |
| next_presentation_time, {}, {}, |
| [this](fuchsia::images::PresentationInfo info) { OnPresent(std::move(info)); }); |
| } |
| |
| void PrintReport() { |
| FX_LOGS(INFO) << "----- REPORT -----"; |
| for (int i = 0; i < NUM_STATES; i++) { |
| if (saved_levels_[i] == kFullScreenLayers - 1) { |
| FX_LOGS(INFO) << "State " << state_names_[i] << " completed with a running time of " |
| << saved_times_[i]; |
| } else { |
| FX_LOGS(INFO) << "State " << state_names_[i] << " failed at level " << saved_levels_[i] |
| << " with a running time of " << saved_times_[i]; |
| } |
| } |
| FX_LOGS(INFO) << "--- END REPORT ---"; |
| } |
| |
| static const int kWarmUpPeriod = 10; |
| static const int kSamples = 10; |
| int sample_ = 0; |
| int level_ = false; |
| float avg_times_[kSamples]; |
| float saved_times_[NUM_STATES]; |
| int saved_levels_[NUM_STATES]; |
| |
| const int kViewId = 1; |
| const int kScaleId = 2; |
| |
| // For other resources we create, we use |new_resource_id_| and then |
| // increment it. |
| int new_resource_id_ = 3; |
| |
| uint64_t last_presentation_time_ = 0; |
| |
| float view_width_ = 0; |
| float view_height_ = 0; |
| |
| static const int kFullScreenLayers = 20; |
| std::vector<int> full_screen_shape_nodes_; |
| std::vector<int> shape_node_materials_; |
| std::vector<int> full_res_textures_; |
| |
| // Input. |
| bool pointer_down_ = false; |
| uint32_t pointer_id_ = 0; |
| |
| fidl::Binding<fuchsia::ui::scenic::SessionListener> session_listener_binding_; |
| fuchsia::ui::scenic::SessionPtr session_; |
| }; |
| |
| // Implement the ViewProvider interface, a standard way for an embedder to |
| // provide us a token that, using Scenic APIs, allows us to create a View |
| // that's attached to the embedder's ViewHolder. |
| class ViewProviderService : public fuchsia::ui::app::ViewProvider { |
| public: |
| ViewProviderService(sys::ComponentContext* component_context) |
| : component_context_(component_context) {} |
| |
| // |fuchsia::ui::app::ViewProvider| |
| void CreateView(zx::eventpair view_token, |
| fidl::InterfaceRequest<fuchsia::sys::ServiceProvider> incoming_services, |
| fidl::InterfaceHandle<fuchsia::sys::ServiceProvider> outgoing_services) override { |
| auto view = |
| std::make_unique<View>(component_context_, scenic::ToViewToken(std::move(view_token))); |
| views_.push_back(std::move(view)); |
| } |
| |
| void HandleViewProviderRequest(fidl::InterfaceRequest<fuchsia::ui::app::ViewProvider> request) { |
| bindings_.AddBinding(this, std::move(request)); |
| } |
| |
| private: |
| sys::ComponentContext* component_context_ = nullptr; |
| std::vector<std::unique_ptr<View>> views_; |
| fidl::BindingSet<ViewProvider> bindings_; |
| }; |
| |
| int main(int argc, const char** argv) { |
| async::Loop loop(&kAsyncLoopConfigAttachToCurrentThread); |
| trace::TraceProviderWithFdio trace_provider(loop.dispatcher()); |
| |
| std::unique_ptr<sys::ComponentContext> component_context = |
| sys::ComponentContext::CreateAndServeOutgoingDirectory(); |
| |
| ViewProviderService view_provider(component_context.get()); |
| |
| // Add our ViewProvider service to the outgoing services. |
| component_context->outgoing()->AddPublicService<fuchsia::ui::app::ViewProvider>( |
| [&view_provider](fidl::InterfaceRequest<fuchsia::ui::app::ViewProvider> request) { |
| view_provider.HandleViewProviderRequest(std::move(request)); |
| }); |
| |
| loop.Run(); |
| return 0; |
| } |