Commit d472ad7e authored by Varun Mohan's avatar Varun Mohan Committed by Commit Bot

Calculation of occlusion of native windows on Windows

This calculates the occlusion status of native windows on Windows. We plan to call this on a repeating timer to extract metrics about how often and for how long native windows are occluded.

Bug: 813093
Change-Id: I32c9b477e29657ca087e5617d30859236bd10636
Reviewed-on: https://chromium-review.googlesource.com/973186
Commit-Queue: Varun Mohan <varunmohan@google.com>
Reviewed-by: default avatarScott Violet <sky@chromium.org>
Reviewed-by: default avatarChris Hamilton <chrisha@chromium.org>
Reviewed-by: default avatarFrançois Doray <fdoray@chromium.org>
Cr-Commit-Position: refs/heads/master@{#552540}
parent b1fe5c4f
......@@ -360,6 +360,7 @@ executable("demo") {
test("aura_unittests") {
sources = [
"../compositor_extra/shadow_unittest.cc",
"//ui/aura_extra/window_occlusion_impl_unittest_win.cc",
"gestures/gesture_recognizer_unittest.cc",
"hit_test_data_provider_aura_unittest.cc",
"mus/drag_drop_controller_mus_unittest.cc",
......@@ -392,6 +393,7 @@ test("aura_unittests") {
"//services/ui/public/cpp",
"//skia",
"//testing/gtest",
"//ui/aura_extra",
"//ui/base:test_support",
"//ui/compositor:test_support",
"//ui/compositor_extra",
......
......@@ -9,6 +9,10 @@ component("aura_extra") {
"aura_extra_export.h",
"image_window_delegate.cc",
"image_window_delegate.h",
"window_occlusion_impl_win.cc",
"window_occlusion_impl_win.h",
"window_occlusion_win.cc",
"window_occlusion_win.h",
]
defines = [ "AURA_EXTRA_IMPLEMENTATION" ]
......
This diff is collapsed.
// Copyright 2018 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 "ui/aura_extra/window_occlusion_impl_win.h"
#include "base/win/scoped_gdi_object.h"
#include "ui/aura/window_tree_host.h"
#include "ui/gfx/geometry/rect.h"
namespace aura_extra {
namespace {
// Determines the occlusion status of each aura::Window in |windows_of_interest|
// passed to the constructor. Evaluates a window by subtracting its bounds from
// every window beneath it in the z-order.
class WindowEvaluatorImpl : public WindowEvaluator {
public:
// |windows_of_interest| are the aura::WindowTreeHost's whose occlusion
// status are being calculated. |bounds_delegate| is used to obtain the bounds
// in pixels of each root aura::Window in |windows_of_interest|.
WindowEvaluatorImpl(
const std::vector<aura::WindowTreeHost*>& windows_of_interest,
std::unique_ptr<WindowBoundsDelegate> bounds_delegate);
~WindowEvaluatorImpl();
// WindowEvaluator.
bool EvaluateWindow(bool is_relevant,
const gfx::Rect& window_rect_in_pixels,
HWND hwnd) override;
// Returns whether there was at least one visible root aura::Window passed to
// ComputeNativeWindowOcclusionStatus().
bool HasAtLeastOneVisibleWindow() const {
return !unoccluded_regions_.empty();
}
// Called once the occlusion computation is done. Returns |occlusion_states_|
base::flat_map<aura::WindowTreeHost*, aura::Window::OcclusionState>
TakeResult();
private:
using WindowRegionPair = std::pair<aura::WindowTreeHost*, SkRegion>;
// Stores intermediate values for the unoccluded regions of an aura::Window in
// pixels. Once an aura::Window::OcclusionState is determined for a root
// aura::Window, that aura::Window is removed from |unoccluded_regions_| and
// added to |occlusion_states_| with the computed
// aura::Window::OcclusionState.
std::vector<WindowRegionPair> unoccluded_regions_;
// Stores the final aura::Window::OcclusionState for each root
// aura::WindowTreeHost that is passed to
// ComputeNativeWindowOcclusionStatus().
base::flat_map<aura::WindowTreeHost*, aura::Window::OcclusionState>
occlusion_states_;
DISALLOW_COPY_AND_ASSIGN(WindowEvaluatorImpl);
};
WindowEvaluatorImpl::WindowEvaluatorImpl(
const std::vector<aura::WindowTreeHost*>& windows_of_interest,
std::unique_ptr<WindowBoundsDelegate> bounds_delegate) {
for (aura::WindowTreeHost* window : windows_of_interest) {
// If the window isn't visible at this time, it is
// aura::Window::OcclusionState::HIDDEN.
if (!window->window()->IsVisible() ||
IsIconic(window->GetAcceleratedWidget())) {
occlusion_states_[window] = aura::Window::OcclusionState::HIDDEN;
continue;
}
gfx::Rect window_rect_in_pixels =
bounds_delegate->GetBoundsInPixels(window);
SkRegion window_region(SkIRect::MakeXYWH(
window_rect_in_pixels.x(), window_rect_in_pixels.y(),
window_rect_in_pixels.width(), window_rect_in_pixels.height()));
unoccluded_regions_.emplace_back(window, window_region);
}
}
WindowEvaluatorImpl::~WindowEvaluatorImpl() = default;
bool WindowEvaluatorImpl::EvaluateWindow(bool is_relevant,
const gfx::Rect& window_rect_in_pixels,
HWND hwnd) {
// Loop through |unoccluded_regions_| and determine how |hwnd| affects each
// root window with respect to occlusion.
for (WindowRegionPair& root_window_pair : unoccluded_regions_) {
HWND window_hwnd = root_window_pair.first->GetAcceleratedWidget();
// The EnumWindows callbacks have reached this window in the Z-order
// (EnumWindows goes from front to back). This window must be visible
// because we did not discover that it was completely occluded earlier.
if (hwnd == window_hwnd) {
occlusion_states_[root_window_pair.first] =
aura::Window::OcclusionState::VISIBLE;
// Set the unoccluded region for this window to empty as a signal that the
// occlusion computation is complete.
root_window_pair.second.setEmpty();
continue;
}
// |hwnd| is not taken into account for occlusion computations, move on.
if (!is_relevant)
continue;
// Current occlusion state for this window cannot be determined yet. The
// EnumWindows callbacks are currently above this window in the Z-order.
// Subtract the other windows bounding rectangle from this window's
// unoccluded region if the two regions intersect.
SkRegion window_region(SkIRect::MakeXYWH(
window_rect_in_pixels.x(), window_rect_in_pixels.y(),
window_rect_in_pixels.width(), window_rect_in_pixels.height()));
if (root_window_pair.second.intersects(window_region)) {
root_window_pair.second.op(window_region, SkRegion::kDifference_Op);
if (root_window_pair.second.isEmpty()) {
occlusion_states_[root_window_pair.first] =
aura::Window::OcclusionState::OCCLUDED;
}
}
}
// Occlusion computation is done for windows with an empty region in
// |unoccluded_regions_|. If the window is visible, the region is set to empty
// explicitly. If it is occluded, the region is implicitly empty.
base::EraseIf(unoccluded_regions_, [](const WindowRegionPair& element) {
return element.second.isEmpty();
});
// If |unoccluded_regions_| is empty, the occlusion calculation is complete.
// So, we return false to signal to EnumWindows to stop enumerating.
// Otherwise, we want EnumWindows to continue, and return true.
return !unoccluded_regions_.empty();
}
base::flat_map<aura::WindowTreeHost*, aura::Window::OcclusionState>
WindowEvaluatorImpl::TakeResult() {
return std::move(occlusion_states_);
}
} // namespace
WindowsDesktopWindowIterator::WindowsDesktopWindowIterator() = default;
void WindowsDesktopWindowIterator::Iterate(WindowEvaluator* evaluator) {
evaluator_ = evaluator;
EnumWindows(&EnumWindowsOcclusionCallback, reinterpret_cast<LPARAM>(this));
}
BOOL WindowsDesktopWindowIterator::RunEvaluator(HWND hwnd) {
gfx::Rect window_rect;
bool is_relevant = IsWindowVisibleAndFullyOpaque(hwnd, &window_rect);
return evaluator_->EvaluateWindow(is_relevant, window_rect, hwnd);
}
// static
BOOL CALLBACK
WindowsDesktopWindowIterator::EnumWindowsOcclusionCallback(HWND hwnd,
LPARAM lParam) {
WindowsDesktopWindowIterator* iterator =
reinterpret_cast<WindowsDesktopWindowIterator*>(lParam);
return iterator->RunEvaluator(hwnd);
}
base::flat_map<aura::WindowTreeHost*, aura::Window::OcclusionState>
ComputeNativeWindowOcclusionStatusImpl(
const std::vector<aura::WindowTreeHost*>& windows,
std::unique_ptr<NativeWindowIterator> iterator,
std::unique_ptr<WindowBoundsDelegate> bounds_delegate) {
WindowEvaluatorImpl window_evaluator(windows, std::move(bounds_delegate));
// Only compute occlusion if there was at least one window that is visible.
if (window_evaluator.HasAtLeastOneVisibleWindow())
iterator->Iterate(&window_evaluator);
return window_evaluator.TakeResult();
}
bool IsWindowVisibleAndFullyOpaque(HWND hwnd, gfx::Rect* rect_in_pixels) {
// Filter out invalid hwnds.
if (!IsWindow(hwnd))
return false;
// Filter out windows that are not “visible”.
if (!IsWindowVisible(hwnd))
return false;
// Filter out minimized windows.
if (IsIconic(hwnd))
return false;
LONG ex_styles = GetWindowLong(hwnd, GWL_EXSTYLE);
// Filter out “transparent” windows, windows where the mouse clicks fall
// through them.
if (ex_styles & WS_EX_TRANSPARENT)
return false;
// Filter out “tool windows”, which are floating windows that do not appear on
// the taskbar or ALT-TAB. Floating windows can have larger window rectangles
// than what is visible to the user, so by filtering them out we will avoid
// incorrectly marking native windows as occluded.
if (ex_styles & WS_EX_TOOLWINDOW)
return false;
// Filter out layered windows.
if (ex_styles & WS_EX_LAYERED)
return false;
// Filter out windows that do not have a simple rectangular region.
base::win::ScopedRegion region(CreateRectRgn(0, 0, 0, 0));
if (GetWindowRgn(hwnd, region.get()) == COMPLEXREGION)
return false;
RECT window_rect;
// Filter out windows that take up zero area. The call to GetWindowRect is one
// of the most expensive parts of this function, so it is last.
if (!GetWindowRect(hwnd, &window_rect))
return false;
if (IsRectEmpty(&window_rect))
return false;
rect_in_pixels->SetByBounds(window_rect.left, window_rect.top,
window_rect.right, window_rect.bottom);
return true;
}
} // namespace aura_extra
// Copyright 2018 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.
#ifndef UI_AURA_EXTRA_WINDOW_OCCLUSION_IMPL_WIN_H_
#define UI_AURA_EXTRA_WINDOW_OCCLUSION_IMPL_WIN_H_
#include <windows.h>
#include <winuser.h>
#include "base/containers/flat_map.h"
#include "base/macros.h"
#include "ui/aura/window.h"
#include "ui/aura/window_tree_host.h"
#include "ui/aura_extra/aura_extra_export.h"
namespace aura_extra {
// Delegate to get the native window bounds in pixels for a root aura::Window.
class WindowBoundsDelegate {
public:
WindowBoundsDelegate() {}
virtual ~WindowBoundsDelegate() {}
// Gets the bounds in pixels for |window|.
virtual gfx::Rect GetBoundsInPixels(aura::WindowTreeHost* window) = 0;
DISALLOW_COPY_AND_ASSIGN(WindowBoundsDelegate);
};
// Stores internal state during occlusion computation by
// ComputeNativeWindowOcclusionStatus().
class AURA_EXTRA_EXPORT WindowEvaluator {
public:
WindowEvaluator() {}
// Called by NativeWindowIterator::Iterate and processes the metadata for a
// single window. It is assumed that this is called in reverse z-order
// (topmost window first, bottom window last). |is_relevant| describes if the
// window is relevant to this calculation (it is visible and fully opaque),
// |window_rect_in_pixels| is the bounds of the window in pixels, and |hwnd|
// is the HWND of the window. Returns false if no more windows need to be
// evaluated (this happens when all the desired occlusion states have been
// determined), true otherwise.
virtual bool EvaluateWindow(bool is_relevant,
const gfx::Rect& window_rect_in_pixels,
HWND hwnd) = 0;
DISALLOW_COPY_AND_ASSIGN(WindowEvaluator);
};
// Interface to enumerate through all the native windows. Overriden in tests to
// avoid having to rely on the OS to enumerate native windows using
// EnumWindows().
class NativeWindowIterator {
public:
virtual ~NativeWindowIterator() {}
// Enumerates through a collection of windows and applies |evaluator| to each
// window. Enumeration is done from topmost to bottommost in the z-order, and
// will stop once the evaluator returns false.
virtual void Iterate(WindowEvaluator* evaluator) = 0;
};
class AURA_EXTRA_EXPORT WindowsDesktopWindowIterator
: public NativeWindowIterator {
public:
WindowsDesktopWindowIterator();
// NativeWindowIterator:
void Iterate(WindowEvaluator* evaluator) override;
private:
// Runs |evaluator_| for |hwnd|. Returns TRUE if the evaluator should be run
// again, FALSE otherwise.
BOOL RunEvaluator(HWND hwnd);
static BOOL CALLBACK EnumWindowsOcclusionCallback(HWND hwnd, LPARAM lParam);
WindowEvaluator* evaluator_ = nullptr;
DISALLOW_COPY_AND_ASSIGN(WindowsDesktopWindowIterator);
};
// Returns true if we are interested in |hwnd| for purposes of occlusion
// calculation. We are interested in |hwnd| if it is a window that is visible,
// opaque, and a simple rectangle. If we are interested in |hwnd|, stores the
// window rectangle in |rect|
bool AURA_EXTRA_EXPORT IsWindowVisibleAndFullyOpaque(HWND hwnd,
gfx::Rect* rect_in_pixels);
// Implementation of ComputeNativeWindowOcclusionStatus().
base::flat_map<aura::WindowTreeHost*, aura::Window::OcclusionState>
AURA_EXTRA_EXPORT ComputeNativeWindowOcclusionStatusImpl(
const std::vector<aura::WindowTreeHost*>& windows,
std::unique_ptr<NativeWindowIterator> iterator,
std::unique_ptr<WindowBoundsDelegate> bounds_delegate);
} // namespace aura_extra
#endif // UI_AURA_EXTRA_WINDOW_OCCLUSION_IMPL_WIN_H_
// Copyright 2018 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 "ui/aura_extra/window_occlusion_win.h"
#include "ui/aura_extra/window_occlusion_impl_win.h"
namespace aura_extra {
namespace {
// Default implementation of WindowBoundsDelegate using GetWindowRect().
class WindowBoundsDelegateImpl : public WindowBoundsDelegate {
public:
WindowBoundsDelegateImpl();
~WindowBoundsDelegateImpl() override {}
// WindowBoundsDelegate:
gfx::Rect GetBoundsInPixels(aura::WindowTreeHost* window) override;
DISALLOW_COPY_AND_ASSIGN(WindowBoundsDelegateImpl);
};
WindowBoundsDelegateImpl::WindowBoundsDelegateImpl() = default;
gfx::Rect WindowBoundsDelegateImpl::GetBoundsInPixels(
aura::WindowTreeHost* window) {
HWND hwnd = window->GetAcceleratedWidget();
RECT window_rect_in_pixels;
bool success = GetWindowRect(hwnd, &window_rect_in_pixels);
DCHECK(success);
return gfx::Rect(window_rect_in_pixels);
}
} // namespace
base::flat_map<aura::WindowTreeHost*, aura::Window::OcclusionState>
ComputeNativeWindowOcclusionStatus(
const std::vector<aura::WindowTreeHost*>& windows) {
return ComputeNativeWindowOcclusionStatusImpl(
windows, std::make_unique<WindowsDesktopWindowIterator>(),
std::make_unique<WindowBoundsDelegateImpl>());
}
} // namespace aura_extra
// Copyright 2018 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.
#ifndef UI_AURA_EXTRA_WINDOW_OCCLUSION_WIN_H_
#define UI_AURA_EXTRA_WINDOW_OCCLUSION_WIN_H_
#include <vector>
#include "base/containers/flat_map.h"
#include "ui/aura/window.h"
#include "ui/aura/window_tree_host.h"
#include "ui/aura_extra/aura_extra_export.h"
namespace aura_extra {
// Computes the native window occlusion status for each aura::WindowTreeHost in
// |windows|.
AURA_EXTRA_EXPORT
base::flat_map<aura::WindowTreeHost*, aura::Window::OcclusionState>
ComputeNativeWindowOcclusionStatus(
const std::vector<aura::WindowTreeHost*>& windows);
} // namespace aura_extra
#endif // UI_AURA_EXTRA_WINDOW_OCCLUSION_WIN_H
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment