From 5192815098ba9845df1d320f23e7d19f178d5dd5 Mon Sep 17 00:00:00 2001 From: wheaney <42350981+wheaney@users.noreply.github.com> Date: Wed, 3 Jul 2024 12:53:06 -0700 Subject: [PATCH] Attempt to make cursor updates more efficient by queueing and performing once per frame, skip repaint trigger if a cursor update occurs --- gnome/src/cursormanager.js | 74 ++++++++++++++++++++++++-------------- gnome/src/extension.js | 19 +++++++++- gnome/src/xrEffect.js | 13 ------- 3 files changed, 66 insertions(+), 40 deletions(-) diff --git a/gnome/src/cursormanager.js b/gnome/src/cursormanager.js index b63ef5e..9bb16e6 100644 --- a/gnome/src/cursormanager.js +++ b/gnome/src/cursormanager.js @@ -11,8 +11,6 @@ export class CursorManager { this._mainActor = mainActor; this._refreshRate = refreshRate; - this._changeHookFn = null; - // Set/destroyed by _enableCloningMouse/_disableCloningMouse this._cursorWantedVisible = null; this._cursorTracker = null; @@ -131,8 +129,8 @@ export class CursorManager { } else { this._mainActor.add_actor(this._cursorActor); } - this._cursorChangedConnection = this._cursorTracker.connect('cursor-changed', this._updateMouseSprite.bind(this)); - this._cursorVisibilityChangedConnection = this._cursorTracker.connect('visibility-changed', this._handleVisibilityChanged.bind(this)); + this._cursorChangedConnection = this._cursorTracker.connect('cursor-changed', this._queueSpriteUpdate.bind(this)); + this._cursorVisibilityChangedConnection = this._cursorTracker.connect('visibility-changed', this._queueVisibilityUpdate.bind(this)); // Some elements will occasionally appear above the cursor, so we periodically reset the actor stacking. // This could theoretically be fixed "better" by attaching to all events that might affect actor ordering, @@ -144,11 +142,11 @@ export class CursorManager { }).bind(this)); const interval = 1000 / this._refreshRate; - this._cursorWatch = this._cursorWatcher.addWatch(interval, this._updateMousePosition.bind(this)); + this._cursorWatch = this._cursorWatcher.addWatch(interval, this._queuePositionUpdate.bind(this)); const [x, y] = global.get_pointer(); - this._updateMousePosition(x, y); - this._updateMouseSprite(); + this._queuePositionUpdate(x, y); + this._queueSpriteUpdate(); } if (this._cursorTracker.set_keep_focus_while_hidden) { @@ -203,34 +201,58 @@ export class CursorManager { } } - _updateMousePosition(x, y) { - this._cursorActor.set_position(x, y); + _queuePositionUpdate(x, y) { + this._queued_cursor_position = [x, y]; } - _updateMouseSprite() { - const sprite = this._cursorTracker.get_sprite(); - if (sprite) { - this._cursorSprite.content.texture = sprite; - this._cursorSprite.show(); - } else { - this._cursorSprite.hide(); + _queueSpriteUpdate() { + this._queued_sprite_update = true; + } + + _queueVisibilityUpdate() { + this._cursorTrackerSetPointerVisibleBound(false); + this._queued_visibility_update = true; + this._queueSpriteUpdate(); + } + + handleNewFrame() { + let redraw = false; + if (this._queued_cursor_position) { + const [x, y] = this._queued_cursor_position; + this._cursorActor.set_position(x, y); + this._queued_cursor_position = null; + redraw = true; } - const [xHot, yHot] = this._cursorTracker.get_hot(); - this._cursorSprite.set({ - translation_x: -xHot, - translation_y: -yHot, - }); - } + if (this._queued_sprite_update) { + const sprite = this._cursorTracker.get_sprite(); + if (sprite) { + this._cursorSprite.content.texture = sprite; + this._cursorSprite.show(); + } else { + this._cursorSprite.hide(); + } + + const [xHot, yHot] = this._cursorTracker.get_hot(); + this._cursorSprite.set({ + translation_x: -xHot, + translation_y: -yHot, + }); + this._queued_sprite_update = false; + redraw = true; + } - _handleVisibilityChanged() { - this._cursorTrackerSetPointerVisibleBound(false); - this._updateMouseSprite(); + if (this._queued_visibility_update) { + this._queued_visibility_update = false; + redraw = true; + } + + return redraw; } // updates the stacking and other attributes that are hard to track and may periodically get out of sync _periodicReset() { - this._handleVisibilityChanged(); + this._queueVisibilityUpdate(); this._mainActor.set_child_above_sibling(this._cursorActor, null); // some other processes are uninhibiting when they shouldn't, so we need to re-inhibit here diff --git a/gnome/src/extension.js b/gnome/src/extension.js index 2914f1d..faa5205 100644 --- a/gnome/src/extension.js +++ b/gnome/src/extension.js @@ -52,6 +52,7 @@ export default class BreezyDesktopExtension extends Extension { this._disable_anti_aliasing_binding = null; this._optimal_monitor_config_binding = null; this._headset_as_primary_binding = null; + this._redraw_timeline = null; if (!Globals.logger) { Globals.logger = new Logger({ @@ -207,7 +208,7 @@ export default class BreezyDesktopExtension extends Extension { try { const targetMonitor = this._target_monitor.monitor; const refreshRate = targetMonitor.refreshRate ?? 60; - this._cursor_manager = new CursorManager(Main.layoutManager.uiGroup, refreshRate * 1.05); + this._cursor_manager = new CursorManager(Main.layoutManager.uiGroup, refreshRate); this._cursor_manager.enable(); this._overlay = new St.Bin({ style: 'background-color: rgba(0, 0, 0, 1);'}); @@ -263,6 +264,17 @@ export default class BreezyDesktopExtension extends Extension { this._add_settings_keybinding('recenter-display-shortcut', this._recenter_display.bind(this)); this._add_settings_keybinding('toggle-display-distance-shortcut', this._xr_effect._change_distance.bind(this._xr_effect)); this._add_settings_keybinding('toggle-follow-shortcut', this._toggle_follow_mode.bind(this)); + + this._redraw_timeline = Clutter.Timeline.new_for_actor(Main.layoutManager.uiGroup, 1000); + this._redraw_timeline.connect('new-frame', (() => { + if (this._is_effect_running) { + // if the cursor's frame handler triggered a redraw, we'll skip triggering it + const skip_repaint = this._cursor_manager?.handleNewFrame(); + if (!skip_repaint) this._xr_effect?.queue_repaint(); + } + }).bind(this)); + this._redraw_timeline.set_repeat_count(-1); + this._redraw_timeline.start(); } catch (e) { Globals.logger.log(`ERROR: BreezyDesktopExtension _effect_enable ${e.message}\n${e.stack}`); this._effect_disable(); @@ -372,6 +384,11 @@ export default class BreezyDesktopExtension extends Extension { Globals.logger.log_debug('BreezyDesktopExtension _effect_disable'); this._is_effect_running = false; + if (this._redraw_timeline) { + this._redraw_timeline.stop(); + this._redraw_timeline = null; + } + if (this._running_poller_id) { GLib.source_remove(this._running_poller_id); this._running_poller_id = undefined; diff --git a/gnome/src/xrEffect.js b/gnome/src/xrEffect.js index d09e954..10b6589 100644 --- a/gnome/src/xrEffect.js +++ b/gnome/src/xrEffect.js @@ -292,8 +292,6 @@ export const XREffect = GObject.registerClass({ this.customBannerImage = new Clutter.Image(); this.customBannerImage.set_data(customBanner.get_pixels(), Cogl.PixelFormat.RGB_888, customBanner.width, customBanner.height, customBanner.rowstride); - - this._redraw_timeline = null; } _change_distance() { @@ -341,13 +339,6 @@ export const XREffect = GObject.registerClass({ this.setIntermittentUniformVariables = setIntermittentUniformVariables.bind(this); this.setIntermittentUniformVariables(); - this._redraw_timeline = Clutter.Timeline.new_for_actor(this.get_actor(), 1000); - this._redraw_timeline.connect('new-frame', (() => { - this.queue_repaint(); - }).bind(this)); - this._redraw_timeline.set_repeat_count(-1); - this._redraw_timeline.start(); - this._uniforms_timeout_id = GLib.timeout_add(GLib.PRIORITY_DEFAULT, 250, (() => { this.setIntermittentUniformVariables(); return GLib.SOURCE_CONTINUE; @@ -394,10 +385,6 @@ export const XREffect = GObject.registerClass({ } cleanup() { - if (this._redraw_timeline) { - this._redraw_timeline.stop(); - this._redraw_timeline = null; - } if (this._uniforms_timeout_id) GLib.source_remove(this._uniforms_timeout_id); } }); \ No newline at end of file