Move WindowPreviewLayout from JS to C

This layout manager is used quite often and the time we spend calling
it's allocate and get_preferred_width/heigth functions increases with
every open window.

We can save a lot of precious time during the layout cycle by moving
this layout manager from JS to C, thus avoiding the overhead of
trampolining between C and JS land.

In a measurement where the average time spent in vfunc_allocate() of the
Workspace actor was measured while opening an overview with 20 windows,
the average time spent went down from 3.1 ms to 2.3 ms.

Part-of: <https://gitlab.gnome.org/GNOME/gnome-shell/-/merge_requests/1743>
This commit is contained in:
Jonas Dreßler 2020-06-20 17:22:30 +02:00 committed by Marge Bot
parent 3eb40df06b
commit 04c781674c
4 changed files with 511 additions and 178 deletions

View File

@ -22,180 +22,6 @@ const ICON_OVERLAP = 0.7;
const ICON_TITLE_SPACING = 6;
var WindowPreviewLayout = GObject.registerClass({
Properties: {
'bounding-box': GObject.ParamSpec.boxed(
'bounding-box', 'Bounding box', 'Bounding box',
GObject.ParamFlags.READABLE,
Clutter.ActorBox.$gtype),
},
}, class WindowPreviewLayout extends Clutter.LayoutManager {
_init() {
super._init();
this._container = null;
this._boundingBox = new Clutter.ActorBox();
this._windows = new Map();
}
_layoutChanged() {
let frameRect;
for (const windowInfo of this._windows.values()) {
const frame = windowInfo.metaWindow.get_frame_rect();
frameRect = frameRect?.union(frame) ?? frame;
}
if (!frameRect)
frameRect = new Meta.Rectangle();
const oldBox = this._boundingBox.copy();
this._boundingBox.set_origin(frameRect.x, frameRect.y);
this._boundingBox.set_size(frameRect.width, frameRect.height);
if (!this._boundingBox.equal(oldBox))
this.notify('bounding-box');
// Always call layout_changed(), a size or position change of an
// attached dialog might not affect the boundingBox
this.layout_changed();
}
vfunc_set_container(container) {
this._container = container;
}
vfunc_get_preferred_height(_container, _forWidth) {
return [0, this._boundingBox.get_height()];
}
vfunc_get_preferred_width(_container, _forHeight) {
return [0, this._boundingBox.get_width()];
}
vfunc_allocate(container, box) {
// If the scale isn't 1, we weren't allocated our preferred size
// and have to scale the children allocations accordingly.
const scaleX = this._boundingBox.get_width() > 0
? box.get_width() / this._boundingBox.get_width()
: 1;
const scaleY = this._boundingBox.get_height() > 0
? box.get_height() / this._boundingBox.get_height()
: 1;
const childBox = new Clutter.ActorBox();
for (const child of container) {
if (!child.visible)
continue;
const windowInfo = this._windows.get(child);
if (windowInfo) {
const bufferRect = windowInfo.metaWindow.get_buffer_rect();
childBox.set_origin(
bufferRect.x - this._boundingBox.x1,
bufferRect.y - this._boundingBox.y1);
const [, , natWidth, natHeight] = child.get_preferred_size();
childBox.set_size(natWidth, natHeight);
childBox.x1 *= scaleX;
childBox.x2 *= scaleX;
childBox.y1 *= scaleY;
childBox.y2 *= scaleY;
child.allocate(childBox);
} else {
child.allocate_preferred_size(0, 0);
}
}
}
/**
* addWindow:
* @param {Meta.Window} window: the MetaWindow instance
*
* Creates a ClutterActor drawing the texture of @window and adds it
* to the container. If @window is already part of the preview, this
* function will do nothing.
*
* @returns {Clutter.Actor} The newly created actor drawing @window
*/
addWindow(window) {
const index = [...this._windows.values()].findIndex(info =>
info.metaWindow === window);
if (index !== -1)
return null;
const windowActor = window.get_compositor_private();
const actor = new Clutter.Clone({ source: windowActor });
this._windows.set(actor, {
metaWindow: window,
windowActor,
sizeChangedId: window.connect('size-changed', () =>
this._layoutChanged()),
positionChangedId: window.connect('position-changed', () =>
this._layoutChanged()),
windowActorDestroyId: windowActor.connect('destroy', () =>
actor.destroy()),
destroyId: actor.connect('destroy', () =>
this.removeWindow(window)),
});
this._container.add_child(actor);
this._layoutChanged();
return actor;
}
/**
* removeWindow:
* @param {Meta.Window} window: the window to remove from the preview
*
* Removes a MetaWindow @window from the preview which has been added
* previously using addWindow(). If @window is not part of preview,
* this function will do nothing.
*/
removeWindow(window) {
const entry = [...this._windows].find(
([, i]) => i.metaWindow === window);
if (!entry)
return;
const [actor, windowInfo] = entry;
windowInfo.metaWindow.disconnect(windowInfo.sizeChangedId);
windowInfo.metaWindow.disconnect(windowInfo.positionChangedId);
windowInfo.windowActor.disconnect(windowInfo.windowActorDestroyId);
actor.disconnect(windowInfo.destroyId);
this._windows.delete(actor);
this._container.remove_child(actor);
this._layoutChanged();
}
/**
* getWindows:
*
* Gets an array of all MetaWindows that were added to the layout
* using addWindow(), ordered by the insertion order.
*
* @returns {Array} An array including all windows
*/
getWindows() {
return [...this._windows.values()].map(i => i.metaWindow);
}
get boundingBox() {
return this._boundingBox;
}
});
var WindowPreview = GObject.registerClass({
Properties: {
'overlay-enabled': GObject.ParamSpec.boolean(
@ -235,7 +61,7 @@ var WindowPreview = GObject.registerClass({
// the initialization of the actor if that layout manager keeps track
// of its container, so set the layout manager after creating the
// container
this._windowContainer.layout_manager = new WindowPreviewLayout();
this._windowContainer.layout_manager = new Shell.WindowPreviewLayout();
this.add_child(this._windowContainer);
this._addWindow(metaWindow);
@ -601,7 +427,7 @@ var WindowPreview = GObject.registerClass({
}
_addWindow(metaWindow) {
const clone = this._windowContainer.layout_manager.addWindow(metaWindow);
const clone = this._windowContainer.layout_manager.add_window(metaWindow);
if (!clone)
return;
@ -620,7 +446,7 @@ var WindowPreview = GObject.registerClass({
}
_deleteAll() {
const windows = this._windowContainer.layout_manager.getWindows();
const windows = this._windowContainer.layout_manager.get_windows();
// Delete all windows, starting from the bottom-most (most-modal) one
for (const window of windows.reverse())
@ -645,7 +471,7 @@ var WindowPreview = GObject.registerClass({
}
_hasAttachedDialogs() {
return this._windowContainer.layout_manager.getWindows().length > 1;
return this._windowContainer.layout_manager.get_windows().length > 1;
}
_updateAttachedDialogs() {

View File

@ -109,6 +109,7 @@ libshell_public_headers = [
'shell-tray-icon.h',
'shell-tray-manager.h',
'shell-util.h',
'shell-window-preview-layout.h',
'shell-window-tracker.h',
'shell-wm.h'
]
@ -151,6 +152,7 @@ libshell_sources = [
'shell-tray-icon.c',
'shell-tray-manager.c',
'shell-util.c',
'shell-window-preview-layout.c',
'shell-window-tracker.c',
'shell-wm.c'
]

View File

@ -0,0 +1,472 @@
#include "config.h"
#include <clutter/clutter.h>
#include <meta/window.h>
#include "shell-window-preview-layout.h"
typedef struct _ShellWindowPreviewLayoutPrivate ShellWindowPreviewLayoutPrivate;
struct _ShellWindowPreviewLayoutPrivate
{
ClutterActor *container;
GHashTable *windows;
ClutterActorBox bounding_box;
};
enum
{
PROP_0,
PROP_BOUNDING_BOX,
PROP_LAST
};
static GParamSpec *obj_props[PROP_LAST] = { NULL, };
G_DEFINE_TYPE_WITH_PRIVATE (ShellWindowPreviewLayout, shell_window_preview_layout,
CLUTTER_TYPE_LAYOUT_MANAGER);
typedef struct _WindowInfo
{
MetaWindow *window;
ClutterActor *window_actor;
gulong size_changed_id;
gulong position_changed_id;
gulong window_actor_destroy_id;
gulong destroy_id;
} WindowInfo;
static void
shell_window_preview_layout_get_property (GObject *object,
unsigned int property_id,
GValue *value,
GParamSpec *pspec)
{
ShellWindowPreviewLayout *self = SHELL_WINDOW_PREVIEW_LAYOUT (object);
ShellWindowPreviewLayoutPrivate *priv;
priv = shell_window_preview_layout_get_instance_private (self);
switch (property_id)
{
case PROP_BOUNDING_BOX:
g_value_set_boxed (value, &priv->bounding_box);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
}
}
static void
shell_window_preview_layout_set_container (ClutterLayoutManager *layout,
ClutterContainer *container)
{
ShellWindowPreviewLayout *self = SHELL_WINDOW_PREVIEW_LAYOUT (layout);
ShellWindowPreviewLayoutPrivate *priv;
ClutterLayoutManagerClass *parent_class;
priv = shell_window_preview_layout_get_instance_private (self);
priv->container = CLUTTER_ACTOR (container);
parent_class = CLUTTER_LAYOUT_MANAGER_CLASS (shell_window_preview_layout_parent_class);
parent_class->set_container (layout, container);
}
static void
shell_window_preview_layout_get_preferred_width (ClutterLayoutManager *layout,
ClutterContainer *container,
float for_height,
float *min_width_p,
float *natural_width_p)
{
ShellWindowPreviewLayout *self = SHELL_WINDOW_PREVIEW_LAYOUT (layout);
ShellWindowPreviewLayoutPrivate *priv;
priv = shell_window_preview_layout_get_instance_private (self);
if (min_width_p)
*min_width_p = 0;
if (natural_width_p)
*natural_width_p = clutter_actor_box_get_width (&priv->bounding_box);
}
static void
shell_window_preview_layout_get_preferred_height (ClutterLayoutManager *layout,
ClutterContainer *container,
float for_width,
float *min_height_p,
float *natural_height_p)
{
ShellWindowPreviewLayout *self = SHELL_WINDOW_PREVIEW_LAYOUT (layout);
ShellWindowPreviewLayoutPrivate *priv;
priv = shell_window_preview_layout_get_instance_private (self);
if (min_height_p)
*min_height_p = 0;
if (natural_height_p)
*natural_height_p = clutter_actor_box_get_height (&priv->bounding_box);
}
static void
shell_window_preview_layout_allocate (ClutterLayoutManager *layout,
ClutterContainer *container,
const ClutterActorBox *box)
{
ShellWindowPreviewLayout *self = SHELL_WINDOW_PREVIEW_LAYOUT (layout);
ShellWindowPreviewLayoutPrivate *priv;
float scale_x, scale_y;
float bounding_box_width, bounding_box_height;
ClutterActorIter iter;
ClutterActor *child;
priv = shell_window_preview_layout_get_instance_private (self);
bounding_box_width = clutter_actor_box_get_width (&priv->bounding_box);
bounding_box_height = clutter_actor_box_get_height (&priv->bounding_box);
if (bounding_box_width == 0)
scale_x = 1.f;
else
scale_x = clutter_actor_box_get_width (box) / bounding_box_width;
if (bounding_box_height == 0)
scale_y = 1.f;
else
scale_y = clutter_actor_box_get_height (box) / bounding_box_height;
clutter_actor_iter_init (&iter, CLUTTER_ACTOR (container));
while (clutter_actor_iter_next (&iter, &child))
{
ClutterActorBox child_box = { 0, };
WindowInfo *window_info;
if (!clutter_actor_is_visible (child))
continue;
window_info = g_hash_table_lookup (priv->windows, child);
if (window_info)
{
MetaRectangle buffer_rect;
float child_nat_width, child_nat_height;
meta_window_get_buffer_rect (window_info->window, &buffer_rect);
clutter_actor_box_set_origin (&child_box,
buffer_rect.x - priv->bounding_box.x1,
buffer_rect.y - priv->bounding_box.y1);
clutter_actor_get_preferred_size (child, NULL, NULL,
&child_nat_width, &child_nat_height);
clutter_actor_box_set_size (&child_box, child_nat_width, child_nat_height);
child_box.x1 *= scale_x;
child_box.x2 *= scale_x;
child_box.y1 *= scale_y;
child_box.y2 *= scale_y;
clutter_actor_allocate (child, &child_box);
}
else
{
float x, y;
clutter_actor_get_fixed_position (child, &x, &y);
clutter_actor_allocate_preferred_size (child, x, y);
}
}
}
static void
on_layout_changed (ShellWindowPreviewLayout *self)
{
ShellWindowPreviewLayoutPrivate *priv;
GHashTableIter iter;
gpointer value;
gboolean first_rect = TRUE;
MetaRectangle bounding_rect = { 0, };
ClutterActorBox old_bounding_box;
priv = shell_window_preview_layout_get_instance_private (self);
old_bounding_box =
(ClutterActorBox) CLUTTER_ACTOR_BOX_INIT (priv->bounding_box.x1,
priv->bounding_box.y1,
priv->bounding_box.x2,
priv->bounding_box.y2);
g_hash_table_iter_init (&iter, priv->windows);
while (g_hash_table_iter_next (&iter, NULL, &value))
{
WindowInfo *window_info = value;
MetaRectangle frame_rect;
meta_window_get_frame_rect (window_info->window, &frame_rect);
if (first_rect)
{
bounding_rect = frame_rect;
first_rect = FALSE;
continue;
}
meta_rectangle_union (&frame_rect, &bounding_rect, &bounding_rect);
}
clutter_actor_box_set_origin (&priv->bounding_box,
(float) bounding_rect.x,
(float) bounding_rect.y);
clutter_actor_box_set_size (&priv->bounding_box,
(float) bounding_rect.width,
(float) bounding_rect.height);
if (!clutter_actor_box_equal (&priv->bounding_box, &old_bounding_box))
g_object_notify_by_pspec (G_OBJECT (self), obj_props[PROP_BOUNDING_BOX]);
clutter_layout_manager_layout_changed (CLUTTER_LAYOUT_MANAGER (self));
}
static void
on_window_size_position_changed (MetaWindow *window,
ShellWindowPreviewLayout *self)
{
on_layout_changed (self);
}
static void
on_window_destroyed (ClutterActor *actor)
{
clutter_actor_destroy (actor);
}
static void
on_actor_destroyed (ClutterActor *actor,
ShellWindowPreviewLayout *self)
{
ShellWindowPreviewLayoutPrivate *priv;
WindowInfo *window_info;
priv = shell_window_preview_layout_get_instance_private (self);
window_info = g_hash_table_lookup (priv->windows, actor);
g_assert (window_info != NULL);
shell_window_preview_layout_remove_window (self, window_info->window);
}
static void
shell_window_preview_layout_dispose (GObject *gobject)
{
ShellWindowPreviewLayout *self = SHELL_WINDOW_PREVIEW_LAYOUT (gobject);
ShellWindowPreviewLayoutPrivate *priv;
GHashTableIter iter;
gpointer key, value;
priv = shell_window_preview_layout_get_instance_private (self);
g_hash_table_iter_init (&iter, priv->windows);
while (g_hash_table_iter_next (&iter, &key, &value))
{
ClutterActor *actor = key;
WindowInfo *info = value;
g_clear_signal_handler (&info->size_changed_id, info->window);
g_clear_signal_handler (&info->position_changed_id, info->window);
g_clear_signal_handler (&info->window_actor_destroy_id, info->window_actor);
g_clear_signal_handler (&info->destroy_id, actor);
clutter_actor_remove_child (priv->container, actor);
}
g_hash_table_remove_all (priv->windows);
G_OBJECT_CLASS (shell_window_preview_layout_parent_class)->dispose (gobject);
}
static void
shell_window_preview_layout_init (ShellWindowPreviewLayout *self)
{
ShellWindowPreviewLayoutPrivate *priv;
priv = shell_window_preview_layout_get_instance_private (self);
priv->windows = g_hash_table_new (NULL, NULL);
}
static void
shell_window_preview_layout_class_init (ShellWindowPreviewLayoutClass *klass)
{
ClutterLayoutManagerClass *layout_class = CLUTTER_LAYOUT_MANAGER_CLASS (klass);
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
layout_class->get_preferred_width = shell_window_preview_layout_get_preferred_width;
layout_class->get_preferred_height = shell_window_preview_layout_get_preferred_height;
layout_class->allocate = shell_window_preview_layout_allocate;
layout_class->set_container = shell_window_preview_layout_set_container;
gobject_class->dispose = shell_window_preview_layout_dispose;
gobject_class->get_property = shell_window_preview_layout_get_property;
/**
* ShellWindowPreviewLayout:bounding-box:
*/
obj_props[PROP_BOUNDING_BOX] =
g_param_spec_boxed ("bounding-box",
"Bounding Box",
"Bounding Box",
CLUTTER_TYPE_ACTOR_BOX,
G_PARAM_READABLE |
G_PARAM_STATIC_STRINGS);
g_object_class_install_properties (gobject_class, PROP_LAST, obj_props);
}
/**
* shell_window_preview_layout_add_window:
* @self: a #ShellWindowPreviewLayout
* @window: the #MetaWindow
*
* Creates a ClutterActor drawing the texture of @window and adds it
* to the container. If @window is already part of the preview, this
* function will do nothing.
*
* Returns: (transfer none): The newly created actor drawing @window
*/
ClutterActor *
shell_window_preview_layout_add_window (ShellWindowPreviewLayout *self,
MetaWindow *window)
{
ShellWindowPreviewLayoutPrivate *priv;
ClutterActor *window_actor, *actor;
WindowInfo *window_info;
GHashTableIter iter;
gpointer value;
priv = shell_window_preview_layout_get_instance_private (self);
g_hash_table_iter_init (&iter, priv->windows);
while (g_hash_table_iter_next (&iter, NULL, &value))
{
WindowInfo *info = value;
if (info->window == window)
return NULL;
}
window_actor = CLUTTER_ACTOR (meta_window_get_compositor_private (window));
actor = clutter_clone_new (window_actor);
window_info = g_new0 (WindowInfo, 1);
window_info->window = window;
window_info->window_actor = window_actor;
window_info->size_changed_id =
g_signal_connect (window, "size-changed",
G_CALLBACK (on_window_size_position_changed), self);
window_info->position_changed_id =
g_signal_connect (window, "position-changed",
G_CALLBACK (on_window_size_position_changed), self);
window_info->window_actor_destroy_id =
g_signal_connect (window_actor, "destroy",
G_CALLBACK (on_window_destroyed), actor);
window_info->destroy_id =
g_signal_connect (actor, "destroy",
G_CALLBACK (on_actor_destroyed), self);
g_hash_table_insert (priv->windows, actor, window_info);
clutter_actor_add_child (priv->container, actor);
on_layout_changed (self);
return actor;
}
/**
* shell_window_preview_layout_remove_window:
* @self: a #ShellWindowPreviewLayout
* @window: the #MetaWindow
*
* Removes a MetaWindow @window from the preview which has been added
* previously using shell_window_preview_layout_add_window().
* If @window is not part of preview, this function will do nothing.
*/
void
shell_window_preview_layout_remove_window (ShellWindowPreviewLayout *self,
MetaWindow *window)
{
ShellWindowPreviewLayoutPrivate *priv;
ClutterActor *actor;
WindowInfo *window_info = NULL;
GHashTableIter iter;
gpointer key, value;
priv = shell_window_preview_layout_get_instance_private (self);
g_hash_table_iter_init (&iter, priv->windows);
while (g_hash_table_iter_next (&iter, &key, &value))
{
WindowInfo *info = value;
if (info->window == window)
{
actor = CLUTTER_ACTOR (key);
window_info = info;
break;
}
}
if (window_info == NULL)
return;
g_clear_signal_handler (&window_info->size_changed_id, window);
g_clear_signal_handler (&window_info->position_changed_id, window);
g_clear_signal_handler (&window_info->window_actor_destroy_id, window_info->window_actor);
g_clear_signal_handler (&window_info->destroy_id, actor);
g_hash_table_remove (priv->windows, actor);
clutter_actor_remove_child (priv->container, actor);
on_layout_changed (self);
}
/**
* shell_window_preview_layout_get_windows:
* @self: a #ShellWindowPreviewLayout
*
* Gets an array of all MetaWindows that were added to the layout
* using shell_window_preview_layout_add_window(), ordered by the
* insertion order.
*
* Returns: (transfer container) (element-type Meta.Window): The list of windows
*/
GList *
shell_window_preview_layout_get_windows (ShellWindowPreviewLayout *self)
{
ShellWindowPreviewLayoutPrivate *priv;
GList *windows = NULL;
GHashTableIter iter;
gpointer value;
priv = shell_window_preview_layout_get_instance_private (self);
g_hash_table_iter_init (&iter, priv->windows);
while (g_hash_table_iter_next (&iter, NULL, &value))
{
WindowInfo *window_info = value;
windows = g_list_prepend (windows, window_info->window);
}
return windows;
}

View File

@ -0,0 +1,33 @@
#ifndef __SHELL_WINDOW_PREVIEW_LAYOUT_H__
#define __SHELL_WINDOW_PREVIEW_LAYOUT_H__
G_BEGIN_DECLS
#include <clutter/clutter.h>
#define SHELL_TYPE_WINDOW_PREVIEW_LAYOUT (shell_window_preview_layout_get_type ())
G_DECLARE_FINAL_TYPE (ShellWindowPreviewLayout, shell_window_preview_layout,
SHELL, WINDOW_PREVIEW_LAYOUT, ClutterLayoutManager)
typedef struct _ShellWindowPreviewLayout ShellWindowPreviewLayout;
typedef struct _ShellWindowPreviewLayoutPrivate ShellWindowPreviewLayoutPrivate;
struct _ShellWindowPreviewLayout
{
/*< private >*/
ClutterLayoutManager parent;
ShellWindowPreviewLayoutPrivate *priv;
};
ClutterActor * shell_window_preview_layout_add_window (ShellWindowPreviewLayout *self,
MetaWindow *window);
void shell_window_preview_layout_remove_window (ShellWindowPreviewLayout *self,
MetaWindow *window);
GList * shell_window_preview_layout_get_windows (ShellWindowPreviewLayout *self);
G_END_DECLS
#endif /* __SHELL_WINDOW_PREVIEW_LAYOUT_H__ */