react-x11 2.47.0 → 2.47.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-x11",
3
- "version": "2.47.0",
3
+ "version": "2.47.1",
4
4
  "description": "react renderer with X11 as a target",
5
5
  "main": "./src/index.js",
6
6
  "files": [
@@ -109,7 +109,7 @@
109
109
  "yoga-layout": "^3.2.1"
110
110
  },
111
111
  "optionalDependencies": {
112
- "@windowkit/appkit": "^0.27.0",
112
+ "@windowkit/appkit": "^0.28.0",
113
113
  "@windowkit/wayland": "^3.1.1",
114
114
  "@windowkit/win32": "^0.0.9",
115
115
  "dbus-native": "^0.16.0",
@@ -255,7 +255,11 @@ export class WaylandGLContext {
255
255
  if (this.destroyed) return null;
256
256
  const w = this.window.bufferWidth;
257
257
  const h = this.window.bufferHeight;
258
- const surface = this.chain.surfaceFor(w, h);
258
+ // a window whose viewport shows part of a buffer keeps its buffers
259
+ // through a resize that fits them (`surfaceFor`'s slack)
260
+ const surface = this.chain.surfaceFor(w, h, {
261
+ slack: this.window.showsPartOfBuffer === true,
262
+ });
259
263
  this._surface = surface;
260
264
  this.gpu.makeCurrent(surface);
261
265
  let resized = false;
@@ -321,11 +325,12 @@ export class WaylandGLContext {
321
325
  // next buffer might be missing. Then the GPU work is settled (optionally
322
326
  // through a fence rather than a stall) before the swap hands it over.
323
327
  const rects = this.chain.blitHint(damage);
328
+ // into the top left of a buffer that may be larger than the window
324
329
  this.backing.blitTo(
325
330
  null,
326
331
  rects === 'all' ? null : rects,
327
- this.chain.width,
328
- this.chain.height,
332
+ this.chain.allocWidth,
333
+ this.chain.allocHeight,
329
334
  );
330
335
  await this._settleGpu();
331
336
 
@@ -338,6 +343,9 @@ export class WaylandGLContext {
338
343
  // is EGL_BAD_SURFACE (0x300d): the second window of examples/windows.jsx.
339
344
  this.gpu.makeCurrent(this._surface);
340
345
  const presented = await this.chain.swap(damage);
346
+ // the part of the buffer just attached that is the window — only with a
347
+ // buffer, since the one shown otherwise is the last, of its own size
348
+ if (presented) win.showBufferPart?.(this.chain.width, this.chain.height);
341
349
  // `swap` attached and damaged but did not commit; and when nothing went
342
350
  // out — every buffer busy — the frame request still needs a commit to be
343
351
  // delivered. Either way, exactly one commit.
@@ -59,6 +59,20 @@ import { importDmabuf, DmabufRefused } from './dmabuf.js';
59
59
  /** How many frames may be in the compositor's hands at once. */
60
60
  const DEFAULT_MAX_IN_FLIGHT = 2;
61
61
 
62
+ /**
63
+ * Buffers are made a multiple of this larger than the window where they
64
+ * may be (`slack`), so that a resize a few pixels at a time — a window edge
65
+ * dragged — shows a different part of the same buffers rather than making
66
+ * new ones at every step.
67
+ */
68
+ const SLACK_STEP = 128;
69
+ /** A visible area this many times smaller than the buffers makes them
70
+ * again, smaller: a window shrunk far is not to hold its old size's
71
+ * memory. */
72
+ const SLACK_SHRINK = 4;
73
+ /** Frames at one size before buffers grown for a drag are cut back to it. */
74
+ const SLACK_SETTLE = 60;
75
+
62
76
  /**
63
77
  * Damage sets are kept small: past a handful of rectangles the bookkeeping
64
78
  * costs more than the full-surface copy it is trying to avoid, and the
@@ -92,8 +106,14 @@ export class WaylandSwapchain {
92
106
  this.gbm = null;
93
107
  /** `${generation}:${key}` -> { wlBuffer, busy, damage, key, generation } */
94
108
  this.buffers = new Map();
109
+ /** the part of each buffer shown: the window's buffer size */
95
110
  this.width = 0;
96
111
  this.height = 0;
112
+ /** the buffers' own size, at least the part shown (`slack`) */
113
+ this.allocWidth = 0;
114
+ this.allocHeight = 0;
115
+ /** frames since the size shown last changed */
116
+ this._sameSize = 0;
97
117
  /** set once the compositor has refused a tiled buffer */
98
118
  this.linear = false;
99
119
  /** swaps since a buffer was last seen for the first time */
@@ -111,14 +131,66 @@ export class WaylandSwapchain {
111
131
  /**
112
132
  * Point the chain at a size.
113
133
  *
134
+ * With `slack` — where the window has a viewport, whose source rectangle
135
+ * shows part of a buffer — the buffers are made larger than the size and
136
+ * kept while it fits in them: dragging a window's edge used to make the
137
+ * chain's buffers again at every step, and import each one into the
138
+ * compositor, a dozen requests and a GPU allocation a step. Without it,
139
+ * the buffers are the size exactly, as a surface with no viewport shows
140
+ * all of its buffer.
141
+ *
142
+ * @param {number} width
143
+ * @param {number} height
144
+ * @param {{ slack?: boolean }} [opts]
114
145
  * @returns the GBM surface to draw into
115
146
  */
116
- surfaceFor(width, height) {
147
+ surfaceFor(width, height, { slack = false } = {}) {
117
148
  const w = Math.max(1, Math.round(width));
118
149
  const h = Math.max(1, Math.round(height));
119
- if (!this.gbm) return this._create(w, h);
120
- if (w === this.width && h === this.height) return this.gbm;
150
+ if (!this.gbm) {
151
+ const [aw, ah] = slack ? slackFor(w, h, 1) : [w, h];
152
+ this._create(aw, ah);
153
+ this.width = w;
154
+ this.height = h;
155
+ return this.gbm;
156
+ }
157
+ if (w === this.width && h === this.height) {
158
+ // A size held a while is given buffers its own size again, where a
159
+ // drag left them larger than it needs.
160
+ if (
161
+ ++this._sameSize === SLACK_SETTLE &&
162
+ (this.allocWidth !== w || this.allocHeight !== h)
163
+ ) {
164
+ const [aw, ah] = slack ? slackFor(w, h, 1) : [w, h];
165
+ if (aw !== this.allocWidth || ah !== this.allocHeight)
166
+ this._resize(aw, ah);
167
+ }
168
+ return this.gbm;
169
+ }
170
+ this._sameSize = 0;
171
+ if (
172
+ slack &&
173
+ w <= this.allocWidth &&
174
+ h <= this.allocHeight &&
175
+ w * h * SLACK_SHRINK >= this.allocWidth * this.allocHeight
176
+ ) {
177
+ // The buffers hold it: show another part of them. What each holds is
178
+ // of the old size, so all of it is owed to it.
179
+ this.width = w;
180
+ this.height = h;
181
+ for (const entry of this.buffers.values()) entry.damage = 'all';
182
+ return this.gbm;
183
+ }
184
+ const growing = w > this.allocWidth || h > this.allocHeight;
185
+ const [aw, ah] = slack ? slackFor(w, h, growing ? 1.25 : 1) : [w, h];
186
+ this._resize(aw, ah);
187
+ this.width = w;
188
+ this.height = h;
189
+ return this.gbm;
190
+ }
121
191
 
192
+ /** Give the chain buffers of this size, keeping its GBM surface. */
193
+ _resize(w, h) {
122
194
  // In place: the handle stays valid, the context keeps its GL objects and
123
195
  // stays current. Buffers from the old generation are gone, so their
124
196
  // `wl_buffer`s go with them — the compositor may still be showing one, so
@@ -132,14 +204,13 @@ export class WaylandSwapchain {
132
204
  { cause: err },
133
205
  ),
134
206
  );
135
- return this.gbm;
207
+ return;
136
208
  }
137
209
  this._dropBuffers();
138
- this.width = w;
139
- this.height = h;
210
+ this.allocWidth = w;
211
+ this.allocHeight = h;
140
212
  this.inFlight = 0;
141
213
  this._swapsSinceNew = 0;
142
- return this.gbm;
143
214
  }
144
215
 
145
216
  _create(w, h) {
@@ -157,8 +228,8 @@ export class WaylandSwapchain {
157
228
  { cause: err },
158
229
  );
159
230
  }
160
- this.width = w;
161
- this.height = h;
231
+ this.allocWidth = w;
232
+ this.allocHeight = h;
162
233
  this._swapsSinceNew = 0;
163
234
  return this.gbm;
164
235
  }
@@ -285,7 +356,8 @@ export class WaylandSwapchain {
285
356
  this.policy.linearFallback &&
286
357
  !this.linear
287
358
  ) {
288
- const { width, height } = this;
359
+ // the buffers again, at the size they were: the part shown stays
360
+ const { allocWidth: width, allocHeight: height } = this;
289
361
  this.linear = true;
290
362
  this._dropBuffers();
291
363
  try {
@@ -409,3 +481,14 @@ export function unionDamage(a, b) {
409
481
  const merged = [...a, ...b];
410
482
  return merged.length > MAX_RECTS ? 'all' : merged;
411
483
  }
484
+
485
+ /**
486
+ * Buffers for a size, with room to grow: each side rounded up to a
487
+ * multiple of `SLACK_STEP`, after `grow` — 1.25 for a size outgrowing the
488
+ * buffers it had, so a window dragged wider makes a few sets of buffers
489
+ * rather than one a bucket.
490
+ */
491
+ function slackFor(w, h, grow) {
492
+ const up = (v) => Math.ceil((v * grow) / SLACK_STEP) * SLACK_STEP;
493
+ return [up(w), up(h)];
494
+ }
@@ -182,6 +182,8 @@ export class WaylandWindow extends EventEmitter {
182
182
  this._onConfigured = resolve;
183
183
  });
184
184
  this._viewport = null;
185
+ /** the part of the buffer the viewport shows, once one is attached */
186
+ this._shownPart = null;
185
187
  this._fractional = null;
186
188
  this._geometry = null;
187
189
  /** the xdg-decoration object, where the compositor has the protocol (ssd.js) */
@@ -545,6 +547,31 @@ export class WaylandWindow extends EventEmitter {
545
547
  }
546
548
  }
547
549
 
550
+ /**
551
+ * Whether the surface shows a part of its buffer, the top left
552
+ * `bufferWidth` by `bufferHeight` of it, rather than all of it: where the
553
+ * compositor has a viewport to say so. The buffer may then be larger than
554
+ * the window, and is kept as the window is resized within it.
555
+ */
556
+ get showsPartOfBuffer() {
557
+ return this._viewport !== null;
558
+ }
559
+
560
+ /**
561
+ * Show the top left `width` by `height` of the buffer the next commit
562
+ * attaches (`showsPartOfBuffer`). Only with a commit that attaches one at
563
+ * least that large: a source rectangle past the buffer shown is a protocol
564
+ * error, and a commit with no new buffer — every buffer busy, a configure
565
+ * acked — shows the old one. In buffer pixels, the buffer scale being 1.
566
+ */
567
+ showBufferPart(width, height) {
568
+ if (!this._viewport) return;
569
+ const shown = this._shownPart;
570
+ if (shown && shown.width === width && shown.height === height) return;
571
+ this._shownPart = { width, height };
572
+ this._viewport.$.set_source(0, 0, width, height);
573
+ }
574
+
548
575
  /** The size the backing buffer has to be for the current scale. */
549
576
  get bufferWidth() {
550
577
  return Math.max(1, Math.round(this.width * this.scale));