react-x11 2.15.0 → 2.15.2

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.15.0",
3
+ "version": "2.15.2",
4
4
  "description": "react renderer with X11 as a target",
5
5
  "main": "./src/index.js",
6
6
  "files": [
@@ -532,7 +532,7 @@ export class WaylandContext2D {
532
532
  */
533
533
  begin(width, height, timestamp = 0) {
534
534
  this.init();
535
- this._device.owner = this;
535
+ this._take();
536
536
  const t = this._target;
537
537
  if (t) {
538
538
  t.bind();
@@ -612,7 +612,7 @@ export class WaylandContext2D {
612
612
  // Mid stencil-then-cover the state is set up for the pass, not for a
613
613
  // plain batch, and the device cannot have changed hands under it.
614
614
  if (this._device.owner === this || this._stencilling) return;
615
- this._device.owner = this;
615
+ this._take();
616
616
  this._makeCurrent?.();
617
617
  this.init();
618
618
  // As `begin()` has it: a target's size *is* the projection.
@@ -624,6 +624,26 @@ export class WaylandContext2D {
624
624
  this._applyState();
625
625
  }
626
626
 
627
+ /**
628
+ * Take the device, drawing whatever the last owner still had buffered.
629
+ *
630
+ * A context batches: its quads sit in a vertex buffer until the mode
631
+ * changes, the buffer fills, or someone flushes. So the handover is where
632
+ * the outgoing owner's batch is drawn — otherwise a frame's worth of work
633
+ * can sit there while the pixels it was meant to produce are read by
634
+ * whoever comes next, and stay there until that context happens to draw
635
+ * something of a different kind (#578). The state is still the outgoing
636
+ * owner's at this point, which is what the flush needs.
637
+ *
638
+ * It is also what device.js's invariant rests on: the owner is the only
639
+ * context that can be holding anything.
640
+ */
641
+ _take() {
642
+ const previous = this._device.owner;
643
+ if (previous && previous !== this) previous._flush();
644
+ this._device.owner = this;
645
+ }
646
+
627
647
  /** Give the device up, so the next draw through this context re-takes it. */
628
648
  _release() {
629
649
  if (this._device.owner === this) this._device.owner = null;
@@ -1950,6 +1970,11 @@ export class WaylandContext2D {
1950
1970
 
1951
1971
  /** Two triangles from four corners, in order TL, TR, BR, BL. */
1952
1972
  _quad(pos, uv, color, params) {
1973
+ // Buffering is taking the device, not a step before it: the quad is GL
1974
+ // work that has been decided on and not yet issued, and the handover in
1975
+ // `_take` is the only thing that can get it issued before someone reads
1976
+ // the pixels it makes. A property compare in the common case.
1977
+ this._claim();
1953
1978
  if (this._n + 6 > MAX_VERTS) this._flush();
1954
1979
  const v = this._verts;
1955
1980
  let o = this._n * STRIDE;
@@ -1974,6 +1999,7 @@ export class WaylandContext2D {
1974
1999
 
1975
2000
  /** One triangle, for the stencil pass. Colour and params are irrelevant. */
1976
2001
  _tri(x0, y0, x1, y1, x2, y2) {
2002
+ this._claim();
1977
2003
  if (this._n + 3 > MAX_VERTS) this._flush();
1978
2004
  const v = this._verts;
1979
2005
  let o = this._n * STRIDE;
@@ -16,6 +16,15 @@
16
16
  // back. Whoever drew in between does not have to know who comes next, and a
17
17
  // caller does not have to know there is a device at all.
18
18
  //
19
+ // Claiming is also a *batch boundary*: a 2d context buffers its quads and
20
+ // draws them in one call later, so the outgoing owner is flushed on the way
21
+ // out. That is what makes the invariant this module offers true — **only the
22
+ // owner can have work still buffered** — and the invariant is what lets
23
+ // anyone about to read a target's pixels get them by flushing one context
24
+ // (`flushDevice`) rather than by knowing which one drew them. Without it a
25
+ // held surface context's whole frame could sit in a vertex buffer while the
26
+ // `drawImage` that should show it sampled the texture behind it (#578).
27
+ //
19
28
  // One record per `gl`, keyed weakly: a GPU that goes away takes its record
20
29
  // with it.
21
30
  const devices = new WeakMap();
@@ -53,3 +62,17 @@ export function releaseDevice(gl) {
53
62
  const device = shared(gl) ? devices.get(gl) : null;
54
63
  if (device) device.owner = null;
55
64
  }
65
+
66
+ /**
67
+ * Draw what is still buffered, so that a target's texture holds everything
68
+ * that has been drawn into it.
69
+ *
70
+ * The owner is the only context that can be holding anything (see above), so
71
+ * flushing it is flushing the device. Call before *reading* a target's pixels
72
+ * behind the contexts' backs — a framebuffer blit, a scroll copy — the way
73
+ * `releaseDevice` is called after writing them.
74
+ */
75
+ export function flushDevice(gl) {
76
+ const device = shared(gl) ? devices.get(gl) : null;
77
+ device?.owner?.flush();
78
+ }
@@ -21,7 +21,7 @@
21
21
  // asking EGL for it means the window's own config no longer has to carry
22
22
  // stencil bits at all; the request in glcontext.js stays as belt and braces.
23
23
 
24
- import { releaseDevice } from './device.js';
24
+ import { flushDevice, releaseDevice } from './device.js';
25
25
 
26
26
  export class GLTarget {
27
27
  /**
@@ -133,6 +133,9 @@ export class GLTarget {
133
133
  */
134
134
  blitTo(drawFbo, rects, dstWidth = this.width, dstHeight = this.height) {
135
135
  const gl = this.gl;
136
+ // Reading these pixels, so everything drawn into them has to be in the
137
+ // texture and not still in a context's vertex buffer (device.js).
138
+ flushDevice(gl);
136
139
  gl.bindFramebuffer(gl.READ_FRAMEBUFFER, this.fbo);
137
140
  gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, drawFbo);
138
141
  gl.disable(gl.SCISSOR_TEST);
@@ -201,6 +204,11 @@ export class GLTarget {
201
204
  const sy = dstY0 - dy;
202
205
 
203
206
  const gl = this.gl;
207
+ // A scroll reads this target before it writes it, so anything a context
208
+ // still has buffered has to land first — or the band that moves is the
209
+ // frame before last's, and the buffered quads arrive afterwards at the
210
+ // position the scroll just left (device.js).
211
+ flushDevice(gl);
204
212
  const scratch = scratchFor(gl, bw, bh);
205
213
  // out: this[sx,sy,bw,bh] -> scratch[0,0]
206
214
  gl.bindFramebuffer(gl.READ_FRAMEBUFFER, this.fbo);
@@ -17,7 +17,7 @@
17
17
  // xkb_types `type "FOUR_LEVEL" { modifiers= Shift+LevelThree;
18
18
  // map[Shift]= 2; ... }` modifier set -> level
19
19
  // xkb_symbols `key <AD01> { [ q, Q ] };` keycode -> keysyms per level,
20
- // with `symbols[Group2]` and `type=` where given
20
+ // with `symbols[Group2]`/`symbols[2]` and `type=` where given
21
21
  // `modifier_map Mod5 { <LVL3> }` real modifier -> keycodes
22
22
  // xkb_compat `interpret ISO_Level3_Shift { virtualModifier= LevelThree; }`
23
23
  // keysym -> virtual modifier
@@ -187,17 +187,32 @@ export class XkbKeymap {
187
187
  // explicit per-group or global type
188
188
  const typeAll = body.match(/(?:^|[,{\s])type\s*=\s*"([^"]+)"/);
189
189
  const typeByGroup = new Map();
190
- for (const t of body.matchAll(/type\[Group(\d)\]\s*=\s*"([^"]+)"/g))
190
+ for (const t of body.matchAll(/type\[(?:Group)?(\d)\]\s*=\s*"([^"]+)"/g))
191
191
  typeByGroup.set(+t[1], t[2]);
192
192
  // symbols[GroupN]= [ a, b ] and the bare form { [ a, b ] }
193
+ //
194
+ // The group subscript has two spellings, and the one this file was
195
+ // written against is the one a compositor never sends. `xkbcomp -xkb`
196
+ // writes `symbols[Group1]`; **libxkbcommon writes `symbols[1]`**, and
197
+ // libxkbcommon is what serialises the keymap on the other end of
198
+ // `wl_keyboard.keymap` — so every keymap that reaches this parser for
199
+ // real uses the bare number. Both are accepted, because a keymap also
200
+ // arrives here from `xkbcomp` output in a test or a bug report.
193
201
  const lists = [];
194
202
  for (const g of body.matchAll(
195
- /symbols\[Group(\d)\]\s*=\s*\[([^\]]*)\]/g,
203
+ /symbols\[(?:Group)?(\d)\]\s*=\s*\[([^\]]*)\]/g,
196
204
  )) {
197
205
  lists[+g[1] - 1] = g[2];
198
206
  }
199
207
  if (lists.length === 0) {
200
- const bare = body.match(/\[([^\]]*)\]/);
208
+ // The bare form, `key <ESC> { [ Escape ] };` — the list is the one
209
+ // bracket that is *not* a subscript, which is what the leading
210
+ // delimiter says: a subscript is always glued to the word in front
211
+ // of it (`symbols[`, `type[`, `actions[`). Matching any bracket at
212
+ // all is how an unrecognised subscript spelling used to end up here
213
+ // and take `[1]` for the symbol list, which decodes every key on the
214
+ // keyboard to the keysym named `1`.
215
+ const bare = body.match(/(?:^|[,{\s])\[([^\]]*)\]/);
201
216
  if (bare) lists[0] = bare[1];
202
217
  }
203
218
  for (let gi = 0; gi < lists.length; gi++) {