| // Copyright 2015 The Chromium 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 "components/exo/surface.h" |
| |
| #include <utility> |
| |
| #include "ash/public/cpp/shell_window_ids.h" |
| #include "base/callback_helpers.h" |
| #include "base/containers/adapters.h" |
| #include "base/logging.h" |
| #include "base/macros.h" |
| #include "base/memory/ptr_util.h" |
| #include "base/trace_event/trace_event.h" |
| #include "base/trace_event/traced_value.h" |
| #include "components/exo/buffer.h" |
| #include "components/exo/frame_sink_resource_manager.h" |
| #include "components/exo/shell_surface_util.h" |
| #include "components/exo/surface_delegate.h" |
| #include "components/exo/surface_observer.h" |
| #include "components/exo/wm_helper.h" |
| #include "components/viz/common/quads/render_pass.h" |
| #include "components/viz/common/quads/shared_quad_state.h" |
| #include "components/viz/common/quads/solid_color_draw_quad.h" |
| #include "components/viz/common/quads/surface_draw_quad.h" |
| #include "components/viz/common/quads/texture_draw_quad.h" |
| #include "components/viz/common/resources/single_release_callback.h" |
| #include "components/viz/service/surfaces/surface.h" |
| #include "components/viz/service/surfaces/surface_manager.h" |
| #include "third_party/khronos/GLES2/gl2.h" |
| #include "third_party/skia/include/core/SkPath.h" |
| #include "ui/aura/client/aura_constants.h" |
| #include "ui/aura/client/drag_drop_delegate.h" |
| #include "ui/aura/window_delegate.h" |
| #include "ui/aura/window_occlusion_tracker.h" |
| #include "ui/aura/window_targeter.h" |
| #include "ui/base/class_property.h" |
| #include "ui/base/cursor/cursor.h" |
| #include "ui/base/hit_test.h" |
| #include "ui/compositor/layer.h" |
| #include "ui/events/event.h" |
| #include "ui/gfx/buffer_format_util.h" |
| #include "ui/gfx/geometry/dip_util.h" |
| #include "ui/gfx/geometry/safe_integer_conversions.h" |
| #include "ui/gfx/geometry/size_conversions.h" |
| #include "ui/gfx/gpu_fence.h" |
| #include "ui/gfx/gpu_memory_buffer.h" |
| #include "ui/gfx/presentation_feedback.h" |
| #include "ui/gfx/transform_util.h" |
| #include "ui/views/widget/widget.h" |
| |
| #if defined(OS_CHROMEOS) |
| #include "ash/display/output_protection_delegate.h" |
| #include "ash/wm/desks/desks_util.h" |
| #endif // defined(OS_CHROMEOS) |
| |
| DEFINE_UI_CLASS_PROPERTY_TYPE(exo::Surface*) |
| |
| namespace exo { |
| namespace { |
| |
| // A property key containing the surface that is associated with |
| // window. If unset, no surface is associated with window. |
| DEFINE_UI_CLASS_PROPERTY_KEY(Surface*, kSurfaceKey, nullptr) |
| |
| // A property key to store whether the surface should only consume |
| // stylus input events. |
| DEFINE_UI_CLASS_PROPERTY_KEY(bool, kStylusOnlyKey, false) |
| |
| // Helper function that returns an iterator to the first entry in |list| |
| // with |key|. |
| template <typename T, typename U> |
| typename T::iterator FindListEntry(T& list, U key) { |
| return std::find_if(list.begin(), list.end(), |
| [key](const typename T::value_type& entry) { |
| return entry.first == key; |
| }); |
| } |
| |
| // Helper function that returns true if |list| contains an entry with |key|. |
| template <typename T, typename U> |
| bool ListContainsEntry(T& list, U key) { |
| return FindListEntry(list, key) != list.end(); |
| } |
| |
| // Helper function that returns true if |format| may have an alpha channel. |
| // Note: False positives are allowed but false negatives are not. |
| bool FormatHasAlpha(gfx::BufferFormat format) { |
| switch (format) { |
| case gfx::BufferFormat::BGR_565: |
| case gfx::BufferFormat::RGBX_8888: |
| case gfx::BufferFormat::BGRX_8888: |
| case gfx::BufferFormat::YVU_420: |
| case gfx::BufferFormat::YUV_420_BIPLANAR: |
| return false; |
| default: |
| return true; |
| } |
| } |
| |
| // Helper function that returns |size| after adjusting for |transform|. |
| gfx::Size ToTransformedSize(const gfx::Size& size, Transform transform) { |
| switch (transform) { |
| case Transform::NORMAL: |
| case Transform::ROTATE_180: |
| return size; |
| case Transform::ROTATE_90: |
| case Transform::ROTATE_270: |
| return gfx::Size(size.height(), size.width()); |
| } |
| |
| NOTREACHED(); |
| } |
| |
| bool IsDeskContainer(aura::Window* container) { |
| #if defined(OS_CHROMEOS) |
| return ash::desks_util::IsDeskContainer(container); |
| #else |
| return container->id() == ash::kShellWindowId_DefaultContainerDeprecated; |
| #endif // defined(OS_CHROMEOS) |
| } |
| |
| class CustomWindowDelegate : public aura::WindowDelegate { |
| public: |
| explicit CustomWindowDelegate(Surface* surface) : surface_(surface) {} |
| ~CustomWindowDelegate() override {} |
| |
| // Overridden from aura::WindowDelegate: |
| gfx::Size GetMinimumSize() const override { return gfx::Size(); } |
| gfx::Size GetMaximumSize() const override { return gfx::Size(); } |
| void OnBoundsChanged(const gfx::Rect& old_bounds, |
| const gfx::Rect& new_bounds) override {} |
| gfx::NativeCursor GetCursor(const gfx::Point& point) override { |
| views::Widget* widget = |
| views::Widget::GetTopLevelWidgetForNativeView(surface_->window()); |
| if (widget) |
| return widget->GetNativeWindow()->GetCursor(point /* not used */); |
| return ui::CursorType::kNull; |
| } |
| int GetNonClientComponent(const gfx::Point& point) const override { |
| views::Widget* widget = |
| views::Widget::GetTopLevelWidgetForNativeView(surface_->window()); |
| if (widget && IsDeskContainer(widget->GetNativeView()->parent()) && |
| surface_->HitTest(point)) { |
| return HTCLIENT; |
| } |
| |
| return HTNOWHERE; |
| } |
| bool ShouldDescendIntoChildForEventHandling( |
| aura::Window* child, |
| const gfx::Point& location) override { |
| return true; |
| } |
| bool CanFocus() override { return true; } |
| void OnCaptureLost() override {} |
| void OnPaint(const ui::PaintContext& context) override {} |
| void OnDeviceScaleFactorChanged(float old_device_scale_factor, |
| float new_device_scale_factor) override {} |
| void OnWindowDestroying(aura::Window* window) override {} |
| void OnWindowDestroyed(aura::Window* window) override { delete this; } |
| void OnWindowTargetVisibilityChanged(bool visible) override {} |
| void OnWindowOcclusionChanged(aura::Window::OcclusionState occlusion_state, |
| const SkRegion& occluded_region) override { |
| surface_->OnWindowOcclusionChanged(); |
| } |
| bool HasHitTestMask() const override { return true; } |
| void GetHitTestMask(SkPath* mask) const override { |
| surface_->GetHitTestMask(mask); |
| } |
| void OnKeyEvent(ui::KeyEvent* event) override { |
| // Propagates the key event upto the top-level views Widget so that we can |
| // trigger proper events in the views/ash level there. Event handling for |
| // Surfaces is done in a post event handler in keyboard.cc. |
| views::Widget* widget = |
| views::Widget::GetTopLevelWidgetForNativeView(surface_->window()); |
| if (widget) |
| widget->OnKeyEvent(event); |
| } |
| |
| private: |
| Surface* const surface_; |
| |
| DISALLOW_COPY_AND_ASSIGN(CustomWindowDelegate); |
| }; |
| |
| class CustomWindowTargeter : public aura::WindowTargeter { |
| public: |
| CustomWindowTargeter() {} |
| ~CustomWindowTargeter() override {} |
| |
| // Overridden from aura::WindowTargeter: |
| bool EventLocationInsideBounds(aura::Window* window, |
| const ui::LocatedEvent& event) const override { |
| Surface* surface = Surface::AsSurface(window); |
| if (!surface || !surface->IsInputEnabled(surface)) |
| return false; |
| |
| gfx::Point local_point = event.location(); |
| if (window->parent()) |
| aura::Window::ConvertPointToTarget(window->parent(), window, |
| &local_point); |
| return surface->HitTest(local_point); |
| } |
| |
| private: |
| DISALLOW_COPY_AND_ASSIGN(CustomWindowTargeter); |
| }; |
| |
| const std::string& GetApplicationId(aura::Window* window) { |
| static const std::string empty_app_id; |
| if (!window) |
| return empty_app_id; |
| while (window) { |
| const std::string* app_id = exo::GetShellApplicationId(window); |
| if (app_id) |
| return *app_id; |
| window = window->parent(); |
| } |
| return empty_app_id; |
| } |
| |
| } // namespace |
| |
| DEFINE_UI_CLASS_PROPERTY_KEY(int32_t, kClientSurfaceIdKey, 0) |
| |
| ScopedSurface::ScopedSurface(Surface* surface, SurfaceObserver* observer) |
| : surface_(surface), observer_(observer) { |
| surface_->AddSurfaceObserver(observer_); |
| } |
| |
| ScopedSurface::~ScopedSurface() { |
| surface_->RemoveSurfaceObserver(observer_); |
| } |
| |
| //////////////////////////////////////////////////////////////////////////////// |
| // Surface, public: |
| |
| Surface::Surface() |
| : window_( |
| std::make_unique<aura::Window>(new CustomWindowDelegate(this), |
| aura::client::WINDOW_TYPE_CONTROL)) { |
| window_->SetName("ExoSurface"); |
| window_->SetProperty(kSurfaceKey, this); |
| window_->Init(ui::LAYER_NOT_DRAWN); |
| window_->SetEventTargeter(std::make_unique<CustomWindowTargeter>()); |
| window_->set_owned_by_parent(false); |
| WMHelper::GetInstance()->SetDragDropDelegate(window_.get()); |
| } |
| Surface::~Surface() { |
| for (SurfaceObserver& observer : observers_) |
| observer.OnSurfaceDestroying(this); |
| |
| // Call all frame callbacks with a null frame time to indicate that they |
| // have been cancelled. |
| frame_callbacks_.splice(frame_callbacks_.end(), pending_frame_callbacks_); |
| for (const auto& frame_callback : frame_callbacks_) |
| frame_callback.Run(base::TimeTicks()); |
| |
| // Call all presentation callbacks with a null presentation time to indicate |
| // that they have been cancelled. |
| presentation_callbacks_.splice(presentation_callbacks_.end(), |
| pending_presentation_callbacks_); |
| for (const auto& presentation_callback : presentation_callbacks_) |
| presentation_callback.Run(gfx::PresentationFeedback()); |
| |
| WMHelper::GetInstance()->ResetDragDropDelegate(window_.get()); |
| } |
| |
| // static |
| Surface* Surface::AsSurface(const aura::Window* window) { |
| return window->GetProperty(kSurfaceKey); |
| } |
| |
| void Surface::Attach(Buffer* buffer) { |
| Attach(buffer, gfx::Vector2d()); |
| } |
| |
| void Surface::Attach(Buffer* buffer, gfx::Vector2d offset) { |
| TRACE_EVENT2("exo", "Surface::Attach", "buffer_id", |
| buffer ? buffer->gfx_buffer() : nullptr, "app_id", |
| GetApplicationId(window_.get())); |
| has_pending_contents_ = true; |
| pending_buffer_.Reset(buffer ? buffer->AsWeakPtr() : base::WeakPtr<Buffer>()); |
| pending_state_.offset = offset; |
| } |
| |
| gfx::Vector2d Surface::GetBufferOffset() { |
| return state_.offset; |
| } |
| |
| bool Surface::HasPendingAttachedBuffer() const { |
| return pending_buffer_.buffer() != nullptr; |
| } |
| |
| void Surface::Damage(const gfx::Rect& damage) { |
| TRACE_EVENT1("exo", "Surface::Damage", "damage", damage.ToString()); |
| |
| pending_damage_.Union(damage); |
| } |
| |
| void Surface::RequestFrameCallback(const FrameCallback& callback) { |
| TRACE_EVENT0("exo", "Surface::RequestFrameCallback"); |
| |
| pending_frame_callbacks_.push_back(callback); |
| } |
| |
| void Surface::RequestPresentationCallback( |
| const PresentationCallback& callback) { |
| TRACE_EVENT0("exo", "Surface::RequestPresentationCallback"); |
| |
| pending_presentation_callbacks_.push_back(callback); |
| } |
| |
| void Surface::SetOpaqueRegion(const cc::Region& region) { |
| TRACE_EVENT1("exo", "Surface::SetOpaqueRegion", "region", region.ToString()); |
| |
| pending_state_.opaque_region = region; |
| } |
| |
| void Surface::SetInputRegion(const cc::Region& region) { |
| TRACE_EVENT1("exo", "Surface::SetInputRegion", "region", region.ToString()); |
| |
| pending_state_.input_region = region; |
| } |
| |
| void Surface::ResetInputRegion() { |
| TRACE_EVENT0("exo", "Surface::ResetInputRegion"); |
| |
| pending_state_.input_region = base::nullopt; |
| } |
| |
| void Surface::SetInputOutset(int outset) { |
| TRACE_EVENT1("exo", "Surface::SetInputOutset", "outset", outset); |
| |
| pending_state_.input_outset = outset; |
| } |
| |
| void Surface::SetBufferScale(float scale) { |
| TRACE_EVENT1("exo", "Surface::SetBufferScale", "scale", scale); |
| |
| pending_state_.buffer_scale = scale; |
| } |
| |
| void Surface::SetBufferTransform(Transform transform) { |
| TRACE_EVENT1("exo", "Surface::SetBufferTransform", "transform", |
| static_cast<int>(transform)); |
| |
| pending_state_.buffer_transform = transform; |
| } |
| |
| void Surface::AddSubSurface(Surface* sub_surface) { |
| TRACE_EVENT1("exo", "Surface::AddSubSurface", "sub_surface", |
| sub_surface->AsTracedValue()); |
| |
| DCHECK(!sub_surface->window()->parent()); |
| sub_surface->window()->SetBounds( |
| gfx::Rect(sub_surface->window()->bounds().size())); |
| window_->AddChild(sub_surface->window()); |
| |
| DCHECK(!ListContainsEntry(pending_sub_surfaces_, sub_surface)); |
| pending_sub_surfaces_.push_back(std::make_pair(sub_surface, gfx::Point())); |
| sub_surfaces_.push_back(std::make_pair(sub_surface, gfx::Point())); |
| sub_surfaces_changed_ = true; |
| } |
| |
| void Surface::RemoveSubSurface(Surface* sub_surface) { |
| TRACE_EVENT1("exo", "Surface::RemoveSubSurface", "sub_surface", |
| sub_surface->AsTracedValue()); |
| |
| if (sub_surface->window()->IsVisible()) |
| sub_surface->window()->Hide(); |
| window_->RemoveChild(sub_surface->window()); |
| |
| DCHECK(ListContainsEntry(pending_sub_surfaces_, sub_surface)); |
| pending_sub_surfaces_.erase( |
| FindListEntry(pending_sub_surfaces_, sub_surface)); |
| |
| DCHECK(ListContainsEntry(sub_surfaces_, sub_surface)); |
| auto it = FindListEntry(sub_surfaces_, sub_surface); |
| sub_surfaces_.erase(it); |
| // Force recreating resources when the surface is added to a tree again. |
| sub_surface->SurfaceHierarchyResourcesLost(); |
| sub_surfaces_changed_ = true; |
| } |
| |
| void Surface::SetSubSurfacePosition(Surface* sub_surface, |
| const gfx::Point& position) { |
| TRACE_EVENT2("exo", "Surface::SetSubSurfacePosition", "sub_surface", |
| sub_surface->AsTracedValue(), "position", position.ToString()); |
| |
| auto it = FindListEntry(pending_sub_surfaces_, sub_surface); |
| DCHECK(it != pending_sub_surfaces_.end()); |
| if (it->second == position) |
| return; |
| it->second = position; |
| sub_surfaces_changed_ = true; |
| } |
| |
| void Surface::PlaceSubSurfaceAbove(Surface* sub_surface, Surface* reference) { |
| TRACE_EVENT2("exo", "Surface::PlaceSubSurfaceAbove", "sub_surface", |
| sub_surface->AsTracedValue(), "reference", |
| reference->AsTracedValue()); |
| |
| if (sub_surface == reference) { |
| DLOG(WARNING) << "Client tried to place sub-surface above itself"; |
| return; |
| } |
| |
| auto position_it = pending_sub_surfaces_.begin(); |
| if (reference != this) { |
| position_it = FindListEntry(pending_sub_surfaces_, reference); |
| if (position_it == pending_sub_surfaces_.end()) { |
| DLOG(WARNING) << "Client tried to place sub-surface above a reference " |
| "surface that is neither a parent nor a sibling"; |
| return; |
| } |
| |
| // Advance iterator to have |position_it| point to the sibling surface |
| // above |reference|. |
| ++position_it; |
| } |
| |
| DCHECK(ListContainsEntry(pending_sub_surfaces_, sub_surface)); |
| auto it = FindListEntry(pending_sub_surfaces_, sub_surface); |
| if (it == position_it) |
| return; |
| pending_sub_surfaces_.splice(position_it, pending_sub_surfaces_, it); |
| sub_surfaces_changed_ = true; |
| } |
| |
| void Surface::PlaceSubSurfaceBelow(Surface* sub_surface, Surface* sibling) { |
| TRACE_EVENT2("exo", "Surface::PlaceSubSurfaceBelow", "sub_surface", |
| sub_surface->AsTracedValue(), "sibling", |
| sibling->AsTracedValue()); |
| |
| if (sub_surface == sibling) { |
| DLOG(WARNING) << "Client tried to place sub-surface below itself"; |
| return; |
| } |
| |
| auto sibling_it = FindListEntry(pending_sub_surfaces_, sibling); |
| if (sibling_it == pending_sub_surfaces_.end()) { |
| DLOG(WARNING) << "Client tried to place sub-surface below a surface that " |
| "is not a sibling"; |
| return; |
| } |
| |
| DCHECK(ListContainsEntry(pending_sub_surfaces_, sub_surface)); |
| auto it = FindListEntry(pending_sub_surfaces_, sub_surface); |
| if (it == sibling_it) |
| return; |
| pending_sub_surfaces_.splice(sibling_it, pending_sub_surfaces_, it); |
| sub_surfaces_changed_ = true; |
| } |
| |
| void Surface::OnSubSurfaceCommit() { |
| if (delegate_) |
| delegate_->OnSurfaceCommit(); |
| } |
| |
| void Surface::SetViewport(const gfx::Size& viewport) { |
| TRACE_EVENT1("exo", "Surface::SetViewport", "viewport", viewport.ToString()); |
| |
| pending_state_.viewport = viewport; |
| } |
| |
| void Surface::SetCrop(const gfx::RectF& crop) { |
| TRACE_EVENT1("exo", "Surface::SetCrop", "crop", crop.ToString()); |
| |
| pending_state_.crop = crop; |
| } |
| |
| void Surface::SetOnlyVisibleOnSecureOutput(bool only_visible_on_secure_output) { |
| TRACE_EVENT1("exo", "Surface::SetOnlyVisibleOnSecureOutput", |
| "only_visible_on_secure_output", only_visible_on_secure_output); |
| |
| pending_state_.only_visible_on_secure_output = only_visible_on_secure_output; |
| } |
| |
| void Surface::SetBlendMode(SkBlendMode blend_mode) { |
| TRACE_EVENT1("exo", "Surface::SetBlendMode", "blend_mode", |
| static_cast<int>(blend_mode)); |
| |
| pending_state_.blend_mode = blend_mode; |
| } |
| |
| void Surface::SetAlpha(float alpha) { |
| TRACE_EVENT1("exo", "Surface::SetAlpha", "alpha", alpha); |
| |
| pending_state_.alpha = alpha; |
| } |
| |
| void Surface::SetFrame(SurfaceFrameType type) { |
| TRACE_EVENT1("exo", "Surface::SetFrame", "type", static_cast<uint32_t>(type)); |
| |
| if (delegate_) |
| delegate_->OnSetFrame(type); |
| } |
| |
| void Surface::SetFrameColors(SkColor active_color, SkColor inactive_color) { |
| TRACE_EVENT2("exo", "Surface::SetFrameColors", "active_color", active_color, |
| "inactive_color", inactive_color); |
| |
| if (delegate_) |
| delegate_->OnSetFrameColors(active_color, inactive_color); |
| } |
| |
| void Surface::SetStartupId(const char* startup_id) { |
| TRACE_EVENT1("exo", "Surface::SetStartupId", "startup_id", startup_id); |
| |
| if (delegate_) |
| delegate_->OnSetStartupId(startup_id); |
| } |
| |
| void Surface::SetApplicationId(const char* application_id) { |
| TRACE_EVENT1("exo", "Surface::SetApplicationId", "application_id", |
| application_id); |
| |
| if (delegate_) |
| delegate_->OnSetApplicationId(application_id); |
| } |
| |
| void Surface::SetParent(Surface* parent, const gfx::Point& position) { |
| TRACE_EVENT2("exo", "Surface::SetParent", "parent", !!parent, "position", |
| position.ToString()); |
| |
| if (delegate_) |
| delegate_->OnSetParent(parent, position); |
| } |
| |
| void Surface::SetClientSurfaceId(int32_t client_surface_id) { |
| if (client_surface_id) |
| window_->SetProperty(kClientSurfaceIdKey, client_surface_id); |
| else |
| window_->ClearProperty(kClientSurfaceIdKey); |
| } |
| |
| int32_t Surface::GetClientSurfaceId() const { |
| return window_->GetProperty(kClientSurfaceIdKey); |
| } |
| |
| void Surface::SetEmbeddedSurfaceId( |
| base::RepeatingCallback<viz::SurfaceId()> surface_id_callback) { |
| get_current_surface_id_ = std::move(surface_id_callback); |
| first_embedded_surface_id_ = viz::SurfaceId(); |
| } |
| |
| void Surface::SetAcquireFence(std::unique_ptr<gfx::GpuFence> gpu_fence) { |
| TRACE_EVENT1("exo", "Surface::SetAcquireFence", "fence_fd", |
| gpu_fence ? gpu_fence->GetGpuFenceHandle().native_fd.fd : -1); |
| |
| pending_acquire_fence_ = std::move(gpu_fence); |
| } |
| |
| bool Surface::HasPendingAcquireFence() const { |
| return !!pending_acquire_fence_; |
| } |
| |
| void Surface::Commit() { |
| TRACE_EVENT1("exo", "Surface::Commit", "buffer_id", |
| pending_buffer_.buffer() ? pending_buffer_.buffer()->gfx_buffer() |
| : nullptr); |
| |
| for (auto& observer : observers_) |
| observer.OnCommit(this); |
| |
| needs_commit_surface_ = true; |
| if (delegate_) |
| delegate_->OnSurfaceCommit(); |
| else |
| CommitSurfaceHierarchy(false); |
| } |
| |
| void Surface::CommitSurfaceHierarchy(bool synchronized) { |
| TRACE_EVENT0("exo", "Surface::CommitSurfaceHierarchy"); |
| if (needs_commit_surface_ && (synchronized || !IsSynchronized())) { |
| needs_commit_surface_ = false; |
| synchronized = true; |
| |
| // TODO(penghuang): Make the damage more precise for sub surface changes. |
| // https://crbug.com/779704 |
| bool needs_full_damage = |
| sub_surfaces_changed_ || |
| pending_state_.opaque_region != state_.opaque_region || |
| pending_state_.buffer_scale != state_.buffer_scale || |
| pending_state_.buffer_transform != state_.buffer_transform || |
| pending_state_.viewport != state_.viewport || |
| pending_state_.crop != state_.crop || |
| pending_state_.only_visible_on_secure_output != |
| state_.only_visible_on_secure_output || |
| pending_state_.blend_mode != state_.blend_mode || |
| pending_state_.alpha != state_.alpha; |
| |
| bool needs_update_buffer_transform = |
| pending_state_.buffer_scale != state_.buffer_scale || |
| pending_state_.buffer_transform != state_.buffer_transform; |
| |
| #if defined(OS_CHROMEOS) |
| bool needs_output_protection = |
| pending_state_.only_visible_on_secure_output != |
| state_.only_visible_on_secure_output; |
| #endif // defined(OS_CHROMEOS) |
| |
| bool pending_invert_y = false; |
| |
| // If the current state is fully transparent, the last submitted frame will |
| // not include the TextureDrawQuad for the resource, so the resource might |
| // have been released and needs to be updated again. |
| if (!state_.alpha && pending_state_.alpha) |
| needs_update_resource_ = true; |
| |
| state_ = pending_state_; |
| pending_state_.only_visible_on_secure_output = false; |
| |
| window_->SetEventTargetingPolicy( |
| (state_.input_region.has_value() && state_.input_region->IsEmpty()) |
| ? aura::EventTargetingPolicy::kDescendantsOnly |
| : aura::EventTargetingPolicy::kTargetAndDescendants); |
| |
| #if defined(OS_CHROMEOS) |
| if (needs_output_protection) { |
| if (!output_protection_) { |
| output_protection_ = |
| std::make_unique<ash::OutputProtectionDelegate>(window_.get()); |
| } |
| |
| uint32_t protection_mask = state_.only_visible_on_secure_output |
| ? display::CONTENT_PROTECTION_METHOD_HDCP |
| : display::CONTENT_PROTECTION_METHOD_NONE; |
| |
| output_protection_->SetProtection(protection_mask, base::DoNothing()); |
| } |
| #endif // defined(OS_CHROMEOS) |
| |
| // We update contents if Attach() has been called since last commit. |
| if (has_pending_contents_) { |
| has_pending_contents_ = false; |
| |
| bool current_invert_y = |
| current_buffer_.buffer() && current_buffer_.buffer()->y_invert(); |
| pending_invert_y = |
| pending_buffer_.buffer() && pending_buffer_.buffer()->y_invert(); |
| if (current_invert_y != pending_invert_y) |
| needs_update_buffer_transform = true; |
| |
| current_buffer_ = std::move(pending_buffer_); |
| acquire_fence_ = std::move(pending_acquire_fence_); |
| if (state_.alpha) |
| needs_update_resource_ = true; |
| } |
| // Either we didn't have a pending acquire fence, or we had one along with |
| // a new buffer, and it was already moved to acquire_fence_. Note that |
| // it is a commit-time client error to commit a fence without a buffer. |
| DCHECK(!pending_acquire_fence_); |
| |
| if (needs_update_buffer_transform) |
| UpdateBufferTransform(pending_invert_y); |
| |
| // Move pending frame callbacks to the end of |frame_callbacks_|. |
| frame_callbacks_.splice(frame_callbacks_.end(), pending_frame_callbacks_); |
| |
| // Move pending presentation callbacks to the end of |
| // |presentation_callbacks_|. |
| presentation_callbacks_.splice(presentation_callbacks_.end(), |
| pending_presentation_callbacks_); |
| |
| UpdateContentSize(); |
| |
| // Synchronize window hierarchy. This will position and update the stacking |
| // order of all sub-surfaces after committing all pending state of |
| // sub-surface descendants. |
| if (sub_surfaces_changed_) { |
| sub_surfaces_.clear(); |
| aura::Window* stacking_target = nullptr; |
| for (const auto& sub_surface_entry : pending_sub_surfaces_) { |
| Surface* sub_surface = sub_surface_entry.first; |
| sub_surfaces_.push_back(sub_surface_entry); |
| // Move sub-surface to its new position in the stack. |
| if (stacking_target) |
| window_->StackChildAbove(sub_surface->window(), stacking_target); |
| |
| // Stack next sub-surface above this sub-surface. |
| stacking_target = sub_surface->window(); |
| |
| // Update sub-surface position relative to surface origin. |
| sub_surface->window()->SetBounds(gfx::Rect( |
| sub_surface_entry.second, sub_surface->window()->bounds().size())); |
| } |
| sub_surfaces_changed_ = false; |
| } |
| |
| gfx::Rect output_rect(content_size_); |
| if (needs_full_damage) { |
| damage_ = output_rect; |
| } else { |
| // pending_damage_ is in Surface coordinates. |
| damage_.Swap(&pending_damage_); |
| damage_.Intersect(output_rect); |
| } |
| pending_damage_.Clear(); |
| } |
| |
| surface_hierarchy_content_bounds_ = gfx::Rect(content_size_); |
| if (state_.input_region) { |
| hit_test_region_ = *state_.input_region; |
| hit_test_region_.Intersect(surface_hierarchy_content_bounds_); |
| } else { |
| hit_test_region_ = surface_hierarchy_content_bounds_; |
| } |
| |
| int outset = state_.input_outset; |
| if (outset > 0) { |
| gfx::Rect input_rect = surface_hierarchy_content_bounds_; |
| input_rect.Inset(-outset, -outset); |
| hit_test_region_ = input_rect; |
| } |
| |
| for (const auto& sub_surface_entry : base::Reversed(sub_surfaces_)) { |
| auto* sub_surface = sub_surface_entry.first; |
| gfx::Vector2d offset = sub_surface_entry.second.OffsetFromOrigin(); |
| // Synchronously commit all pending state of the sub-surface and its |
| // descendants. |
| sub_surface->CommitSurfaceHierarchy(synchronized); |
| surface_hierarchy_content_bounds_.Union( |
| sub_surface->surface_hierarchy_content_bounds() + offset); |
| hit_test_region_.Union(sub_surface->hit_test_region_ + offset); |
| } |
| } |
| |
| void Surface::AppendSurfaceHierarchyCallbacks( |
| std::list<FrameCallback>* frame_callbacks, |
| std::list<PresentationCallback>* presentation_callbacks) { |
| // Move frame callbacks to the end of |frame_callbacks|. |
| frame_callbacks->splice(frame_callbacks->end(), frame_callbacks_); |
| // Move presentation callbacks to the end of |presentation_callbacks|. |
| presentation_callbacks->splice(presentation_callbacks->end(), |
| presentation_callbacks_); |
| |
| for (const auto& sub_surface_entry : base::Reversed(sub_surfaces_)) { |
| auto* sub_surface = sub_surface_entry.first; |
| sub_surface->AppendSurfaceHierarchyCallbacks(frame_callbacks, |
| presentation_callbacks); |
| } |
| } |
| |
| void Surface::AppendSurfaceHierarchyContentsToFrame( |
| const gfx::Point& origin, |
| float device_scale_factor, |
| FrameSinkResourceManager* resource_manager, |
| viz::CompositorFrame* frame) { |
| // The top most sub-surface is at the front of the RenderPass's quad_list, |
| // so we need composite sub-surface in reversed order. |
| for (const auto& sub_surface_entry : base::Reversed(sub_surfaces_)) { |
| auto* sub_surface = sub_surface_entry.first; |
| // Synchronsouly commit all pending state of the sub-surface and its |
| // decendents. |
| sub_surface->AppendSurfaceHierarchyContentsToFrame( |
| origin + sub_surface_entry.second.OffsetFromOrigin(), |
| device_scale_factor, resource_manager, frame); |
| } |
| |
| if (needs_update_resource_) |
| UpdateResource(resource_manager); |
| |
| AppendContentsToFrame(origin, device_scale_factor, frame); |
| |
| DCHECK(!current_resource_.id || |
| resource_manager->HasReleaseCallbackForResource(current_resource_.id)); |
| } |
| |
| bool Surface::IsSynchronized() const { |
| return delegate_ && delegate_->IsSurfaceSynchronized(); |
| } |
| |
| bool Surface::IsInputEnabled(Surface* surface) const { |
| return !delegate_ || delegate_->IsInputEnabled(surface); |
| } |
| |
| bool Surface::HasHitTestRegion() const { |
| return !hit_test_region_.IsEmpty(); |
| } |
| |
| bool Surface::HitTest(const gfx::Point& point) const { |
| return hit_test_region_.Contains(point); |
| } |
| |
| void Surface::GetHitTestMask(SkPath* mask) const { |
| hit_test_region_.GetBoundaryPath(mask); |
| } |
| |
| void Surface::SetSurfaceDelegate(SurfaceDelegate* delegate) { |
| DCHECK(!delegate_ || !delegate); |
| delegate_ = delegate; |
| } |
| |
| bool Surface::HasSurfaceDelegate() const { |
| return !!delegate_; |
| } |
| |
| void Surface::AddSurfaceObserver(SurfaceObserver* observer) { |
| observers_.AddObserver(observer); |
| } |
| |
| void Surface::RemoveSurfaceObserver(SurfaceObserver* observer) { |
| observers_.RemoveObserver(observer); |
| } |
| |
| bool Surface::HasSurfaceObserver(const SurfaceObserver* observer) const { |
| return observers_.HasObserver(observer); |
| } |
| |
| std::unique_ptr<base::trace_event::TracedValue> Surface::AsTracedValue() const { |
| std::unique_ptr<base::trace_event::TracedValue> value( |
| new base::trace_event::TracedValue()); |
| value->SetString("name", window_->layer()->name()); |
| return value; |
| } |
| |
| bool Surface::IsStylusOnly() { |
| return window_->GetProperty(kStylusOnlyKey); |
| } |
| |
| void Surface::SetStylusOnly() { |
| window_->SetProperty(kStylusOnlyKey, true); |
| } |
| |
| void Surface::SurfaceHierarchyResourcesLost() { |
| // Update resource and full damage are needed for next frame. |
| needs_update_resource_ = true; |
| for (const auto& sub_surface : sub_surfaces_) |
| sub_surface.first->SurfaceHierarchyResourcesLost(); |
| } |
| |
| bool Surface::FillsBoundsOpaquely() const { |
| return !current_resource_has_alpha_ || |
| state_.blend_mode == SkBlendMode::kSrc || |
| state_.opaque_region.Contains(gfx::Rect(content_size_)); |
| } |
| |
| void Surface::SetOcclusionTracking(bool tracking) { |
| is_tracking_occlusion_ = tracking; |
| // TODO(edcourtney): Currently, it doesn't seem to be possible to stop |
| // tracking the occlusion state once started, but it would be nice to stop if |
| // the tracked occlusion region becomes empty. |
| if (is_tracking_occlusion_) |
| window()->TrackOcclusionState(); |
| } |
| |
| void Surface::SetSurfaceHierarchyContentBoundsForTest( |
| const gfx::Rect& content_bounds) { |
| surface_hierarchy_content_bounds_ = content_bounds; |
| } |
| |
| //////////////////////////////////////////////////////////////////////////////// |
| // Buffer, private: |
| |
| Surface::State::State() {} |
| |
| Surface::State::~State() = default; |
| |
| bool Surface::State::operator==(const State& other) const { |
| return other.opaque_region == opaque_region && |
| other.input_region == input_region && |
| other.buffer_scale == buffer_scale && |
| other.buffer_transform == buffer_transform && |
| other.viewport == viewport && other.crop == crop && |
| other.only_visible_on_secure_output == only_visible_on_secure_output && |
| other.blend_mode == blend_mode && other.alpha == alpha; |
| } |
| |
| Surface::BufferAttachment::BufferAttachment() {} |
| |
| Surface::BufferAttachment::~BufferAttachment() { |
| if (buffer_) |
| buffer_->OnDetach(); |
| } |
| |
| Surface::BufferAttachment& Surface::BufferAttachment::operator=( |
| BufferAttachment&& other) { |
| if (buffer_) |
| buffer_->OnDetach(); |
| buffer_ = other.buffer_; |
| size_ = other.size_; |
| other.buffer_ = base::WeakPtr<Buffer>(); |
| other.size_ = gfx::Size(); |
| return *this; |
| } |
| |
| base::WeakPtr<Buffer>& Surface::BufferAttachment::buffer() { |
| return buffer_; |
| } |
| |
| const base::WeakPtr<Buffer>& Surface::BufferAttachment::buffer() const { |
| return buffer_; |
| } |
| |
| const gfx::Size& Surface::BufferAttachment::size() const { |
| return size_; |
| } |
| |
| void Surface::BufferAttachment::Reset(base::WeakPtr<Buffer> buffer) { |
| size_ = gfx::Size(); |
| if (buffer) { |
| buffer->OnAttach(); |
| size_ = buffer->GetSize(); |
| } |
| if (buffer_) |
| buffer_->OnDetach(); |
| buffer_ = buffer; |
| } |
| |
| void Surface::UpdateResource(FrameSinkResourceManager* resource_manager) { |
| DCHECK(needs_update_resource_); |
| needs_update_resource_ = false; |
| if (current_buffer_.buffer()) { |
| if (current_buffer_.buffer()->ProduceTransferableResource( |
| resource_manager, std::move(acquire_fence_), |
| state_.only_visible_on_secure_output, ¤t_resource_)) { |
| current_resource_has_alpha_ = |
| FormatHasAlpha(current_buffer_.buffer()->GetFormat()); |
| } else { |
| current_resource_.id = 0; |
| // Use the buffer's size, so the AppendContentsToFrame() will append |
| // a SolidColorDrawQuad with the buffer's size. |
| current_resource_.size = current_buffer_.size(); |
| current_resource_has_alpha_ = false; |
| } |
| } else { |
| current_resource_.id = 0; |
| current_resource_.size = gfx::Size(); |
| current_resource_has_alpha_ = false; |
| } |
| } |
| |
| void Surface::UpdateBufferTransform(bool y_invert) { |
| SkMatrix buffer_matrix; |
| switch (state_.buffer_transform) { |
| case Transform::NORMAL: |
| buffer_matrix.setIdentity(); |
| break; |
| case Transform::ROTATE_90: |
| buffer_matrix.setSinCos(-1, 0, 0.5f, 0.5f); |
| break; |
| case Transform::ROTATE_180: |
| buffer_matrix.setSinCos(0, -1, 0.5f, 0.5f); |
| break; |
| case Transform::ROTATE_270: |
| buffer_matrix.setSinCos(1, 0, 0.5f, 0.5f); |
| break; |
| } |
| if (y_invert) |
| buffer_matrix.preScale(1, -1, 0.5f, 0.5f); |
| buffer_matrix.postIDiv(state_.buffer_scale, state_.buffer_scale); |
| buffer_transform_ = gfx::Transform(buffer_matrix); |
| } |
| |
| void Surface::AppendContentsToFrame(const gfx::Point& origin, |
| float device_scale_factor, |
| viz::CompositorFrame* frame) { |
| const std::unique_ptr<viz::RenderPass>& render_pass = |
| frame->render_pass_list.back(); |
| gfx::Rect output_rect(origin, content_size_); |
| gfx::Rect quad_rect(0, 0, 1, 1); |
| |
| // Surface bounds are in DIPs, but |damage_rect| and |output_rect| are in |
| // pixels, so we need to scale by the |device_scale_factor|. |
| gfx::Rect damage_rect = damage_.bounds(); |
| if (!damage_rect.IsEmpty()) { |
| // Outset damage by 1 DIP to as damage is in surface coordinate space and |
| // client might not be aware of |device_scale_factor| and the |
| // scaling/filtering it requires. |
| damage_rect.Inset(-1, -1); |
| damage_rect += origin.OffsetFromOrigin(); |
| damage_rect.Intersect(output_rect); |
| render_pass->damage_rect.Union( |
| gfx::ConvertRectToPixel(device_scale_factor, damage_rect)); |
| } |
| |
| // Compute the total transformation from post-transform buffer coordinates to |
| // target coordinates. |
| SkMatrix viewport_to_target_matrix; |
| // Scale and offset the normalized space to fit the content size rectangle. |
| viewport_to_target_matrix.setScale( |
| content_size_.width() * state_.buffer_scale, |
| content_size_.height() * state_.buffer_scale); |
| viewport_to_target_matrix.postTranslate(origin.x(), origin.y()); |
| // Convert from DPs to pixels. |
| viewport_to_target_matrix.postScale(device_scale_factor, device_scale_factor); |
| |
| gfx::Transform quad_to_target_transform(buffer_transform_); |
| quad_to_target_transform.ConcatTransform( |
| gfx::Transform(viewport_to_target_matrix)); |
| |
| bool are_contents_opaque = !current_resource_has_alpha_ || |
| state_.blend_mode == SkBlendMode::kSrc || |
| state_.opaque_region.Contains(output_rect); |
| |
| viz::SharedQuadState* quad_state = |
| render_pass->CreateAndAppendSharedQuadState(); |
| quad_state->SetAll( |
| quad_to_target_transform, quad_rect /*quad_layer_rect=*/, |
| quad_rect /*visible_quad_layer_rect=*/, |
| gfx::RRectF() /*rounded_corner_bounds=*/, gfx::Rect() /*clip_rect=*/, |
| false /*is_clipped=*/, are_contents_opaque, state_.alpha /*opacity=*/, |
| SkBlendMode::kSrcOver /*blend_mode=*/, 0 /*sorting_context_id=*/); |
| |
| if (current_resource_.id) { |
| gfx::RectF uv_crop(gfx::SizeF(1, 1)); |
| if (!state_.crop.IsEmpty()) { |
| // The crop rectangle is a post-transformation rectangle. To get the UV |
| // coordinates, we need to convert it to normalized buffer coordinates and |
| // pass them through the inverse of the buffer transformation. |
| uv_crop = gfx::RectF(state_.crop); |
| gfx::Size transformed_buffer_size( |
| ToTransformedSize(current_resource_.size, state_.buffer_transform)); |
| if (!transformed_buffer_size.IsEmpty()) |
| uv_crop.Scale(1.f / transformed_buffer_size.width(), |
| 1.f / transformed_buffer_size.height()); |
| |
| buffer_transform_.TransformRectReverse(&uv_crop); |
| } |
| |
| SkColor background_color = SK_ColorTRANSPARENT; |
| if (current_resource_has_alpha_ && are_contents_opaque) |
| background_color = SK_ColorBLACK; // Avoid writing alpha < 1 |
| |
| // If this surface is being replaced by a SurfaceId emit a SurfaceDrawQuad. |
| if (get_current_surface_id_) { |
| auto current_surface_id = get_current_surface_id_.Run(); |
| // If the surface ID is valid update it, otherwise keep showing the old |
| // one for now. |
| if (current_surface_id.is_valid()) { |
| latest_embedded_surface_id_ = current_surface_id; |
| if (!current_surface_id.HasSameEmbedTokenAs( |
| first_embedded_surface_id_)) { |
| first_embedded_surface_id_ = current_surface_id; |
| } |
| } |
| if (latest_embedded_surface_id_.is_valid()) { |
| viz::SurfaceDrawQuad* surface_quad = |
| render_pass->CreateAndAppendDrawQuad<viz::SurfaceDrawQuad>(); |
| surface_quad->SetNew(quad_state, quad_rect, quad_rect, |
| viz::SurfaceRange(first_embedded_surface_id_, |
| latest_embedded_surface_id_), |
| background_color, |
| /*stretch_content_to_fill_bounds=*/true, |
| /*ignores_input_event=*/false); |
| } |
| } else if (state_.alpha) { |
| // Texture quad is only needed if buffer is not fully transparent. |
| viz::TextureDrawQuad* texture_quad = |
| render_pass->CreateAndAppendDrawQuad<viz::TextureDrawQuad>(); |
| float vertex_opacity[4] = {1.0, 1.0, 1.0, 1.0}; |
| texture_quad->SetNew( |
| quad_state, quad_rect, quad_rect, |
| /* needs_blending=*/!are_contents_opaque, current_resource_.id, |
| /* premultiplied_alpha=*/true, uv_crop.origin(), |
| uv_crop.bottom_right(), background_color, vertex_opacity, |
| /* y_flipped=*/false, /* nearest_neighbor=*/false, |
| state_.only_visible_on_secure_output, |
| gfx::ProtectedVideoType::kClear); |
| if (current_resource_.is_overlay_candidate) |
| texture_quad->set_resource_size_in_pixels(current_resource_.size); |
| frame->resource_list.push_back(current_resource_); |
| } |
| } else { |
| viz::SolidColorDrawQuad* solid_quad = |
| render_pass->CreateAndAppendDrawQuad<viz::SolidColorDrawQuad>(); |
| solid_quad->SetNew(quad_state, quad_rect, quad_rect, SK_ColorBLACK, |
| false /* force_anti_aliasing_off */); |
| } |
| } |
| |
| void Surface::UpdateContentSize() { |
| gfx::Size content_size; |
| if (!state_.viewport.IsEmpty()) { |
| content_size = state_.viewport; |
| } else if (!state_.crop.IsEmpty()) { |
| DLOG_IF(WARNING, !gfx::IsExpressibleAsInt(state_.crop.width()) || |
| !gfx::IsExpressibleAsInt(state_.crop.height())) |
| << "Crop rectangle size (" << state_.crop.size().ToString() |
| << ") most be expressible using integers when viewport is not set"; |
| content_size = gfx::ToCeiledSize(state_.crop.size()); |
| } else { |
| content_size = gfx::ToCeiledSize( |
| gfx::ScaleSize(gfx::SizeF(ToTransformedSize(current_buffer_.size(), |
| state_.buffer_transform)), |
| 1.0f / state_.buffer_scale)); |
| } |
| |
| // Enable/disable sub-surface based on if it has contents. |
| if (has_contents()) |
| window_->Show(); |
| else |
| window_->Hide(); |
| |
| if (content_size_ != content_size) { |
| content_size_ = content_size; |
| window_->SetBounds(gfx::Rect(window_->bounds().origin(), content_size_)); |
| } |
| } |
| |
| void Surface::OnWindowOcclusionChanged() { |
| if (!is_tracking_occlusion_) |
| return; |
| |
| for (SurfaceObserver& observer : observers_) |
| observer.OnWindowOcclusionChanged(this); |
| } |
| |
| } // namespace exo |