react-x11 2.52.0 → 2.54.0

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.
Files changed (115) hide show
  1. package/README.md +10 -10
  2. package/docs/README.md +160 -314
  3. package/docs/accessibility.md +176 -229
  4. package/docs/appearance.md +77 -176
  5. package/docs/click-to-component.md +18 -51
  6. package/docs/clipboard.md +40 -59
  7. package/docs/components.md +518 -1036
  8. package/docs/dbus.md +61 -133
  9. package/docs/debugging.md +152 -245
  10. package/docs/desktop-calendar.md +51 -88
  11. package/docs/desktop.md +222 -347
  12. package/docs/devtools.md +50 -167
  13. package/docs/drag-and-drop.md +126 -204
  14. package/docs/ecosystem/animation.md +91 -125
  15. package/docs/ecosystem/data-fetching.md +38 -53
  16. package/docs/ecosystem/dev-tooling.md +159 -202
  17. package/docs/ecosystem/forms.md +61 -97
  18. package/docs/ecosystem/headless.md +64 -89
  19. package/docs/ecosystem/i18n.md +18 -21
  20. package/docs/ecosystem/icons.md +93 -126
  21. package/docs/ecosystem/layout.md +42 -52
  22. package/docs/ecosystem/routing.md +18 -29
  23. package/docs/ecosystem/state.md +71 -88
  24. package/docs/ecosystem/testing.md +131 -157
  25. package/docs/ecosystem/theming.md +119 -165
  26. package/docs/ecosystem.md +145 -179
  27. package/docs/elements.md +1096 -1774
  28. package/docs/embedding.md +83 -132
  29. package/docs/events.md +412 -695
  30. package/docs/extending.md +784 -1326
  31. package/docs/eyedropper.md +63 -122
  32. package/docs/filedialog.md +71 -107
  33. package/docs/frame.md +195 -339
  34. package/docs/gl.md +65 -62
  35. package/docs/globalmenu.md +63 -146
  36. package/docs/macos.md +531 -3234
  37. package/docs/notifications.md +33 -47
  38. package/docs/packaging.md +166 -309
  39. package/docs/permissions.md +9 -14
  40. package/docs/react-features.md +81 -106
  41. package/docs/remote.md +32 -76
  42. package/docs/scale.md +72 -186
  43. package/docs/styling.md +329 -547
  44. package/docs/system.md +200 -280
  45. package/docs/testing.md +63 -83
  46. package/docs/typescript.md +6 -48
  47. package/docs/uri-schemes.md +67 -107
  48. package/docs/wayland-backend.md +200 -459
  49. package/docs/windows-integrations.md +75 -131
  50. package/docs/windows.md +343 -1367
  51. package/package.json +3 -2
  52. package/src/Reconciler.js +2 -2
  53. package/src/a11y.js +1 -1
  54. package/src/backend/context2d.js +3 -3
  55. package/src/bootstrap.js +1 -1
  56. package/src/cocoa/app.js +6 -6
  57. package/src/cocoa/bezels.js +1 -1
  58. package/src/cocoa/fonts.js +1 -1
  59. package/src/cocoa/globalmenu.js +1 -1
  60. package/src/cocoa/keymap.js +1 -1
  61. package/src/cocoa/panewindow.js +1 -1
  62. package/src/cocoa/player.js +1 -1
  63. package/src/cocoa/presenter.js +2 -2
  64. package/src/cocoa/promotion.js +1 -1
  65. package/src/cocoa/relaunch.js +1 -1
  66. package/src/cocoa/threaded.js +1 -1
  67. package/src/cocoa/video.js +1 -1
  68. package/src/cocoa/window.js +4 -4
  69. package/src/components/Menu.js +7 -2
  70. package/src/components/Switch.js +1 -1
  71. package/src/components/native.js +1 -1
  72. package/src/dnd.js +2 -2
  73. package/src/glnodes.js +1 -1
  74. package/src/grid.js +1 -1
  75. package/src/imagedecode-worker.js +32 -0
  76. package/src/imagedecode.js +140 -2
  77. package/src/layouts.js +1 -1
  78. package/src/nodes/animation.js +1 -1
  79. package/src/nodes/invalidate.js +1 -1
  80. package/src/nodes/layouthost.js +1 -1
  81. package/src/nodes/video.js +19 -8
  82. package/src/nodes/window/droptarget.js +1 -1
  83. package/src/ntk.d.ts +12 -0
  84. package/src/pacing.js +1 -1
  85. package/src/refresh/index.js +2 -1
  86. package/src/types/video.d.ts +5 -2
  87. package/src/videobackend.js +28 -0
  88. package/src/videoframes.js +29 -16
  89. package/src/wayland/app.js +27 -0
  90. package/src/wayland/video.js +376 -0
  91. package/src/win32/a11y.js +1 -1
  92. package/src/win32/app.js +2 -2
  93. package/src/win32/fonts.js +1 -1
  94. package/src/win32/glarea.js +4 -4
  95. package/src/win32/overlay.js +1 -1
  96. package/src/win32/panewindow.js +3 -3
  97. package/src/win32/shell.js +1 -1
  98. package/src/win32/window.js +2 -2
  99. package/src/windowid.js +1 -1
  100. package/src/xvideo.js +189 -0
  101. package/docs/architecture/accessibility.md +0 -470
  102. package/docs/architecture/animation.md +0 -707
  103. package/docs/architecture/container-queries.md +0 -525
  104. package/docs/architecture/custom-layout.md +0 -334
  105. package/docs/architecture/drag-and-drop.md +0 -941
  106. package/docs/architecture/element-layer-contents.md +0 -622
  107. package/docs/architecture/frame-pacing.md +0 -255
  108. package/docs/architecture/grid-layout.md +0 -358
  109. package/docs/architecture/protocol-efficiency.md +0 -357
  110. package/docs/architecture/video.md +0 -1149
  111. package/docs/architecture/windowed-regions.md +0 -854
  112. package/docs/glx.md +0 -137
  113. package/docs/wayland.md +0 -545
  114. package/docs/windows-embedding.md +0 -271
  115. package/docs/windows-gl.md +0 -221
@@ -1,271 +0,0 @@
1
- # Embedding on Windows: `<Frame>`, `<foreign>`, and being embedded
2
-
3
- Three questions, one public API each, and they are **already written**. What
4
- this page works out is which Windows mechanism sits under each, and what that
5
- costs — not what to call any of it.
6
-
7
- The rule the whole page is written against: **the API does not change.** An
8
- app writes `<Frame src>`, `<foreign windowId>` and `<window embeddable>` and
9
- asks `useSupports('embedding')`; which primitive answers is the backend's
10
- business and nobody else's.
11
-
12
- ## Where this stands today
13
-
14
- **`<Frame src>` is built and works.** Steps 1 and 2 below are what built
15
- it. The numbers, because the whole point of
16
- the element is a number:
17
-
18
- | the host's event loop, while the pane computes | beats in 9s | median | p95 | worst |
19
- | ---------------------------------------------- | ----------- | ------ | ----- | --------- |
20
- | the pane in its own process | 261 | 11ms | 12ms | **13ms** |
21
- | the same module inline | 172 | 11ms | 114ms | **411ms** |
22
-
23
- Same component, same work, mounted two ways. Framed, the host never stalls
24
- longer than a frame; inline it stalls for a third of a second and loses a
25
- third of its heartbeats. That is the ticker not breaking stride.
26
-
27
- `<foreign>` still refuses here, and still says so:
28
-
29
- ```
30
- react-x11: <foreign> needs a backend with cross-process window embedding,
31
- and this one has none — so nothing can be put in it. Ask
32
- useSupports('embedding') before rendering one.
33
- ```
34
-
35
- which is correct and deliberate — see the section on it below. `<Frame>` no
36
- longer goes near that path: a backend that composites panes from a shared
37
- buffer answers `createPaneHost`, and `<Frame>` dispatches on that instead.
38
-
39
- Steps 3 and 4 are done too: no pane is forked before anyone asks whether it
40
- can be shown, and an embeddable window hands out a handle a host can embed.
41
- What is left is `<foreign>` — the one direction this page recommends _not_
42
- building — and OLE, which the finding below makes a larger question than it
43
- looked.
44
-
45
- ## The two directions are different problems
46
-
47
- | | who owns the pixels | what the other side must be |
48
- | --------------------- | ------------------------------------------ | --------------------------------- |
49
- | `<Frame src>` | a pane of **our own** app, another process | nothing — we start it |
50
- | `<foreign windowId>` | **another application's** window | a cooperating or ordinary app |
51
- | `<window embeddable>` | **us**, inside somebody else's UI | a host that knows how to place us |
52
-
53
- X11 answers all three with one primitive — reparenting, with XEmbed as the
54
- handshake — which is why they look like one feature there. Windows has no
55
- single primitive that covers them, so each needs deciding on its own.
56
-
57
- ## `<Frame>`: the surface path — how it was built
58
-
59
- The seam already exists and is tiny. A backend that composites panes from
60
- shared memory declares itself with `app.createPaneHost(wnd)`, returning an
61
- object with three methods (`src/cocoa/panehost.js` is the whole reference
62
- implementation, 78 lines):
63
-
64
- ```js
65
- setRect(rect); // where the pane goes, in the host's window
66
- present(surfaceId); // the pane published a new buffer; show it
67
- destroy();
68
- ```
69
-
70
- Everything else — the fork, the props channel, the bridged theme, the crash
71
- fallback, layout and hit testing — is backend-agnostic and already runs here.
72
-
73
- On Cocoa `surfaceId` is an `IOSurface`. The Windows counterpart is a
74
- **DirectComposition surface handle**, and the machinery around it is mostly in
75
- place already:
76
-
77
- - `WindowVisual(windowId)` (windows/src/bridge.h) hands back a window's root
78
- DComp visual, and `glcontext.cc` already parents a **child visual** into it
79
- for `<glarea>`. A pane host is the same move with different content.
80
- - `BridgeD3DDevice()` is the shared D3D11 device.
81
-
82
- What is missing is the cross-process buffer. Two candidates, and they are not
83
- equally good:
84
-
85
- **`DCompositionCreateSurfaceHandle`** — purpose-built for this. The pane
86
- creates the handle, makes a composition swapchain against it
87
- (`IDXGIFactory2::CreateSwapChainForCompositionSurfaceHandle`), and draws; the
88
- host opens it with `IDCompositionDevice::CreateSurfaceFromHandle` and hands it
89
- to a visual with `SetContent`. It is an NT handle, so it crosses processes
90
- with `DuplicateHandle` like any other. This is the path Chromium uses for
91
- out-of-process content, and the flip is the composition engine's rather than
92
- ours — no explicit synchronisation to get wrong.
93
-
94
- **A shared DXGI texture** — `IDXGIResource1::CreateSharedHandle` on the pane
95
- side, `ID3D11Device1::OpenSharedResource1` on the host's. More general, and it
96
- puts synchronisation back on us: a keyed mutex or a shared fence per frame,
97
- which is a class of bug the first option does not have.
98
-
99
- **Recommendation: the DComp surface handle.** It matches how the rest of this
100
- backend already presents, it needs no fence protocol across the process
101
- boundary, and `present(handle)` then means what `present(iosurfaceId)` means
102
- on Cocoa — "this buffer is the current one" — which keeps the seam honest
103
- rather than only similar.
104
-
105
- The one real question a spike had to answer: the pane process must create
106
- DComp objects, which means a **D3D device of its own**. Two devices, one
107
- adapter; the handle is shared, the devices are not.
108
-
109
- **Answered: the pane keeps the bridge unchanged.** `start()` already builds
110
- this process's devices, the pane calls it like any other process, and the
111
- handle crosses. What the spike also turned up, and what no document states,
112
- is two facts the implementation rests on — both now asserted by the bridge's
113
- `test/pane.js`, because both fail silently:
114
-
115
- - **A pane's back buffer is two frames stale.** A flip chain recycles
116
- buffers rather than keeping a bitmap, so a pane repainting only its damage
117
- leaves the rest of itself showing frame N-2. Five presents, the fifth
118
- touching one corner: the rest came back as the third. The pane window
119
- therefore repaints this frame's damage _and_ the last frame's
120
- (`STALE_FRAMES`, src/win32/panewindow.js), as one clipped pass over their
121
- bounding box — one pass and not one per rect, because two passes over the
122
- same pixel composite it twice and a translucent node painted twice is the
123
- wrong colour.
124
- - **A pane has to be clipped to its rect.** The two sides disagree about a
125
- pane's size for as long as a round trip takes, and every shrink of a
126
- `<Frame>`'s box is one of those. Unclipped, the frames in between paint at
127
- the old size over whatever is beside the pane; `paneSetRect` now sets the
128
- visual's clip, so a pane that has not caught up is short of its corner
129
- instead.
130
-
131
- ## `<foreign>`: child HWNDs, and why it is the least attractive of the three
132
-
133
- `SetParent` across processes works, and it is the closest thing Windows has to
134
- reparenting. It is also the one the platform warns about, and the warning is
135
- real: parenting across a process boundary attaches the two threads' input
136
- queues, so a hung child hangs the parent's input — docs/windows.md already
137
- says this of `<foreign>` and it is the reason to be slow here.
138
-
139
- It is worth noting what the X11 version is used for (docs/embedding.md): a
140
- terminal pane, a video surface, a docked tray icon, a control-panel applet.
141
- On Windows the first two are better served by other things — a terminal is a
142
- pseudoconsole this app can render itself, and video is a swapchain — and the
143
- last two do not exist as embeddable objects at all.
144
-
145
- **Recommendation: leave `<foreign>` refusing on Windows.** It already refuses
146
- honestly, `useSupports('embedding')` already says so, and a `SetParent`
147
- implementation would buy a narrow set of cases at the cost of handing any
148
- embedded program the ability to freeze the host. Revisit if a concrete need
149
- turns up; do not build it speculatively.
150
-
151
- ## `<window embeddable>`: being the guest
152
-
153
- This is the one the API is most ready for and the one with the most choices
154
- under it. The prop already means exactly the right thing — "created, not
155
- shown; somebody else will place me" — and core already honours it on every
156
- backend by not mapping the window.
157
-
158
- **As a child HWND — measured, and it does not work.** This was the
159
- recommendation on this page, and it is wrong. A composition target bound to
160
- one of our windows **stops presenting the moment that window becomes a
161
- child**, and the guest goes blank. Three ways of asking, one answer:
162
-
163
- | what was tried | the guest's own pixels |
164
- | --------------------------------------------------- | ---------------------- |
165
- | top-level, composed, drawn — the baseline | **30800 / 30800** |
166
- | …then `SetParent` into another window as `WS_CHILD` | 0 / 30800 |
167
- | created, reparented, and composed only _afterwards_ | 0 / 30800 |
168
- | the same again without `WS_EX_NOREDIRECTIONBITMAP` | 0 / 30800 |
169
-
170
- So it is not an ordering mistake and not the no-redirection style: a
171
- DirectComposition target wants a top-level window. Reading the _host_ back
172
- cannot see this either way — a child's composition tree is its own and
173
- `PrintWindow` on the parent never contains it — which is why the numbers
174
- above are the guest's own pixels, with a screen capture agreeing.
175
-
176
- What a child HWND would cost, then, is not `SetParent`: it is **a second
177
- presentation path** for embeddable windows, drawing through a redirection
178
- bitmap instead of a composition surface. That is not a flag. It is
179
- `beginDraw` per damage rect, `scrollRegion`, transparency and rounded popups
180
- — the whole surface model this backend is built on — written again, for the
181
- one case that cannot have the first one.
182
-
183
- **What to do instead: publish the buffer, not the window.** The pane
184
- mechanism already built for `<Frame>` is the answer, pointed the other way: a
185
- guest makes a composition surface handle, draws into it, and hands the handle
186
- out; the host binds it to a visual in its own tree. It exists, it is tested,
187
- it needs no reparenting, and it does not attach the two processes' input
188
- queues — so the input-queue caveat that makes `<foreign>` unattractive here
189
- does not apply to the guest direction at all.
190
-
191
- It asks something of the host: the host must be able to bind a composition
192
- surface, which WinUI 3, another react-x11 app, a DComp-based Electron host
193
- or a game editor can, and a plain Win32 or OLE document host cannot. That is
194
- the real trade, and it is a better one than a second renderer.
195
-
196
- The open question was the **API**, not the mechanism. X11's guest hands out a
197
- window id from `windowIdOf(ref)`; Windows' would hand out a buffer handle,
198
- which is the same shape and a different number — "same public API, different
199
- implementation", exactly as asked. What it needed was a name and a home, and
200
- that was a decision rather than a build — taken below.
201
-
202
- **As an OLE embedding, for actual documents.** "Embed into a Windows document"
203
- in the literal sense — a pane inside a Word or Outlook document — is OLE
204
- Compound Documents: register a CLSID, implement `IOleObject`,
205
- `IOleInPlaceObject`, `IViewObject2` and `IDataObject`, and in-place activation
206
- gives us a child HWND in the host's document, which is the previous paragraph
207
- with a large COM preamble in front of it.
208
-
209
- It is a real answer to a real question, and it is a lot: a registered local
210
- server, apartment discipline, and a storage format for the "not currently
211
- activated" state (a document has to show _something_ when the object is not
212
- running — a metafile or a bitmap, which `window.snapshot()` can already
213
- produce). Worth its own decision, not worth folding into this one.
214
-
215
- **Built: `windowHandleOf(ref)` / `useWindowHandle(ref)`.** `<window
216
- embeddable>` already meant the right thing on every backend; this is the
217
- number that goes with it, and it is deliberately not `windowIdOf` — on X11
218
- the two are the same, and that is exactly why nobody noticed they were two
219
- questions.
220
-
221
- | backend | what it hands out |
222
- | ------------- | ----------------------------------------------- |
223
- | X11 | the XID — the same number in any process |
224
- | Windows | a composition surface handle, valid in the host |
225
- | anything else | `null`, which is the capability |
226
-
227
- An embeddable window on Windows is therefore not an HWND at all: it is the
228
- same shared buffer a `<Frame>` pane draws into, and `createRoot({ win32: {
229
- paneHostPid } })` names the host when it is not the parent process. An app
230
- writes the same thing everywhere and passes the number out of band, as an XID
231
- is passed; what the host does with it is the host's business.
232
-
233
- OLE, if it is ever wanted, is a separate question about a host that cannot
234
- bind a surface, not a variation on this one.
235
-
236
- ## What must not change
237
-
238
- - `<Frame src>` stays one element that works on every backend, with the same
239
- `props`, `fallback` and `restart()`.
240
- - `useSupports('embedding')` stays the question an app asks before rendering
241
- `<foreign>`, and stays false here until something answers it.
242
- - `<window embeddable>` keeps meaning "created, not shown" everywhere.
243
- - Nothing grows a Windows-only name. A DComp surface handle is what
244
- `present()` carries here, the way an IOSurface id is what it carries on
245
- macOS — the seam names neither (AGENTS.md, "Vocabulary").
246
-
247
- ## Order of work
248
-
249
- 1. ~~**Spike the two-device question**~~ — **done.** The pane creates its own
250
- D3D11 device through the ordinary `start()`, and the handle crosses. It
251
- also turned up the two silent facts above.
252
- 2. ~~**`Win32PaneHost`**, against the existing three-method seam~~ — **done**,
253
- with `examples/frame.jsx` as the acceptance test and the table at the top
254
- of this page as its result. `src/win32/panewindow.js` is the pane's half,
255
- `src/win32/panehost.js` the host's, and `windows/src/pane.cc` the eight
256
- exports under them.
257
- 3. ~~**Do not fork a pane that cannot be shown.**~~ — **done.** `<Frame>` now
258
- asks before the fork: `createPaneHost` or `canEmbed`, either of which is a
259
- way to show a pane, and neither of which needs a process to find out.
260
- Backend-agnostic, and `test/frame-capability.test.js` holds all three
261
- answers.
262
- 4. ~~**Publish an embeddable window's buffer handle**~~ — **done.**
263
- `windowHandleOf(ref)` / `useWindowHandle(ref)`, answering an XID on X11
264
- and a surface handle here, with `test/window-handle.test.js` holding the
265
- difference. Not its HWND, which the table above rules out.
266
- 5. OLE, if a document embedding is actually wanted — and now a bigger
267
- question than it looked, because in-place activation renders into a child
268
- HWND and this backend cannot draw into one.
269
-
270
- Steps 1 and 2 are what made `<Frame src>` mean the same thing on three
271
- backends. Everything after them is new ground.
@@ -1,221 +0,0 @@
1
- # 3D graphics on Windows
2
-
3
- **Status: a report, with one measurement that changes the plan.** Written
4
- 2026-09-19 against react-x11 2.16.1 on Windows 11 build 26200, with a GeForce
5
- GTX 1080 Ti. [windows.md](windows.md) is the backend's design record and said
6
- `<glarea>` is ANGLE; this is the report that asked whether it has to be.
7
-
8
- ## What react-x11's 3D story actually is
9
-
10
- `<glarea>` is **a GL surface in the layout and nothing above it**
11
- ([gl.md](gl.md)). The scene graph — meshes, materials, lights,
12
- post-processing — is `@react-x11/components/three`, which brings its own
13
- reconciler and renders through that surface. So "3D support" on a backend is
14
- one question: can `onDraw` be handed a working GL context, and can what it
15
- draws reach the screen composited with everything else?
16
-
17
- Three answers ship today, and they are different APIs rather than three
18
- spellings of one:
19
-
20
- | | how it draws | shaders |
21
- | ------------------ | ------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------- |
22
- | **direct** (X11) | GLES 2 on the GPU via the `x11-dri` addon; frames reach the server as dma-buf over DRI3 + Present, or over Apple-DRI on XQuartz | yes |
23
- | **indirect** (X11) | GL commands encoded into the X connection | no — the protocol encodes no shader objects |
24
- | **Cocoa** | the same `x11-dri` CGL context, rendering into IOSurfaces presented as a `CALayer` sublayer | yes |
25
-
26
- The shape worth noticing: **`x11-dri` is not an X11 thing.** It is the GL
27
- addon, and the Cocoa backend uses it with the X-specific half swapped out.
28
- wayland.md's open question 4 already anticipated renaming it to a
29
- display-server-neutral GL addon; Windows is the third platform that would want
30
- it, which turns that open question into a decision worth taking.
31
-
32
- ## What Windows can give, measured
33
-
34
- `scripts/win32-probe.jsx`'s sibling in the bridge, `glProbe()`, creates a
35
- throwaway window, sets a pixel format, and asks. On this machine:
36
-
37
- ```
38
- vendor NVIDIA Corporation
39
- renderer NVIDIA GeForce GTX 1080 Ti/PCIe/SSE2
40
- version 4.6.0 NVIDIA 582.66 (core profile)
41
- glsl 4.60 NVIDIA
42
- maxTextureSize 32768
43
- extensions 392
44
- ```
45
-
46
- That is **OpenGL 4.6 core out of `opengl32.dll`, with nothing shipped**. It is
47
- the measurement that matters, because docs/windows.md's plan — ANGLE, two DLLs
48
- beside the app — was written on a Mac from Microsoft's documentation and from
49
- what the browsers do, and the browsers ship ANGLE for reasons that are theirs
50
- rather than ours: they must run identically on every driver, including the
51
- broken ones, and they sandbox the GPU process.
52
-
53
- The catch, and it is the whole reason ANGLE keeps a rung below: **without a
54
- vendor driver, `opengl32.dll` is the 1.1 software rasterizer.** That is a
55
- virtual machine with no passthrough, a CI runner, a fresh install before
56
- Windows Update has fetched the driver, and some remote-desktop sessions. GL
57
- 1.1 has no shaders, no framebuffer objects and no vertex buffers — it is not
58
- a degraded direct backend, it is the indirect backend's feature set with none
59
- of its portability.
60
-
61
- ## The recommendation: a ladder, not a choice
62
-
63
- The same discipline the desktop integrations follow — find the mechanism,
64
- report an honest feature set, degrade in the right direction:
65
-
66
- 1. **WGL** — `wglCreateContextAttribsARB` for a 3.3-or-better core context on
67
- the vendor's driver. This is the rung that answers on an ordinary desktop,
68
- and it ships nothing. GLSL, framebuffer objects, vertex buffers,
69
- instancing: everything the direct backend's contract promises.
70
- 2. **ANGLE** — EGL and GLES 2/3 over Direct3D 11, two DLLs beside the app, an
71
- `optionalDependency` the way `x11-dri` is. It answers where WGL's version
72
- probe comes back below 3.3, which is exactly the driverless case. D3D11
73
- has a WARP software device, so this rung works on a CI runner where WGL
74
- does not.
75
- 3. **nothing**, said plainly — `onError` with a message that names what is
76
- missing, which is what `<glarea>` already does on this backend today.
77
-
78
- Reversing 1 and 2 would be defensible and is what a browser would do. It is
79
- not what this project should do: shipping two DLLs to every app to avoid a
80
- driver check costs every Windows user the download and the packaging story,
81
- and react-x11's own rule is that `npm install` never compiles and rarely
82
- downloads.
83
-
84
- ## The hard part is presentation, not the context
85
-
86
- Getting a GL context is a day. Getting its output onto the screen _composited
87
- with the rest of the window_ is the design problem, and Windows makes it
88
- harder than either shipped backend does.
89
-
90
- The window's content is a DirectComposition surface, and a GL context cannot
91
- draw into one. Three ways out were on the table:
92
-
93
- - **A child HWND with its own context.** This looks like the X11 model
94
- exactly: a `<glarea>` there is a child X window on a GL visual, stacked
95
- above the parent's content, and `glnodes.js` already positions it from the
96
- parent's yoga rect and already knows that a child window cannot be
97
- overlapped by the parent's 2D content. The Windows version would be a child
98
- HWND with a pixel format and a WGL context, and `SwapBuffers` on its own
99
- DC. Cheapest by far, and it reuses the node layer's existing shape rather
100
- than inventing one.
101
-
102
- **It does not work, and this was built and measured before that was
103
- believed.** A window that presents through a DirectComposition target is
104
- shown by DWM _from that visual tree_; the window's redirection bitmap — the
105
- surface a child HWND's pixels go to — is not part of what is composited.
106
- So the child was invisible with the composition tree above it, invisible
107
- with the tree below it (`CreateTargetForHwnd`'s `topmost` argument, both
108
- ways), invisible with the window created without
109
- `WS_EX_NOREDIRECTIONBITMAP` so that a redirection bitmap existed at all,
110
- and invisible through a transparent hole punched in the 2D layer over its
111
- rect — that hole showed the desktop. The GL side was healthy throughout:
112
- with the 2D layer silenced, the same frame appeared in full.
113
-
114
- There is no ordering that puts the two together, because they are not two
115
- layers of one thing.
116
-
117
- - **`WGL_NV_DX_interop2`** — render GL into a texture shared with the D3D11
118
- device and put that on a composition visual. This is the one that
119
- composites properly. docs/windows.md flags it as "a driver's promise rather
120
- than the system's": it is present on NVIDIA and AMD, absent on some Intel
121
- configurations, and absent from WARP.
122
- - **ANGLE into a composition swapchain**, which is docs/windows.md's plan and
123
- is the only one that is both composited and universal — because ANGLE is
124
- already D3D11, so its output is a D3D texture by construction.
125
-
126
- **What shipped is the second**, not because it was the plan but because the
127
- first is not a route. Each surface owns a swap chain made for composition,
128
- shown by a visual of its own stacked over the window's 2D visual, and the GL
129
- context draws into a Direct3D texture the frame copies into that swap chain.
130
- What a child window is on X11 and a subview is on Cocoa, said in this
131
- platform's terms — and it composites with the window's alpha, which the child
132
- route would not have.
133
-
134
- The consequence for the plan below is that **G5 came first**. ANGLE is still
135
- the rung this needs for a machine with no vendor driver, for an Intel
136
- configuration without the interop extension, and for CI on WARP — and it is
137
- now a _fallback_ under a working composited path rather than the step before
138
- one.
139
-
140
- ## How much API is actually needed
141
-
142
- Measured against `@react-x11/components` at `3b82678`, the GL map renderer
143
- (`src/maps/gl/`) uses:
144
-
145
- - **64 functions** — `createBuffer`, `bindBuffer`, `bufferData`,
146
- `createVertexArray`, `bindVertexArray`, `vertexAttribPointer`,
147
- `vertexAttribDivisor`, `drawArraysInstanced`, `createShader`,
148
- `shaderSource`, `compileShader`, `createProgram`, `linkProgram`,
149
- `getUniformLocation`, the `uniform*` family, `createFramebuffer`,
150
- `framebufferTexture2D`, `renderbufferStorage`, `texImage2D`,
151
- `texSubImage2D`, `stencilOpSeparate`, `readPixels`, and the state setters.
152
- - **49 constants.**
153
-
154
- That is a **WebGL 2** shape — instancing and vertex array objects are in it —
155
- over what is, underneath, OpenGL 3.3 core. Sixty-four functions is a bounded,
156
- mechanical binding job rather than an open-ended one, and it is a fraction of
157
- WebGL 2's full surface (~150), because a renderer that knows what it is
158
- drawing does not use all of it.
159
-
160
- Note that two naming conventions exist in the tree and both are real:
161
- `gl.Enable` / `gl.CallList` (the indirect backend's immediate mode, which
162
- `examples/viewer3d.jsx` is written against) and `gl.enable` / `gl.bindBuffer`
163
- (the direct backend's WebGL shape). A Windows context implements the second.
164
-
165
- ## What `three` needs beyond that
166
-
167
- `@react-x11/components/three` renders through the same surface, so it needs
168
- the same context and no more — but it exercises more of the table than the map
169
- renderer does (cube maps, mipmaps, depth textures, more of the `uniform*`
170
- family). Budget the full WebGL 2 surface for it rather than the measured 64.
171
-
172
- ## The plan
173
-
174
- - **Phase G0 — the probe.** `glProbe()` in the bridge, reporting vendor,
175
- renderer, version, GLSL and whether a core context could be created. Done;
176
- the numbers above are its output. It is also what the ladder branches on.
177
- - **Phase G1 — a context. Done, though not as written.** A `<glarea>` was to
178
- become a child window with a WGL core context. It does not: a window
179
- presenting through DirectComposition has no redirection bitmap, so a child
180
- HWND composites nowhere and is invisible whatever it draws. The context is
181
- made against a hidden window and its output reaches the screen through G5
182
- below. `app.chooseGLConfig` answers, so `glnodes.js` takes its existing
183
- path. The context is an **ES** one (`WGL_CONTEXT_ES_PROFILE_BIT_EXT`),
184
- because the shaders these examples ship are GLSL ES and desktop GLSL 1.10
185
- rejects things like `mat3(mat4)` that ES 1.00 allows.
186
- - **Phase G2 — the table. Done.** Entry points above GL 1.1 come from
187
- `wglGetProcAddress`, which is per-context and must be resolved after the
188
- context is current. `maps-gl` draws, and so does the `configurator`
189
- example's laptop.
190
- - **Phase G3 — the overlay and the input. Done.** `gloverlay.js`'s panes
191
- over the surface — a DirectComposition visual with a premultiplied surface
192
- of its own (`src/win32/overlay.js`) — and the rule `glnodes.js` already
193
- states: the surface selects no pointer input, so the pointer reaches the
194
- tree by propagation. On this backend the surface is a visual rather than a
195
- window and selects no input at all, so the second half is free.
196
- - **Phase G5 — composited GL. Done, and first.** `WGL_NV_DX_interop2` onto a
197
- composition visual, because the child-window route above is not a route.
198
- It is what makes a `<glarea>` participate in the window's alpha instead of
199
- punching a hole in it — and a hole, as it turns out, would not have shown
200
- the surface anyway.
201
- - **Phase G4 — ANGLE as the lower rung**, for the driverless case, for an
202
- Intel configuration without the interop extension, and for CI on WARP,
203
- behind the same `chooseGLConfig` seam. Now a fallback under a working
204
- composited path rather than the step before one.
205
-
206
- ## Open questions
207
-
208
- 1. **`x11-dri`'s name, a third time.** windows.md's open question 9 and
209
- wayland.md's open question 4 are the same question, and Windows is the
210
- platform that settles it: the addon is the GL context and the WebGL-shaped
211
- table, and the display system is a parameter. A Windows context could be a
212
- fourth backend inside it rather than a second GL addon in `@windowkit`.
213
- 2. ~~**Whether the child HWND is acceptable as the shipped answer**~~ —
214
- **settled, and not by taste.** A child HWND is not an answer at all here:
215
- `WS_EX_NOREDIRECTIONBITMAP` is what lets the window present through
216
- DirectComposition, and DWM drops the redirection bitmap a child window
217
- would composite into. The question was whether a hole in the window was
218
- acceptable; it turned out a hole would not have shown the surface anyway.
219
- 3. **Which rung CI gets.** WARP gives D3D11 and therefore ANGLE, and gives
220
- WGL nothing. If `<glarea>` is to be tested at all on a GitHub runner, G4
221
- stops being optional.