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.
- package/README.md +10 -10
- package/docs/README.md +160 -314
- package/docs/accessibility.md +176 -229
- package/docs/appearance.md +77 -176
- package/docs/click-to-component.md +18 -51
- package/docs/clipboard.md +40 -59
- package/docs/components.md +518 -1036
- package/docs/dbus.md +61 -133
- package/docs/debugging.md +152 -245
- package/docs/desktop-calendar.md +51 -88
- package/docs/desktop.md +222 -347
- package/docs/devtools.md +50 -167
- package/docs/drag-and-drop.md +126 -204
- package/docs/ecosystem/animation.md +91 -125
- package/docs/ecosystem/data-fetching.md +38 -53
- package/docs/ecosystem/dev-tooling.md +159 -202
- package/docs/ecosystem/forms.md +61 -97
- package/docs/ecosystem/headless.md +64 -89
- package/docs/ecosystem/i18n.md +18 -21
- package/docs/ecosystem/icons.md +93 -126
- package/docs/ecosystem/layout.md +42 -52
- package/docs/ecosystem/routing.md +18 -29
- package/docs/ecosystem/state.md +71 -88
- package/docs/ecosystem/testing.md +131 -157
- package/docs/ecosystem/theming.md +119 -165
- package/docs/ecosystem.md +145 -179
- package/docs/elements.md +1096 -1774
- package/docs/embedding.md +83 -132
- package/docs/events.md +412 -695
- package/docs/extending.md +784 -1326
- package/docs/eyedropper.md +63 -122
- package/docs/filedialog.md +71 -107
- package/docs/frame.md +195 -339
- package/docs/gl.md +65 -62
- package/docs/globalmenu.md +63 -146
- package/docs/macos.md +531 -3234
- package/docs/notifications.md +33 -47
- package/docs/packaging.md +166 -309
- package/docs/permissions.md +9 -14
- package/docs/react-features.md +81 -106
- package/docs/remote.md +32 -76
- package/docs/scale.md +72 -186
- package/docs/styling.md +329 -547
- package/docs/system.md +200 -280
- package/docs/testing.md +63 -83
- package/docs/typescript.md +6 -48
- package/docs/uri-schemes.md +67 -107
- package/docs/wayland-backend.md +200 -459
- package/docs/windows-integrations.md +75 -131
- package/docs/windows.md +343 -1367
- package/package.json +3 -2
- package/src/Reconciler.js +2 -2
- package/src/a11y.js +1 -1
- package/src/backend/context2d.js +3 -3
- package/src/bootstrap.js +1 -1
- package/src/cocoa/app.js +6 -6
- package/src/cocoa/bezels.js +1 -1
- package/src/cocoa/fonts.js +1 -1
- package/src/cocoa/globalmenu.js +1 -1
- package/src/cocoa/keymap.js +1 -1
- package/src/cocoa/panewindow.js +1 -1
- package/src/cocoa/player.js +1 -1
- package/src/cocoa/presenter.js +2 -2
- package/src/cocoa/promotion.js +1 -1
- package/src/cocoa/relaunch.js +1 -1
- package/src/cocoa/threaded.js +1 -1
- package/src/cocoa/video.js +1 -1
- package/src/cocoa/window.js +4 -4
- package/src/components/Menu.js +7 -2
- package/src/components/Switch.js +1 -1
- package/src/components/native.js +1 -1
- package/src/dnd.js +2 -2
- package/src/glnodes.js +1 -1
- package/src/grid.js +1 -1
- package/src/imagedecode-worker.js +32 -0
- package/src/imagedecode.js +140 -2
- package/src/layouts.js +1 -1
- package/src/nodes/animation.js +1 -1
- package/src/nodes/invalidate.js +1 -1
- package/src/nodes/layouthost.js +1 -1
- package/src/nodes/video.js +19 -8
- package/src/nodes/window/droptarget.js +1 -1
- package/src/ntk.d.ts +12 -0
- package/src/pacing.js +1 -1
- package/src/refresh/index.js +2 -1
- package/src/types/video.d.ts +5 -2
- package/src/videobackend.js +28 -0
- package/src/videoframes.js +29 -16
- package/src/wayland/app.js +27 -0
- package/src/wayland/video.js +376 -0
- package/src/win32/a11y.js +1 -1
- package/src/win32/app.js +2 -2
- package/src/win32/fonts.js +1 -1
- package/src/win32/glarea.js +4 -4
- package/src/win32/overlay.js +1 -1
- package/src/win32/panewindow.js +3 -3
- package/src/win32/shell.js +1 -1
- package/src/win32/window.js +2 -2
- package/src/windowid.js +1 -1
- package/src/xvideo.js +189 -0
- package/docs/architecture/accessibility.md +0 -470
- package/docs/architecture/animation.md +0 -707
- package/docs/architecture/container-queries.md +0 -525
- package/docs/architecture/custom-layout.md +0 -334
- package/docs/architecture/drag-and-drop.md +0 -941
- package/docs/architecture/element-layer-contents.md +0 -622
- package/docs/architecture/frame-pacing.md +0 -255
- package/docs/architecture/grid-layout.md +0 -358
- package/docs/architecture/protocol-efficiency.md +0 -357
- package/docs/architecture/video.md +0 -1149
- package/docs/architecture/windowed-regions.md +0 -854
- package/docs/glx.md +0 -137
- package/docs/wayland.md +0 -545
- package/docs/windows-embedding.md +0 -271
- 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.
|
package/docs/windows-gl.md
DELETED
|
@@ -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.
|