| // Copyright 2015 The Chromium Authors |
| // 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/display/output_protection_delegate.h" |
| #include "ash/public/cpp/window_properties.h" |
| #include "ash/wm/desks/desks_util.h" |
| #include "base/containers/adapters.h" |
| #include "base/functional/callback_helpers.h" |
| #include "base/logging.h" |
| #include "base/memory/raw_ptr.h" |
| #include "base/notreached.h" |
| #include "base/numerics/safe_conversions.h" |
| #include "base/ranges/algorithm.h" |
| #include "base/strings/stringprintf.h" |
| #include "base/trace_event/trace_event.h" |
| #include "base/trace_event/traced_value.h" |
| #include "build/build_config.h" |
| #include "components/exo/buffer.h" |
| #include "components/exo/frame_sink_resource_manager.h" |
| #include "components/exo/layer_tree_frame_sink_holder.h" |
| #include "components/exo/shell_surface_util.h" |
| #include "components/exo/surface_delegate.h" |
| #include "components/exo/surface_observer.h" |
| #include "components/exo/window_properties.h" |
| #include "components/exo/wm_helper.h" |
| #include "components/viz/common/quads/compositor_frame.h" |
| #include "components/viz/common/quads/compositor_render_pass.h" |
| #include "components/viz/common/quads/draw_quad.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/quads/tile_draw_quad.h" |
| #include "components/viz/common/resources/resource_id.h" |
| #include "media/media_buildflags.h" |
| #include "third_party/skia/include/core/SkColor.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/cursor/mojom/cursor_type.mojom-shared.h" |
| #include "ui/base/hit_test.h" |
| #include "ui/compositor/layer.h" |
| #include "ui/display/display.h" |
| #include "ui/display/screen.h" |
| #include "ui/events/event.h" |
| #include "ui/gfx/buffer_format_util.h" |
| #include "ui/gfx/buffer_types.h" |
| #include "ui/gfx/color_space.h" |
| #include "ui/gfx/geometry/dip_util.h" |
| #include "ui/gfx/geometry/point.h" |
| #include "ui/gfx/geometry/point_conversions.h" |
| #include "ui/gfx/geometry/point_f.h" |
| #include "ui/gfx/geometry/rect.h" |
| #include "ui/gfx/geometry/rect_conversions.h" |
| #include "ui/gfx/geometry/rect_f.h" |
| #include "ui/gfx/geometry/size_conversions.h" |
| #include "ui/gfx/geometry/skia_conversions.h" |
| #include "ui/gfx/geometry/transform_util.h" |
| #include "ui/gfx/geometry/vector2d_f.h" |
| #include "ui/gfx/gpu_fence.h" |
| #include "ui/gfx/gpu_memory_buffer.h" |
| #include "ui/gfx/presentation_feedback.h" |
| #include "ui/views/widget/widget.h" |
| |
| DEFINE_UI_CLASS_PROPERTY_TYPE(exo::Surface*) |
| |
| namespace exo { |
| |
| BASE_FEATURE(kExoPerSurfaceOcclusion, |
| "ExoPerSurfaceOcclusion", |
| base::FEATURE_ENABLED_BY_DEFAULT); |
| |
| namespace { |
| |
| bool IsExoOcclusionEnabled() { |
| static bool is_enabled = |
| base::FeatureList::IsEnabled(kExoPerSurfaceOcclusion); |
| return is_enabled; |
| } |
| |
| // 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 base::ranges::find(list, key, &T::value_type::first); |
| } |
| |
| // 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(); |
| } |
| |
| bool FormatHasAlpha(gfx::BufferFormat format) { |
| return gfx::AlphaBitsForBufferFormat(format) != 0; |
| } |
| |
| Transform InvertY(Transform transform) { |
| switch (transform) { |
| case Transform::NORMAL: |
| return Transform::FLIPPED_ROTATE_180; |
| case Transform::ROTATE_90: |
| return Transform::FLIPPED_ROTATE_270; |
| case Transform::ROTATE_180: |
| return Transform::FLIPPED; |
| case Transform::ROTATE_270: |
| return Transform::FLIPPED_ROTATE_90; |
| case Transform::FLIPPED: |
| return Transform::ROTATE_180; |
| case Transform::FLIPPED_ROTATE_90: |
| return Transform::ROTATE_270; |
| case Transform::FLIPPED_ROTATE_180: |
| return Transform::NORMAL; |
| case Transform::FLIPPED_ROTATE_270: |
| return Transform::ROTATE_90; |
| } |
| NOTREACHED(); |
| } |
| |
| // Returns a gfx::Transform that can transform a (0,0 1x1) rect to the same |
| // rect while rotate/flip the contents about (0.5, 0.5) origin. It's equivalent |
| // to rotating/flipping about (0, 0) origin then translating by |
| // (0 or 1, 0 or 1). Note that the rotations are counter-clockwise. |
| gfx::Transform ToBufferTransformMatrix(Transform transform, bool invert_y) { |
| switch (invert_y ? InvertY(transform) : transform) { |
| case Transform::NORMAL: |
| return gfx::Transform(); |
| case Transform::ROTATE_90: |
| return gfx::Transform::Affine(0, -1, 1, 0, 0, 1); |
| case Transform::ROTATE_180: |
| return gfx::Transform::Affine(-1, 0, 0, -1, 1, 1); |
| case Transform::ROTATE_270: |
| return gfx::Transform::Affine(0, 1, -1, 0, 1, 0); |
| case Transform::FLIPPED: |
| return gfx::Transform::Affine(-1, 0, 0, 1, 1, 0); |
| case Transform::FLIPPED_ROTATE_90: |
| return gfx::Transform::Affine(0, 1, 1, 0, 0, 0); |
| case Transform::FLIPPED_ROTATE_180: |
| return gfx::Transform::Affine(1, 0, 0, -1, 0, 1); |
| case Transform::FLIPPED_ROTATE_270: |
| return gfx::Transform::Affine(0, -1, -1, 0, 1, 1); |
| } |
| NOTREACHED(); |
| } |
| |
| // 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: |
| case Transform::FLIPPED: |
| case Transform::FLIPPED_ROTATE_180: |
| return size; |
| case Transform::ROTATE_90: |
| case Transform::ROTATE_270: |
| case Transform::FLIPPED_ROTATE_90: |
| case Transform::FLIPPED_ROTATE_270: |
| return gfx::Size(size.height(), size.width()); |
| } |
| |
| NOTREACHED(); |
| } |
| |
| bool IsDeskContainer(aura::Window* container) { |
| return ash::desks_util::IsDeskContainer(container); |
| } |
| |
| class CustomWindowDelegate : public aura::WindowDelegate { |
| public: |
| explicit CustomWindowDelegate(Surface* surface) : surface_(surface) {} |
| |
| CustomWindowDelegate(const CustomWindowDelegate&) = delete; |
| CustomWindowDelegate& operator=(const CustomWindowDelegate&) = delete; |
| |
| ~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::mojom::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 { |
| surface_->OnScaleFactorChanged(old_device_scale_factor, |
| new_device_scale_factor); |
| } |
| void OnWindowDestroying(aura::Window* window) override {} |
| void OnWindowDestroyed(aura::Window* window) override { delete this; } |
| void OnWindowTargetVisibilityChanged(bool visible) override {} |
| void OnWindowOcclusionChanged( |
| aura::Window::OcclusionState old_occlusion_state, |
| aura::Window::OcclusionState new_occlusion_state) override { |
| surface_->OnWindowOcclusionChanged(old_occlusion_state, |
| new_occlusion_state); |
| } |
| 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: |
| const raw_ptr<Surface> surface_; |
| }; |
| |
| class CustomWindowTargeter : public aura::WindowTargeter { |
| public: |
| CustomWindowTargeter() {} |
| |
| CustomWindowTargeter(const CustomWindowTargeter&) = delete; |
| CustomWindowTargeter& operator=(const CustomWindowTargeter&) = delete; |
| |
| ~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 = |
| ConvertEventLocationToWindowCoordinates(window, event); |
| return surface->HitTest(local_point); |
| } |
| }; |
| |
| 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; |
| } |
| |
| int surface_id = 0; |
| |
| void ImmediateExplicitRelease( |
| Buffer::PerCommitExplicitReleaseCallback callback) { |
| if (callback) |
| std::move(callback).Run(/*release_fence=*/gfx::GpuFenceHandle()); |
| } |
| |
| } // namespace |
| |
| DEFINE_OWNED_UI_CLASS_PROPERTY_KEY(std::string, kClientSurfaceIdKey, nullptr) |
| |
| // A property key to store the window session Id set by client or full_restore |
| // component. |
| DEFINE_UI_CLASS_PROPERTY_KEY(int32_t, kWindowSessionId, -1) |
| |
| 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(base::StringPrintf("ExoSurface-%d", surface_id++)); |
| window_->SetProperty(kSurfaceKey, this); |
| window_->Init(ui::LAYER_NOT_DRAWN); |
| window_->SetEventTargeter(std::make_unique<CustomWindowTargeter>()); |
| window_->set_owned_by_parent(false); |
| } |
| |
| 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. |
| state_.frame_callbacks.splice(state_.frame_callbacks.end(), |
| cached_state_.frame_callbacks); |
| state_.frame_callbacks.splice(state_.frame_callbacks.end(), |
| pending_state_.frame_callbacks); |
| for (const auto& frame_callback : state_.frame_callbacks) |
| frame_callback.Run(base::TimeTicks()); |
| |
| // Call all presentation callbacks with a null presentation time to indicate |
| // that they have been cancelled. |
| state_.presentation_callbacks.splice(state_.presentation_callbacks.end(), |
| cached_state_.presentation_callbacks); |
| state_.presentation_callbacks.splice(state_.presentation_callbacks.end(), |
| pending_state_.presentation_callbacks); |
| for (const auto& presentation_callback : state_.presentation_callbacks) |
| presentation_callback.Run(gfx::PresentationFeedback()); |
| |
| // Call explicit release on all explicit release callbacks that have been |
| // committed. |
| ImmediateExplicitRelease( |
| std::move(state_.per_commit_explicit_release_callback_)); |
| ImmediateExplicitRelease( |
| std::move(cached_state_.per_commit_explicit_release_callback_)); |
| |
| // Do not reset the DragDropDelegate in order to handle exit upon deletion. |
| } |
| |
| // static |
| Surface* Surface::AsSurface(const aura::Window* window) { |
| return window->GetProperty(kSurfaceKey); |
| } |
| |
| std::vector<raw_ptr<aura::Window, VectorExperimental>> |
| Surface::GetChildWindows() const { |
| std::vector<raw_ptr<aura::Window, VectorExperimental>> children; |
| for (const auto& [sub_surface, _] : sub_surfaces_) { |
| children.push_back(sub_surface->window()); |
| } |
| return children; |
| } |
| |
| 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->GetBufferId() : nullptr, "app_id", |
| GetApplicationId(window_.get())); |
| has_pending_contents_ = true; |
| if (!pending_state_.buffer.has_value()) |
| pending_state_.buffer.emplace(); |
| pending_state_.buffer->Reset(buffer ? buffer->AsWeakPtr() |
| : base::WeakPtr<Buffer>()); |
| pending_state_.basic_state.offset = offset; |
| } |
| |
| gfx::Vector2d Surface::GetBufferOffset() { |
| return state_.basic_state.offset; |
| } |
| |
| bool Surface::HasPendingAttachedBuffer() const { |
| return pending_state_.buffer.has_value() && |
| pending_state_.buffer->buffer() != nullptr; |
| } |
| |
| void Surface::Damage(const gfx::Rect& damage) { |
| TRACE_EVENT1("exo", "Surface::Damage", "damage", damage.ToString()); |
| |
| gfx::Rect t_damage = damage; |
| if (t_damage.width() == 0x7FFFFFFF) { |
| t_damage.set_width(0x7FFFFFFE); |
| } |
| if (t_damage.height() == 0x7FFFFFFF) { |
| t_damage.set_height(0x7FFFFFFE); |
| } |
| |
| // SkRegion forbids 0x7FFFFFFF (INT32_MAX) as width or height, see |
| // SkRegion_kRunTypeSentinel, and would mark the resulting region from the |
| // union below as empty. See https://crbug.com/1463905 |
| gfx::Rect intersected_damage = gfx::Rect(0x7FFFFFFE, 0x7FFFFFFE); |
| intersected_damage.Intersect(t_damage); |
| pending_state_.damage.Union(intersected_damage); |
| } |
| |
| void Surface::RequestFrameCallback(const FrameCallback& callback) { |
| TRACE_EVENT0("exo", "Surface::RequestFrameCallback"); |
| |
| pending_state_.frame_callbacks.push_back(callback); |
| } |
| |
| void Surface::RequestPresentationCallback( |
| const PresentationCallback& callback) { |
| TRACE_EVENT0("exo", "Surface::RequestPresentationCallback"); |
| |
| pending_state_.presentation_callbacks.push_back(callback); |
| } |
| |
| void Surface::SetOpaqueRegion(const cc::Region& region) { |
| TRACE_EVENT1("exo", "Surface::SetOpaqueRegion", "region", region.ToString()); |
| |
| pending_state_.basic_state.opaque_region = region; |
| } |
| |
| void Surface::SetInputRegion(const cc::Region& region) { |
| TRACE_EVENT1("exo", "Surface::SetInputRegion", "region", region.ToString()); |
| |
| pending_state_.basic_state.input_region = region; |
| } |
| |
| void Surface::ResetInputRegion() { |
| TRACE_EVENT0("exo", "Surface::ResetInputRegion"); |
| |
| pending_state_.basic_state.input_region = std::nullopt; |
| } |
| |
| void Surface::SetInputOutset(int outset) { |
| TRACE_EVENT1("exo", "Surface::SetInputOutset", "outset", outset); |
| |
| pending_state_.basic_state.input_outset = outset; |
| } |
| |
| void Surface::SetBufferScale(float scale) { |
| TRACE_EVENT1("exo", "Surface::SetBufferScale", "scale", scale); |
| |
| pending_state_.basic_state.buffer_scale = scale; |
| } |
| |
| void Surface::SetBufferTransform(Transform transform) { |
| TRACE_EVENT1("exo", "Surface::SetBufferTransform", "transform", |
| static_cast<int>(transform)); |
| |
| pending_state_.basic_state.buffer_transform = transform; |
| } |
| |
| void Surface::AddSubSurface(Surface* sub_surface) { |
| if (sub_surface->is_augmented()) { |
| auto* render_layer = sub_surface; |
| DCHECK(!render_layer->window()->parent()); |
| DCHECK(!ListContainsEntry(render_layers_, render_layer)); |
| render_layers_.emplace_back(render_layer, gfx::PointF()); |
| return; |
| } |
| |
| TRACE_EVENT1("exo", "Surface::AddSubSurface", "sub_surface", |
| sub_surface->AsTracedValue()); |
| |
| DCHECK(!is_augmented()); |
| 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::PointF())); |
| sub_surfaces_.push_back(std::make_pair(sub_surface, gfx::PointF())); |
| sub_surfaces_changed_ = true; |
| |
| // Propagate the kSkipImeProcessing property to the new child. |
| if (window_->GetProperty(aura::client::kSkipImeProcessing)) { |
| sub_surface->window()->SetProperty(aura::client::kSkipImeProcessing, true); |
| } |
| |
| // The shell might have not be added to the root yet. |
| if (window_->GetRootWindow()) { |
| auto display = |
| display::Screen::GetScreen()->GetDisplayNearestWindow(window_.get()); |
| sub_surface->UpdateDisplay(display::kInvalidDisplayId, display.id()); |
| } |
| } |
| |
| void Surface::OnNewOutputAdded() { |
| if (delegate_) |
| delegate_->OnNewOutputAdded(); |
| } |
| |
| void Surface::RemoveSubSurface(Surface* sub_surface) { |
| if (sub_surface->is_augmented()) { |
| auto* render_layer = sub_surface; |
| DCHECK(ListContainsEntry(render_layers_, render_layer)); |
| auto it = FindListEntry(render_layers_, render_layer); |
| render_layers_.erase(it); |
| // Force recreating resources when the render_layer is added to a tree |
| // again. |
| render_layer->SurfaceHierarchyResourcesLost(); |
| return; |
| } |
| |
| TRACE_EVENT1("exo", "Surface::RemoveSubSurface", "sub_surface", |
| sub_surface->AsTracedValue()); |
| |
| if (sub_surface->window()->IsVisible()) { |
| sub_surface->window()->Hide(); |
| } |
| if (sub_surface->window()->parent() == window_.get()) { |
| 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); |
| |
| // Removing a non augmented subsurface extends the damage to the removed |
| // subsurface. |
| if (it != sub_surfaces_.end()) { |
| auto extended_damage = gfx::RectF(it->second, it->first->content_size()); |
| if (extended_damage_dp_.has_value()) { |
| extended_damage_dp_->Union(extended_damage); |
| } else { |
| extended_damage_dp_.emplace(extended_damage); |
| } |
| } |
| |
| 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::PointF& position) { |
| if (sub_surface->is_augmented()) { |
| auto* render_layer = sub_surface; |
| auto it = FindListEntry(render_layers_, render_layer); |
| DCHECK(it != render_layers_.end()); |
| if (it->second == position) { |
| return; |
| } |
| it->second = position; |
| return; |
| } |
| |
| 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) { |
| if (sub_surface == reference) { |
| DLOG(WARNING) << "Client tried to place sub-surface above itself"; |
| return; |
| } |
| if (sub_surface->is_augmented() != reference->is_augmented() && |
| reference != this) { |
| DLOG(WARNING) << "Client tried to stack a augmented-surface relative to a " |
| "sub-surface"; |
| return; |
| } |
| |
| if (sub_surface->is_augmented()) { |
| DoPlaceAboveOrBelow(sub_surface, reference, render_layers_, |
| /*place_above=*/true); |
| } else { |
| TRACE_EVENT2("exo", "Surface::PlaceSubSurfaceAbove", "sub_surface", |
| sub_surface->AsTracedValue(), "reference", |
| reference->AsTracedValue()); |
| |
| if (DoPlaceAboveOrBelow(sub_surface, reference, pending_sub_surfaces_, |
| /*place_above=*/true)) { |
| sub_surfaces_changed_ = true; |
| } |
| } |
| } |
| |
| void Surface::PlaceSubSurfaceBelow(Surface* sub_surface, Surface* sibling) { |
| if (sub_surface == sibling) { |
| DLOG(WARNING) << "Client tried to place sub-surface below itself"; |
| return; |
| } |
| if (sub_surface->is_augmented() != sibling->is_augmented()) { |
| DLOG(WARNING) << "Client tried to stack a augmented-surface relative to a " |
| "sub-surface"; |
| return; |
| } |
| |
| if (sub_surface->is_augmented()) { |
| DoPlaceAboveOrBelow(sub_surface, sibling, render_layers_, |
| /*place_above=*/false); |
| } else { |
| TRACE_EVENT2("exo", "Surface::PlaceSubSurfaceBelow", "sub_surface", |
| sub_surface->AsTracedValue(), "sibling", |
| sibling->AsTracedValue()); |
| |
| if (DoPlaceAboveOrBelow(sub_surface, sibling, pending_sub_surfaces_, |
| /*place_above=*/false)) { |
| sub_surfaces_changed_ = true; |
| } |
| } |
| } |
| |
| bool Surface::DoPlaceAboveOrBelow(Surface* child, |
| Surface* reference, |
| SubSurfaceEntryList& list, |
| bool place_above) { |
| auto position_it = (place_above && reference == this) |
| ? list.begin() |
| : FindListEntry(list, reference); |
| if (position_it == list.end()) { |
| LOG(WARNING) << "Client tried place a sub-surface " |
| << (place_above ? "above" : "below") |
| << " an invalid reference"; |
| return false; |
| } |
| |
| DCHECK(ListContainsEntry(list, child)); |
| auto it = FindListEntry(list, child); |
| |
| if (place_above && reference != this) { |
| ++position_it; |
| } else { |
| --position_it; |
| } |
| // If |child| is already immediately at the right position, do nothing. |
| if (it == position_it) { |
| return false; |
| } |
| if (!place_above) { |
| ++position_it; |
| } |
| |
| list.splice(position_it, list, it); |
| return true; |
| } |
| |
| void Surface::OnSubSurfaceCommit() { |
| if (delegate_) |
| delegate_->OnSurfaceCommit(); |
| } |
| |
| void Surface::SetRoundedCorners(const gfx::RRectF& rounded_corners_bounds, |
| bool commit_override) { |
| TRACE_EVENT1("exo", "Surface::SetRoundedCorner", "corners", |
| rounded_corners_bounds.ToString()); |
| |
| if (rounded_corners_bounds != pending_state_.rounded_corners_bounds) { |
| has_pending_contents_ = true; |
| pending_state_.rounded_corners_bounds = rounded_corners_bounds; |
| } |
| |
| if (commit_override && |
| rounded_corners_bounds != state_.rounded_corners_bounds) { |
| state_.rounded_corners_bounds = rounded_corners_bounds; |
| } |
| } |
| |
| void Surface::SetOverlayPriorityHint(OverlayPriority hint) { |
| TRACE_EVENT0("exo", "Surface::SetOverlayPriorityHint"); |
| pending_state_.overlay_priority_hint = hint; |
| } |
| |
| void Surface::SetClipRect(const std::optional<gfx::RectF>& clip_rect) { |
| TRACE_EVENT1("exo", "Surface::SetClipRect", "clip_rect", |
| (clip_rect ? clip_rect->ToString() : "nullopt")); |
| |
| if (pending_state_.clip_rect == clip_rect) { |
| return; |
| } |
| has_pending_contents_ = true; |
| pending_state_.clip_rect = clip_rect; |
| } |
| |
| void Surface::SetFrameTraceId(int64_t frame_trace_id) { |
| pending_state_.frame_trace_id = frame_trace_id; |
| } |
| |
| void Surface::SetSurfaceTransform(const gfx::Transform& transform) { |
| TRACE_EVENT1("exo", "Surface::SetSurfaceTransform", "transform", |
| transform.ToString()); |
| if (pending_state_.surface_transform != transform) { |
| has_pending_contents_ = true; |
| pending_state_.surface_transform = transform; |
| } |
| } |
| |
| void Surface::SetBackgroundColor(std::optional<SkColor4f> background_color) { |
| TRACE_EVENT0("exo", "Surface::SetBackgroundColor"); |
| pending_state_.basic_state.background_color = background_color; |
| } |
| |
| void Surface::SetViewport(const gfx::SizeF& viewport) { |
| TRACE_EVENT1("exo", "Surface::SetViewport", "viewport", viewport.ToString()); |
| |
| pending_state_.basic_state.viewport = viewport; |
| } |
| |
| void Surface::SetCrop(const gfx::RectF& crop) { |
| TRACE_EVENT1("exo", "Surface::SetCrop", "crop", crop.ToString()); |
| |
| pending_state_.basic_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_.basic_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_.basic_state.blend_mode = blend_mode; |
| } |
| |
| void Surface::SetAlpha(float alpha) { |
| TRACE_EVENT1("exo", "Surface::SetAlpha", "alpha", alpha); |
| |
| pending_state_.basic_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::SetServerStartResize() { |
| if (delegate_) |
| delegate_->OnSetServerStartResize(); |
| SetFrame(SurfaceFrameType::SHADOW); |
| } |
| |
| 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::SetUseImmersiveForFullscreen(bool value) { |
| TRACE_EVENT1("exo", "Surface::SetUseImmersiveForFullscreen", "value", value); |
| |
| if (delegate_) |
| delegate_->SetUseImmersiveForFullscreen(value); |
| } |
| |
| void Surface::ShowSnapPreviewToSecondary() { |
| if (delegate_) |
| delegate_->ShowSnapPreviewToSecondary(); |
| } |
| |
| void Surface::ShowSnapPreviewToPrimary() { |
| if (delegate_) |
| delegate_->ShowSnapPreviewToPrimary(); |
| } |
| |
| void Surface::HideSnapPreview() { |
| if (delegate_) |
| delegate_->HideSnapPreview(); |
| } |
| |
| void Surface::SetSnapPrimary(float snap_ratio) { |
| if (delegate_) |
| delegate_->SetSnapPrimary(snap_ratio); |
| } |
| |
| void Surface::SetSnapSecondary(float snap_ratio) { |
| if (delegate_) |
| delegate_->SetSnapSecondary(snap_ratio); |
| } |
| |
| void Surface::UnsetSnap() { |
| if (delegate_) |
| delegate_->UnsetSnap(); |
| } |
| |
| void Surface::SetCanGoBack() { |
| if (delegate_) |
| delegate_->SetCanGoBack(); |
| } |
| |
| void Surface::UnsetCanGoBack() { |
| if (delegate_) |
| delegate_->UnsetCanGoBack(); |
| } |
| |
| void Surface::SetColorSpace(gfx::ColorSpace color_space) { |
| TRACE_EVENT1("exo", "Surface::SetColorSpace", "color_space", |
| color_space.ToString()); |
| |
| pending_state_.basic_state.color_space = color_space; |
| } |
| |
| 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::RequestActivation() { |
| TRACE_EVENT0("exo", "Surface::RequestActivation"); |
| |
| if (delegate_) |
| delegate_->OnActivationRequested(); |
| } |
| |
| void Surface::SetClientSurfaceId(const char* client_surface_id) { |
| if (client_surface_id && strlen(client_surface_id) > 0) |
| window_->SetProperty(kClientSurfaceIdKey, |
| new std::string(client_surface_id)); |
| else |
| window_->ClearProperty(kClientSurfaceIdKey); |
| } |
| |
| std::string Surface::GetClientSurfaceId() const { |
| std::string* value = window_->GetProperty(kClientSurfaceIdKey); |
| return value ? *value : std::string(); |
| } |
| |
| void Surface::SetContainsVideo(bool contains_video) { |
| TRACE_EVENT1("exo", "Surface::SetContainsVideo", "contains_video", |
| contains_video ? "true" : "false"); |
| pending_state_.basic_state.contains_video = contains_video; |
| } |
| |
| bool Surface::ContainsVideo() { |
| if (state_.basic_state.contains_video) |
| return true; |
| |
| for (auto& subsurface : sub_surfaces_) { |
| if (subsurface.first->ContainsVideo()) |
| return true; |
| } |
| for (auto& render_layer : render_layers_) { |
| if (render_layer.first->ContainsVideo()) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| void Surface::SetWindowSessionId(int32_t window_session_id) { |
| if (window_session_id > 0) |
| window_->SetProperty(kWindowSessionId, window_session_id); |
| else |
| window_->ClearProperty(kWindowSessionId); |
| } |
| |
| int32_t Surface::GetWindowSessionId() { |
| return window_->GetProperty(kWindowSessionId); |
| } |
| |
| void Surface::SetPip() { |
| if (delegate_) |
| delegate_->SetPip(); |
| } |
| |
| void Surface::UnsetPip() { |
| if (delegate_) |
| delegate_->UnsetPip(); |
| } |
| |
| void Surface::SetAspectRatio(const gfx::SizeF& aspect_ratio) { |
| if (delegate_) |
| delegate_->SetAspectRatio(aspect_ratio); |
| } |
| |
| void Surface::SetAcquireFence(std::unique_ptr<gfx::GpuFence> gpu_fence) { |
| TRACE_EVENT1("exo", "Surface::SetAcquireFence", "fence_fd", |
| gpu_fence ? gpu_fence->GetGpuFenceHandle().Peek() : -1); |
| |
| pending_state_.acquire_fence = std::move(gpu_fence); |
| } |
| |
| bool Surface::HasPendingAcquireFence() const { |
| return !!pending_state_.acquire_fence; |
| } |
| |
| bool Surface::HasAcquireFence() const { |
| return !!state_.acquire_fence; |
| } |
| |
| void Surface::SetPerCommitBufferReleaseCallback( |
| Buffer::PerCommitExplicitReleaseCallback callback) { |
| TRACE_EVENT0("exo", "Surface::SetPerCommitBufferReleaseCallback"); |
| |
| pending_state_.per_commit_explicit_release_callback_ = std::move(callback); |
| } |
| |
| bool Surface::HasPendingPerCommitBufferReleaseCallback() const { |
| return !!pending_state_.per_commit_explicit_release_callback_; |
| } |
| |
| void Surface::Commit() { |
| TRACE_EVENT1( |
| "exo", "Surface::Commit", "buffer_id", |
| static_cast<const void*>( |
| pending_state_.buffer.has_value() && pending_state_.buffer->buffer() |
| ? pending_state_.buffer->buffer()->GetBufferId() |
| : nullptr)); |
| |
| for (auto& observer : observers_) |
| observer.OnCommit(this); |
| |
| needs_commit_surface_ = true; |
| |
| // Transfer pending state to cached state. |
| cached_state_.basic_state = pending_state_.basic_state; |
| pending_state_.basic_state.only_visible_on_secure_output = false; |
| has_cached_contents_ |= has_pending_contents_; |
| has_pending_contents_ = false; |
| if (pending_state_.buffer.has_value()) { |
| cached_state_.buffer = std::move(pending_state_.buffer); |
| pending_state_.buffer.reset(); |
| } |
| cached_state_.rounded_corners_bounds = pending_state_.rounded_corners_bounds; |
| cached_state_.overlay_priority_hint = pending_state_.overlay_priority_hint; |
| cached_state_.clip_rect = pending_state_.clip_rect; |
| cached_state_.surface_transform = pending_state_.surface_transform; |
| cached_state_.acquire_fence = std::move(pending_state_.acquire_fence); |
| cached_state_.per_commit_explicit_release_callback_ = |
| std::move(pending_state_.per_commit_explicit_release_callback_); |
| cached_state_.frame_callbacks.splice(cached_state_.frame_callbacks.end(), |
| pending_state_.frame_callbacks); |
| cached_state_.damage.Union(pending_state_.damage); |
| pending_state_.damage.Clear(); |
| |
| // Existing presentation callbacks in the cached state when a new pending |
| // state is merged in should end up delivered as "discarded". |
| for (const auto& presentation_callback : cached_state_.presentation_callbacks) |
| presentation_callback.Run(gfx::PresentationFeedback()); |
| cached_state_.presentation_callbacks.clear(); |
| cached_state_.presentation_callbacks.splice( |
| cached_state_.presentation_callbacks.end(), |
| pending_state_.presentation_callbacks); |
| |
| cached_state_.frame_trace_id = pending_state_.frame_trace_id; |
| pending_state_.frame_trace_id = -1; |
| |
| if (delegate_) |
| delegate_->OnSurfaceCommit(); |
| else |
| CommitSurfaceHierarchy(false); |
| } |
| |
| bool Surface::UpdateDisplay(int64_t old_display, int64_t new_display) { |
| display_id_ = new_display; |
| if (has_contents() && !leave_enter_callback_.is_null()) { |
| if (!leave_enter_callback_.Run(old_display, new_display)) { |
| return false; |
| } |
| } |
| for (const auto& sub_surface_entry : base::Reversed(sub_surfaces_)) { |
| auto* sub_surface = sub_surface_entry.first; |
| if (!sub_surface->UpdateDisplay(old_display, new_display)) |
| return false; |
| } |
| |
| for (auto& observer : observers_) { |
| observer.OnDisplayChanged(this, old_display, new_display); |
| } |
| |
| return true; |
| } |
| |
| display::Display Surface::GetDisplay() const { |
| return display::Screen::GetScreen()->GetDisplayNearestWindow(window()); |
| } |
| |
| 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_; |
| if (!is_augmented()) { |
| needs_full_damage |= |
| cached_state_.basic_state.opaque_region != |
| state_.basic_state.opaque_region || |
| cached_state_.basic_state.buffer_scale != |
| state_.basic_state.buffer_scale || |
| cached_state_.basic_state.buffer_transform != |
| state_.basic_state.buffer_transform || |
| cached_state_.basic_state.viewport != state_.basic_state.viewport || |
| cached_state_.rounded_corners_bounds != |
| state_.rounded_corners_bounds || |
| cached_state_.basic_state.crop != state_.basic_state.crop || |
| cached_state_.basic_state.only_visible_on_secure_output != |
| state_.basic_state.only_visible_on_secure_output || |
| cached_state_.basic_state.blend_mode != |
| state_.basic_state.blend_mode || |
| cached_state_.basic_state.alpha != state_.basic_state.alpha || |
| cached_state_.basic_state.color_space != |
| state_.basic_state.color_space || |
| cached_state_.basic_state.is_tracking_occlusion != |
| state_.basic_state.is_tracking_occlusion; |
| } |
| |
| bool needs_update_buffer_transform = |
| cached_state_.basic_state.buffer_scale != |
| state_.basic_state.buffer_scale || |
| cached_state_.basic_state.buffer_transform != |
| state_.basic_state.buffer_transform; |
| |
| bool needs_output_protection = |
| cached_state_.basic_state.only_visible_on_secure_output != |
| state_.basic_state.only_visible_on_secure_output; |
| |
| bool cached_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_.basic_state.alpha && cached_state_.basic_state.alpha) |
| needs_update_resource_ = true; |
| |
| state_.basic_state = cached_state_.basic_state; |
| cached_state_.basic_state.only_visible_on_secure_output = false; |
| |
| if (!is_augmented()) { |
| window_->SetEventTargetingPolicy( |
| (state_.basic_state.input_region.has_value() && |
| state_.basic_state.input_region->IsEmpty()) |
| ? aura::EventTargetingPolicy::kDescendantsOnly |
| : aura::EventTargetingPolicy::kTargetAndDescendants); |
| |
| if (state_.basic_state.is_tracking_occlusion) { |
| // 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. |
| window_->TrackOcclusionState(); |
| } |
| } |
| |
| if (needs_output_protection) { |
| if (!output_protection_) { |
| output_protection_ = |
| std::make_unique<ash::OutputProtectionDelegate>(window_.get()); |
| } |
| |
| uint32_t protection_mask = |
| state_.basic_state.only_visible_on_secure_output |
| ? display::CONTENT_PROTECTION_METHOD_HDCP |
| : display::CONTENT_PROTECTION_METHOD_NONE; |
| |
| output_protection_->SetProtection(protection_mask, base::DoNothing()); |
| } |
| |
| // We update contents if Attach() has been called since last commit. |
| if (has_cached_contents_) { |
| has_cached_contents_ = false; |
| |
| bool current_invert_y = state_.buffer.has_value() && |
| state_.buffer->buffer() && |
| state_.buffer->buffer()->y_invert(); |
| cached_invert_y = cached_state_.buffer.has_value() && |
| cached_state_.buffer->buffer() && |
| cached_state_.buffer->buffer()->y_invert(); |
| if (current_invert_y != cached_invert_y) |
| needs_update_buffer_transform = true; |
| |
| if (cached_state_.buffer.has_value()) { |
| bool had_contents = has_contents(); |
| |
| state_.buffer = std::move(cached_state_.buffer); |
| cached_state_.buffer.reset(); |
| |
| if (display_id_ != display::kInvalidDisplayId && |
| !leave_enter_callback_.is_null()) { |
| if (!had_contents && has_contents()) { |
| leave_enter_callback_.Run(display::kInvalidDisplayId, display_id_); |
| } else if (had_contents && !has_contents()) { |
| leave_enter_callback_.Run(display_id_, display::kInvalidDisplayId); |
| } |
| } |
| } |
| state_.rounded_corners_bounds = cached_state_.rounded_corners_bounds; |
| state_.clip_rect = cached_state_.clip_rect; |
| state_.surface_transform = cached_state_.surface_transform; |
| state_.acquire_fence = std::move(cached_state_.acquire_fence); |
| state_.per_commit_explicit_release_callback_ = |
| std::move(cached_state_.per_commit_explicit_release_callback_); |
| if (state_.basic_state.alpha) |
| needs_update_resource_ = true; |
| } |
| |
| // The overlay priority hint can get set before any buffer gets |
| // allocated/attached and may influence the format/modifier selection for |
| // these. |
| UpdateOverlayPriorityHint(cached_state_.overlay_priority_hint); |
| |
| // Either we didn't have a pending acquire fence, or we had one along with |
| // a new buffer, and it was already moved to state_.acquire_fence. Note that |
| // it is a commit-time client error to commit a fence without a buffer. |
| DCHECK(!cached_state_.acquire_fence); |
| // Similarly for the per commit buffer release callback. |
| DCHECK(!cached_state_.per_commit_explicit_release_callback_); |
| |
| if (needs_update_buffer_transform) |
| UpdateBufferTransform(cached_invert_y); |
| |
| // Move pending frame callbacks to the end of |state_.frame_callbacks|. |
| state_.frame_callbacks.splice(state_.frame_callbacks.end(), |
| cached_state_.frame_callbacks); |
| |
| // Move pending presentation callbacks to the end of |
| // |state_.presentation_callbacks|. |
| state_.presentation_callbacks.splice(state_.presentation_callbacks.end(), |
| cached_state_.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. |
| // Changes to sub_surface stack is immediately applied to pending, which |
| // will be copied to active directly when parent surface is committed, |
| // skipping the cached state. |
| 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(gfx::ToFlooredPoint(sub_surface_entry.second), |
| sub_surface->window()->bounds().size())); |
| } |
| sub_surfaces_changed_ = false; |
| } |
| |
| gfx::Rect output_rect(gfx::ToCeiledSize(content_size_)); |
| if (needs_full_damage) { |
| state_.damage = output_rect; |
| } else { |
| // cached_state_.damage is in Surface coordinates. |
| state_.damage.Swap(&cached_state_.damage); |
| state_.damage.Intersect(output_rect); |
| } |
| cached_state_.damage.Clear(); |
| |
| state_.frame_trace_id = cached_state_.frame_trace_id; |
| cached_state_.frame_trace_id = -1; |
| } |
| |
| surface_hierarchy_content_bounds_ = |
| gfx::Rect(gfx::ToCeiledSize(content_size_)); |
| |
| for (const auto& render_layer_entry : base::Reversed(render_layers_)) { |
| auto* render_layer = render_layer_entry.first; |
| render_layer->CommitSurfaceHierarchy(synchronized); |
| } |
| |
| if (!is_augmented()) { |
| if (state_.basic_state.input_region) { |
| hit_test_region_ = *state_.basic_state.input_region; |
| hit_test_region_.Intersect(surface_hierarchy_content_bounds_); |
| } else { |
| hit_test_region_ = surface_hierarchy_content_bounds_; |
| } |
| |
| int outset = state_.basic_state.input_outset; |
| if (outset > 0) { |
| gfx::Rect input_rect = surface_hierarchy_content_bounds_; |
| input_rect.Inset(-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 = |
| gfx::ToRoundedPoint(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(), state_.frame_callbacks); |
| // Move presentation callbacks to the end of |presentation_callbacks|. |
| presentation_callbacks->splice(presentation_callbacks->end(), |
| state_.presentation_callbacks); |
| |
| for (const auto& render_layer_entry : base::Reversed(render_layers_)) { |
| auto* render_layer = render_layer_entry.first; |
| render_layer->AppendSurfaceHierarchyCallbacks(frame_callbacks, |
| 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::PointF& parent_to_root_px, |
| const gfx::PointF& to_parent_dp, |
| bool needs_full_damage, |
| FrameSinkResourceManager* resource_manager, |
| std::optional<float> device_scale_factor, |
| 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; |
| // Synchronously commit all pending state of the sub-surface and its |
| // descendents. |
| gfx::PointF to_root_px = |
| parent_to_root_px + |
| gfx::ScalePoint(to_parent_dp, device_scale_factor.value_or(1.f)) |
| .OffsetFromOrigin(); |
| sub_surface->AppendSurfaceHierarchyContentsToFrame( |
| to_root_px, sub_surface_entry.second, needs_full_damage, |
| resource_manager, device_scale_factor, frame); |
| } |
| |
| // Make sure the sub_surfaces are rendered before render_layers for this |
| // surface s.t. sub_surfaces are rendered above the render_layers. |
| for (const auto& render_layer_entry : base::Reversed(render_layers_)) { |
| auto* render_layer = render_layer_entry.first; |
| // Synchronously commit all pending state of the layer and its descendents. |
| gfx::PointF to_root_px = |
| parent_to_root_px + |
| gfx::ScalePoint(to_parent_dp, device_scale_factor.value_or(1.f)) |
| .OffsetFromOrigin(); |
| render_layer->AppendSurfaceHierarchyContentsToFrame( |
| to_root_px, render_layer_entry.second, needs_full_damage, |
| resource_manager, device_scale_factor, frame); |
| } |
| |
| // Update the resource, or if not required, ensure we call the buffer release |
| // callback, since the buffer will not be used for this commit. |
| if (needs_update_resource_) { |
| UpdateResource(resource_manager); |
| } else { |
| ImmediateExplicitRelease( |
| std::move(state_.per_commit_explicit_release_callback_)); |
| } |
| |
| AppendContentsToFrame(parent_to_root_px, to_parent_dp, needs_full_damage, |
| device_scale_factor, frame); |
| } |
| |
| bool Surface::IsSynchronized() const { |
| return (delegate_ && delegate_->IsSurfaceSynchronized()) || is_augmented(); |
| } |
| |
| bool Surface::IsInputEnabled(Surface* surface) const { |
| return !is_augmented() && (!delegate_ || delegate_->IsInputEnabled(surface)); |
| } |
| |
| bool Surface::HasHitTestRegion() const { |
| return !is_augmented() && !hit_test_region_.IsEmpty(); |
| } |
| |
| bool Surface::HitTest(const gfx::Point& point) const { |
| return !is_augmented() && 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_; |
| } |
| |
| SurfaceDelegate* Surface::GetDelegateForTesting() { |
| 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& render_layer : render_layers_) { |
| render_layer.first->SurfaceHierarchyResourcesLost(); |
| } |
| for (const auto& sub_surface : sub_surfaces_) |
| sub_surface.first->SurfaceHierarchyResourcesLost(); |
| } |
| |
| bool Surface::FillsBoundsOpaquely() const { |
| return !current_resource_has_alpha_ || |
| state_.basic_state.blend_mode == SkBlendMode::kSrc || |
| state_.basic_state.opaque_region.Contains( |
| gfx::ToEnclosingRect(gfx::RectF(content_size_))); |
| } |
| |
| void Surface::SetOcclusionTracking(bool tracking) { |
| pending_state_.basic_state.is_tracking_occlusion = tracking; |
| } |
| |
| bool Surface::IsTrackingOcclusion() { |
| return state_.basic_state.is_tracking_occlusion; |
| } |
| |
| 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() = default; |
| |
| Surface::BufferAttachment::~BufferAttachment() { |
| if (buffer_) |
| buffer_->OnDetach(); |
| } |
| |
| Surface::BufferAttachment::BufferAttachment(BufferAttachment&& other) { |
| *this = std::move(other); |
| } |
| |
| 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; |
| } |
| |
| Surface::ExtendedState::ExtendedState() = default; |
| |
| Surface::ExtendedState::~ExtendedState() = default; |
| |
| void Surface::UpdateResource(FrameSinkResourceManager* resource_manager) { |
| DCHECK(needs_update_resource_); |
| needs_update_resource_ = false; |
| if (state_.buffer.has_value() && state_.buffer->buffer()) { |
| gfx::ColorSpace buffer_color_space = state_.basic_state.color_space; |
| // Invalid color spaces cause issues went sent to the buffer. In these cases |
| // revert to passing SRGB as before. |
| if (!buffer_color_space.IsValid()) { |
| buffer_color_space = gfx::ColorSpace::CreateSRGB(); |
| } |
| if (legacy_buffer_release_skippable_ && |
| state_.per_commit_explicit_release_callback_) { |
| state_.buffer->buffer()->SkipLegacyRelease(); |
| } |
| if (state_.buffer->buffer()->ProduceTransferableResource( |
| resource_manager, std::move(state_.acquire_fence), |
| state_.basic_state.only_visible_on_secure_output, |
| ¤t_resource_, buffer_color_space, |
| window_->GetToplevelWindow()->GetProperty( |
| kProtectedNativePixmapQueryDelegate), |
| std::move(state_.per_commit_explicit_release_callback_))) { |
| current_resource_has_alpha_ = |
| FormatHasAlpha(state_.buffer->buffer()->GetFormat()); |
| current_resource_.color_space = state_.basic_state.color_space; |
| } else { |
| current_resource_.id = viz::kInvalidResourceId; |
| // Use the buffer's size, so the AppendContentsToFrame() will append |
| // a SolidColorDrawQuad with the buffer's size. |
| current_resource_.size = state_.buffer->size(); |
| SkColor4f color = state_.buffer->buffer()->GetColor(); |
| current_resource_has_alpha_ = !color.isOpaque(); |
| } |
| } else { |
| current_resource_.id = viz::kInvalidResourceId; |
| current_resource_.size = gfx::Size(); |
| current_resource_has_alpha_ = false; |
| ImmediateExplicitRelease( |
| std::move(state_.per_commit_explicit_release_callback_)); |
| } |
| } |
| |
| void Surface::UpdateBufferTransform(bool y_invert) { |
| buffer_transform_ = |
| ToBufferTransformMatrix(state_.basic_state.buffer_transform, y_invert); |
| if (state_.basic_state.buffer_scale != 0) { |
| buffer_transform_.PostScale(1.0f / state_.basic_state.buffer_scale, |
| 1.0f / state_.basic_state.buffer_scale); |
| } |
| } |
| |
| void Surface::UpdateOverlayPriorityHint(OverlayPriority overlay_priority_hint) { |
| if (state_.overlay_priority_hint == overlay_priority_hint) { |
| return; |
| } |
| |
| state_.overlay_priority_hint = overlay_priority_hint; |
| for (SurfaceObserver& observer : observers_) { |
| observer.OnOverlayPriorityHintChanged(overlay_priority_hint); |
| } |
| } |
| |
| // Some clients (ARC) submit overlapping surfaces that are almost always |
| // occluded. However, due how viz does quad overdrawn quad occlusion with |
| // rounded corners, this does not always remove all occluded quads. To avoid |
| // overdraw and subtle fast rounded corner compositing bugs we remove fully |
| // occluded surfaces here before they even become quads to submit to the |
| // compositor. See b/307557914 |
| // TODO( b/325307643 ) : Provide a generalized solution here for the compositor. |
| static bool IsOccludedByPreviousSqs( |
| const std::unique_ptr<viz::CompositorRenderPass>& render_pass, |
| const gfx::Transform& quad_to_target_transform, |
| const gfx::Rect& quad_rect, |
| const gfx::MaskFilterInfo& msk) { |
| viz::SharedQuadState* prev_sqs = |
| !render_pass->shared_quad_state_list.empty() |
| ? render_pass->shared_quad_state_list.back() |
| : nullptr; |
| // Limit the cases here to pixel aligned occlusions so all tests are known to |
| // be in the same space. |
| if (prev_sqs && quad_to_target_transform.IsIdentity() && |
| prev_sqs->quad_to_target_transform.IsIdentity() && |
| prev_sqs->are_contents_opaque && prev_sqs->opacity == 1.f) { |
| if (prev_sqs->clip_rect && !prev_sqs->clip_rect->Contains(quad_rect)) { |
| return false; |
| } |
| if (prev_sqs->quad_layer_rect.Contains(quad_rect)) { |
| if (msk == prev_sqs->mask_filter_info) { |
| return true; |
| } |
| } |
| } |
| return false; |
| } |
| |
| // Try to share the |SharedQuadState| (sqs) when a single layer can be |
| // reconstructed. This is important for performance reasons in the occlusion |
| // code and correctness in the per edge anti-alias code. |
| static viz::SharedQuadState* AppendOrCreateSharedQuadState( |
| viz::DrawQuad::Material quad_type, |
| float opacity, |
| const std::unique_ptr<viz::CompositorRenderPass>& render_pass, |
| const gfx::Transform& quad_to_target_transform, |
| const gfx::Rect& quad_rect, |
| const gfx::MaskFilterInfo& msk, |
| const std::optional<gfx::Rect>& quad_clip_rect, |
| const bool are_contents_opaque) { |
| viz::SharedQuadState* quad_state = |
| !render_pass->shared_quad_state_list.empty() |
| ? render_pass->shared_quad_state_list.back() |
| : nullptr; |
| auto test_union = quad_rect; |
| bool is_sealed_union = false; |
| if (quad_state) { |
| // A sealed union is when the combined rect has no gaps and can form a |
| // single layer rect. |
| test_union.Union(quad_state->quad_layer_rect); |
| if ((test_union.width() == quad_rect.width() && |
| test_union.width() == quad_state->quad_layer_rect.width())) { |
| if (quad_rect.height() + quad_state->quad_layer_rect.height() >= |
| test_union.height()) |
| is_sealed_union = true; |
| } |
| |
| if ((test_union.height() == quad_rect.height() && |
| test_union.height() == quad_state->quad_layer_rect.height())) { |
| if (quad_rect.width() + quad_state->quad_layer_rect.width() >= |
| test_union.width()) |
| is_sealed_union = true; |
| } |
| } |
| |
| bool prev_texture_draw_quad = false; |
| if (!render_pass->quad_list.empty()) { |
| prev_texture_draw_quad = render_pass->quad_list.back()->material == |
| viz::DrawQuad::Material::kTextureContent; |
| } |
| |
| if (quad_type != viz::DrawQuad::Material::kTextureContent && |
| !prev_texture_draw_quad && quad_state && is_sealed_union && |
| quad_to_target_transform == quad_state->quad_to_target_transform && |
| opacity == quad_state->opacity && |
| quad_clip_rect == quad_state->clip_rect && |
| are_contents_opaque == quad_state->are_contents_opaque && |
| msk == quad_state->mask_filter_info) { |
| // Expland the layer portion of the sqs. |
| quad_state->quad_layer_rect = test_union; |
| quad_state->visible_quad_layer_rect = test_union; |
| } else { |
| quad_state = render_pass->CreateAndAppendSharedQuadState(); |
| quad_state->SetAll(quad_to_target_transform, quad_rect, quad_rect, msk, |
| quad_clip_rect, are_contents_opaque, opacity, |
| SkBlendMode::kSrcOver, /*sorting_context=*/0, |
| /*layer_id=*/0u, /*fast_rounded_corner=*/false); |
| } |
| return quad_state; |
| } |
| |
| void Surface::AppendContentsToFrame(const gfx::PointF& parent_to_root_px, |
| const gfx::PointF& to_parent_dp, |
| bool needs_full_damage, |
| std::optional<float> device_scale_factor, |
| viz::CompositorFrame* frame) { |
| const std::unique_ptr<viz::CompositorRenderPass>& render_pass = |
| frame->render_pass_list.back(); |
| gfx::PointF parent_to_root_dp = gfx::ScalePoint( |
| parent_to_root_px, 1.f / device_scale_factor.value_or(1.f)); |
| gfx::PointF to_root_dp = parent_to_root_dp + to_parent_dp.OffsetFromOrigin(); |
| gfx::RectF output_rect(to_root_dp, content_size_); |
| gfx::Rect quad_rect(0, 0, 1, 1); |
| |
| // Surface bounds are in DIPs, but |damage_rect| should be specified in |
| // pixels, so we need to scale by the |device_scale_factor|. |
| gfx::RectF damage_rect_px; |
| gfx::RectF damage_rect_dp = needs_full_damage |
| ? gfx::RectF(content_size_) |
| : gfx::RectF(state_.damage.bounds()); |
| if (!damage_rect_dp.IsEmpty() || extended_damage_dp_.has_value()) { |
| // 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. |
| if (!damage_rect_dp.IsEmpty()) { |
| damage_rect_dp.Inset(-1); |
| damage_rect_dp += to_root_dp.OffsetFromOrigin(); |
| damage_rect_dp.Intersect(output_rect); |
| } |
| if (extended_damage_dp_.has_value()) { |
| damage_rect_dp.Union(extended_damage_dp_.value() + |
| to_root_dp.OffsetFromOrigin()); |
| extended_damage_dp_.reset(); |
| } |
| |
| damage_rect_px = damage_rect_dp; |
| if (device_scale_factor.has_value()) { |
| if (device_scale_factor.value() <= 1) { |
| damage_rect_px = gfx::ConvertRectToPixels(damage_rect_px, |
| device_scale_factor.value()); |
| } else { |
| // The damage will eventually be rescaled by 1/device_scale_factor. |
| // Since that scale factor is <1, taking the enclosed rect here means |
| // that that rescaled RectF is <1px smaller than |damage_rect| in each |
| // dimension, which makes the enclosing rect equal to |damage_rect|. |
| damage_rect_px.Scale(device_scale_factor.value()); |
| } |
| } |
| } |
| |
| std::optional<gfx::Rect> quad_clip_rect; |
| if (state_.clip_rect) { |
| auto clip_rect_offset = |
| gfx::ScalePoint(to_root_dp, device_scale_factor.value_or(1.f)) |
| .OffsetFromOrigin(); |
| |
| quad_clip_rect = gfx::ToEnclosedRect(*state_.clip_rect + clip_rect_offset); |
| } |
| |
| state_.damage.Clear(); |
| |
| gfx::Vector2dF scale(content_size_.width(), content_size_.height()); |
| |
| gfx::Vector2dF translate(0.0f, 0.0f); |
| |
| scale.Scale(state_.basic_state.buffer_scale); |
| |
| bool are_contents_opaque = |
| !current_resource_has_alpha_ || |
| state_.basic_state.blend_mode == SkBlendMode::kSrc || |
| state_.basic_state.opaque_region.Contains( |
| gfx::ToEnclosedRect(output_rect)); |
| |
| gfx::MaskFilterInfo msk; |
| if (!state_.rounded_corners_bounds.IsEmpty()) { |
| // Set the mask. |
| msk = gfx::MaskFilterInfo(state_.rounded_corners_bounds + |
| to_root_dp.OffsetFromOrigin()); |
| |
| if (device_scale_factor.has_value()) { |
| msk.ApplyTransform( |
| gfx::Transform::MakeScale(device_scale_factor.value())); |
| } |
| } |
| |
| // Compute the total transformation from post-transform buffer coordinates to |
| // target coordinates. |
| // Scale to size then translate to position of subsurface in parent's space. |
| gfx::Transform viewport_to_target_transform( |
| gfx::AxisTransform2d::FromScaleAndTranslation( |
| scale, to_parent_dp.OffsetFromOrigin() + translate)); |
| // Apply delegated transform matrix |
| viewport_to_target_transform.PostConcat(state_.surface_transform); |
| if (device_scale_factor.has_value()) { |
| // Convert from DPs to pixels. |
| viewport_to_target_transform.PostScale(device_scale_factor.value()); |
| } |
| // Translate to root in pixels. |
| viewport_to_target_transform.PostTranslate( |
| parent_to_root_px.OffsetFromOrigin()); |
| |
| gfx::Transform quad_to_target_transform(buffer_transform_); |
| quad_to_target_transform.PostConcat(viewport_to_target_transform); |
| |
| // The overdraw algorithm in 'Display::RemoveOverdrawQuads' operates in |
| // content space and, due to the discretized nature of the |gfx::Rect|, cannot |
| // work with 0,0 1x1 quads. This also means that quads that do not fall on |
| // pixel boundaries (rotated or subpixel rects) cannot be removed by the |
| // algorithm. |
| if (quad_to_target_transform.Preserves2dAxisAlignment()) { |
| gfx::RectF target_space_rect = |
| quad_to_target_transform.MapRect(gfx::RectF(quad_rect)); |
| // This simple rect representation cannot mathematically express a rotation |
| // (and currently does not express flip/mirror) hence the |
| // 'IsPositiveScaleOrTranslation' check. |
| if (gfx::IsNearestRectWithinDistance(target_space_rect, 0.001f) && |
| quad_to_target_transform.IsPositiveScaleOrTranslation()) { |
| quad_rect = gfx::ToNearestRect(target_space_rect); |
| // Later in 'SurfaceAggregator' this transform will have 2d translation. |
| quad_to_target_transform = gfx::Transform(); |
| } |
| } |
| |
| if (IsExoOcclusionEnabled() && |
| IsOccludedByPreviousSqs(render_pass, quad_to_target_transform, quad_rect, |
| msk)) { |
| render_pass->damage_rect.Union(gfx::ToEnclosedRect(damage_rect_px)); |
| if (current_resource_.id) { |
| frame->resource_list.push_back(current_resource_); |
| } |
| return; |
| } |
| |
| if (current_resource_.id) { |
| gfx::RectF uv_crop(gfx::SizeF(1, 1)); |
| if (!state_.basic_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_.basic_state.crop); |
| gfx::Size transformed_buffer_size(ToTransformedSize( |
| current_resource_.size, state_.basic_state.buffer_transform)); |
| if (!transformed_buffer_size.IsEmpty()) { |
| uv_crop.InvScale(transformed_buffer_size.width(), |
| transformed_buffer_size.height()); |
| } |
| uv_crop = buffer_transform_.InverseMapRect(uv_crop).value_or(uv_crop); |
| } |
| |
| SkColor4f background_color = SkColors::kTransparent; |
| if (state_.basic_state.background_color.has_value()) |
| background_color = state_.basic_state.background_color.value(); |
| else if (current_resource_has_alpha_ && are_contents_opaque) |
| background_color = SkColors::kBlack; // Avoid writing alpha < 1 |
| |
| if (state_.basic_state.alpha != 0.0f) { |
| const bool requires_texture_draw_quad = |
| state_.basic_state.only_visible_on_secure_output || |
| state_.overlay_priority_hint != OverlayPriority::LOW; |
| |
| const viz::SharedQuadState* quad_state = AppendOrCreateSharedQuadState( |
| requires_texture_draw_quad ? viz::DrawQuad::Material::kTextureContent |
| : viz::DrawQuad::Material::kTiledContent, |
| state_.basic_state.alpha, render_pass, quad_to_target_transform, |
| quad_rect, msk, quad_clip_rect, are_contents_opaque); |
| |
| // Our historical implementation of the wayland blending protocol is to |
| // treat blend none as fully opaque alpha and not simply "src". This allow |
| // us to treat client buffers as rgbx. For an example see b/305977429 |
| const bool force_rgbx_for_opaque = |
| are_contents_opaque && current_resource_has_alpha_; |
| // Draw quad is only needed if buffer is not fully transparent. |
| |
| if (requires_texture_draw_quad) { |
| viz::TextureDrawQuad* texture_quad = |
| render_pass->CreateAndAppendDrawQuad<viz::TextureDrawQuad>(); |
| texture_quad->SetNew(quad_state, quad_rect, quad_rect, |
| /* needs_blending=*/!are_contents_opaque, |
| current_resource_.id, |
| /* premultiplied*/ true, uv_crop.origin(), |
| uv_crop.bottom_right(), background_color, |
| /* flipped=*/false, /* nearest*/ false, |
| state_.basic_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); |
| |
| if (force_rgbx_for_opaque) { |
| texture_quad->set_force_rgbx(); |
| } |
| |
| switch (state_.overlay_priority_hint) { |
| case OverlayPriority::LOW: |
| texture_quad->overlay_priority_hint = viz::OverlayPriority::kLow; |
| break; |
| case OverlayPriority::REQUIRED: |
| texture_quad->overlay_priority_hint = |
| viz::OverlayPriority::kRequired; |
| break; |
| case OverlayPriority::REGULAR: |
| texture_quad->overlay_priority_hint = |
| viz::OverlayPriority::kRegular; |
| break; |
| } |
| |
| #if BUILDFLAG(USE_ARC_PROTECTED_MEDIA) |
| if (state_.basic_state.only_visible_on_secure_output && |
| state_.buffer.has_value() && state_.buffer->buffer() && |
| state_.buffer->buffer()->NeedsHardwareProtection()) { |
| texture_quad->protected_video_type = |
| gfx::ProtectedVideoType::kHardwareProtected; |
| } |
| #endif // BUILDFLAG(USE_ARC_PROTECTED_MEDIA) |
| |
| if (!damage_rect_px.IsEmpty()) { |
| texture_quad->damage_rect = gfx::ToEnclosedRect(damage_rect_px); |
| render_pass->has_per_quad_damage = true; |
| // Clear handled damage so it will not be added to the |render_pass|. |
| damage_rect_px = gfx::RectF(); |
| } |
| } else { |
| viz::TileDrawQuad* tile_quad = |
| render_pass->CreateAndAppendDrawQuad<viz::TileDrawQuad>(); |
| // TODO(crbug.com/40229946): Support AA quads coming from exo. |
| constexpr bool kForceAntiAliasingOff = true; |
| tile_quad->SetNew( |
| quad_state, quad_rect, quad_rect, |
| /* needs_blending=*/!are_contents_opaque, current_resource_.id, |
| gfx::ScaleRect(uv_crop, current_resource_.size.width(), |
| current_resource_.size.height()), |
| current_resource_.size, |
| /* is_premultiplied=*/true, |
| /* nearest_neighbor */ false, kForceAntiAliasingOff); |
| } |
| } |
| frame->resource_list.push_back(current_resource_); |
| } else { |
| const viz::SharedQuadState* quad_state = AppendOrCreateSharedQuadState( |
| viz::DrawQuad::Material::kSolidColor, state_.basic_state.alpha, |
| render_pass, quad_to_target_transform, quad_rect, msk, quad_clip_rect, |
| are_contents_opaque); |
| SkColor4f color = state_.buffer.has_value() && state_.buffer->buffer() |
| ? state_.buffer->buffer()->GetColor() |
| : SkColors::kBlack; |
| viz::SolidColorDrawQuad* solid_quad = |
| render_pass->CreateAndAppendDrawQuad<viz::SolidColorDrawQuad>(); |
| solid_quad->SetNew(quad_state, quad_rect, quad_rect, color, |
| false /* force_anti_aliasing_off */); |
| } |
| |
| render_pass->damage_rect.Union(gfx::ToEnclosedRect(damage_rect_px)); |
| } |
| |
| void Surface::UpdateContentSize() { |
| gfx::SizeF content_size; |
| // Enable/disable sub-surface based on if it has contents. |
| if (has_contents()) { |
| if (!state_.basic_state.viewport.IsEmpty()) { |
| content_size = state_.basic_state.viewport; |
| } else if (!state_.basic_state.crop.IsEmpty()) { |
| DLOG_IF(WARNING, !base::IsValueInRangeForNumericType<int>( |
| state_.basic_state.crop.width()) || |
| !base::IsValueInRangeForNumericType<int>( |
| state_.basic_state.crop.height())) |
| << "Crop rectangle size (" |
| << state_.basic_state.crop.size().ToString() |
| << ") most be expressible using integers when viewport is not set"; |
| content_size = state_.basic_state.crop.size(); |
| } else { |
| content_size = gfx::ScaleSize( |
| gfx::SizeF(ToTransformedSize( |
| state_.buffer.has_value() ? state_.buffer->size() : gfx::Size(), |
| state_.basic_state.buffer_transform)), |
| 1.0f / state_.basic_state.buffer_scale); |
| } |
| |
| // Check that a window has a parent before showing it. |
| // For example, aura::Window associated with augmented subsurfaces don't |
| // have parents, because they are not part of the tree. |
| if (!is_augmented() && window_->parent()) { |
| window_->Show(); |
| } |
| } else if (!is_augmented()) { |
| window_->Hide(); |
| } |
| |
| if (content_size_ != content_size) { |
| // Save this to later expand the damage area. |
| if (!is_augmented()) { |
| if (extended_damage_dp_.has_value()) { |
| extended_damage_dp_->Union(gfx::RectF(content_size_)); |
| } else { |
| extended_damage_dp_.emplace(gfx::RectF(content_size_)); |
| } |
| } |
| content_size_ = content_size; |
| // TODO(b/191414141) : Check is temporary to isolate damage issue. |
| if (!gfx::ToRoundedSize(content_size_).GetCheckedArea().IsValid()) { |
| DCHECK(false) << " content_size_=" << content_size_.ToString(); |
| constexpr int kMaxSizeScalar = 1 << 15; |
| // Forcibly restrict |content_size_| to 32kx32k. |
| content_size_.SetToMin(gfx::SizeF(kMaxSizeScalar, kMaxSizeScalar)); |
| } |
| if (!is_augmented()) { |
| window_->SetBounds(gfx::Rect(window_->bounds().origin(), |
| gfx::ToCeiledSize(content_size_))); |
| } |
| } |
| } |
| |
| void Surface::SetFrameLocked(bool lock) { |
| for (SurfaceObserver& observer : observers_) |
| observer.OnFrameLockingChanged(this, lock); |
| } |
| |
| void Surface::OnScaleFactorChanged(float old_scale_factor, |
| float new_scale_factor) { |
| for (SurfaceObserver& observer : observers_) { |
| observer.OnScaleFactorChanged(this, old_scale_factor, new_scale_factor); |
| } |
| } |
| |
| void Surface::OnWindowOcclusionChanged( |
| aura::Window::OcclusionState old_occlusion_state, |
| aura::Window::OcclusionState new_occlusion_state) { |
| if (!state_.basic_state.is_tracking_occlusion) |
| return; |
| |
| // The first occlusion calculation happens without a buffer yet attached to |
| // the surface so ignore this change. This avoids `OcclusionState::HIDDEN` |
| // being sent , which will be immediately followed by |
| // `OcclusionState::VISIBLE` anyway once buffer is attached. |
| if (old_occlusion_state == aura::Window::OcclusionState::UNKNOWN && |
| new_occlusion_state == aura::Window::OcclusionState::HIDDEN) { |
| return; |
| } |
| |
| for (SurfaceObserver& observer : observers_) |
| observer.OnWindowOcclusionChanged(this); |
| } |
| |
| void Surface::OnDeskChanged(int state) { |
| for (SurfaceObserver& observer : observers_) |
| observer.OnDeskChanged(this, state); |
| } |
| |
| void Surface::OnTooltipShown(const std::u16string& text, |
| const gfx::Rect& bounds) { |
| for (SurfaceObserver& observer : observers_) |
| observer.OnTooltipShown(this, text, bounds); |
| } |
| |
| void Surface::OnTooltipHidden() { |
| for (SurfaceObserver& observer : observers_) |
| observer.OnTooltipHidden(this); |
| } |
| |
| void Surface::MoveToDesk(int desk_index) { |
| if (delegate_) |
| delegate_->MoveToDesk(desk_index); |
| } |
| |
| void Surface::SetVisibleOnAllWorkspaces() { |
| if (delegate_) |
| delegate_->SetVisibleOnAllWorkspaces(); |
| } |
| |
| void Surface::SetInitialWorkspace(const char* initial_workspace) { |
| if (delegate_) |
| delegate_->SetInitialWorkspace(initial_workspace); |
| } |
| |
| void Surface::Pin(bool trusted) { |
| if (delegate_) |
| delegate_->Pin(trusted); |
| } |
| |
| void Surface::Unpin() { |
| if (delegate_) |
| delegate_->Unpin(); |
| } |
| |
| void Surface::ThrottleFrameRate(bool on) { |
| for (SurfaceObserver& observer : observers_) |
| observer.ThrottleFrameRate(on); |
| } |
| |
| void Surface::SetKeyboardShortcutsInhibited(bool inhibited) { |
| if (keyboard_shortcuts_inhibited_ == inhibited) |
| return; |
| |
| keyboard_shortcuts_inhibited_ = inhibited; |
| |
| // Also set kCanConsumeSystemKeysKey property, so that the key event |
| // is also forwarded to exo::Keyboard. |
| // TODO(hidehiko): Support capability on migrating ARC/Crostini. |
| window_->SetProperty(ash::kCanConsumeSystemKeysKey, inhibited); |
| } |
| |
| SecurityDelegate* Surface::GetSecurityDelegate() { |
| if (delegate_) |
| return delegate_->GetSecurityDelegate(); |
| return nullptr; |
| } |
| |
| void Surface::SetClientAccessibilityId(int id) { |
| if (!window_) { |
| return; |
| } |
| |
| if (id >= 0) { |
| exo::SetShellClientAccessibilityId(window_.get(), id); |
| } else { |
| exo::SetShellClientAccessibilityId(window_.get(), std::nullopt); |
| } |
| } |
| |
| void Surface::SetTopInset(int height) { |
| if (delegate_) { |
| delegate_->SetTopInset(height); |
| } |
| } |
| |
| void Surface::OnFullscreenStateChanged(bool fullscreen) { |
| for (SurfaceObserver& observer : observers_) { |
| observer.OnFullscreenStateChanged(fullscreen); |
| } |
| for (const auto& [surface, point] : sub_surfaces_) { |
| surface->OnFullscreenStateChanged(fullscreen); |
| } |
| } |
| |
| Buffer* Surface::GetBuffer() { |
| if (state_.buffer.has_value()) { |
| return state_.buffer->buffer().get(); |
| } |
| return nullptr; |
| } |
| |
| std::string Surface::DumpDebugInfo() const { |
| bool has_buffer = state_.buffer.has_value() && |
| state_.buffer->buffer().get() && |
| state_.buffer->buffer()->GetBufferId(); |
| |
| auto blend_mode_str = [](SkBlendMode mode) -> std::string { |
| switch (mode) { |
| case SkBlendMode::kSrc: |
| return " kSrc"; |
| case SkBlendMode::kSrcOver: |
| return " kSrcOver"; |
| default: |
| NOTREACHED(); |
| return " InvalidBlendMode"; |
| } |
| }; |
| |
| return "content-size=" + content_size_.ToString() + |
| (current_resource_has_alpha_ ? std::string(" has_alpha") : "") + |
| blend_mode_str(state_.basic_state.blend_mode) + |
| +" opaque-region=" + state_.basic_state.opaque_region.ToString() + |
| " " + |
| (has_buffer |
| ? (std::string("format=") + |
| gfx::BufferFormatToString( |
| state_.buffer->buffer()->GetFormat()) + |
| (FormatHasAlpha(state_.buffer->buffer()->GetFormat()) ? "(a)" |
| : "")) |
| : ""); |
| } |
| |
| } // namespace exo |