react-x11 2.52.0 → 2.53.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 +1091 -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/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/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
package/src/wayland/app.js
CHANGED
|
@@ -30,6 +30,7 @@ import { releaseDevice } from './device.js';
|
|
|
30
30
|
import { WaylandSeat } from './seat.js';
|
|
31
31
|
import { WaylandBackendWindow } from './backendwindow.js';
|
|
32
32
|
import { WaylandSurface } from './surface.js';
|
|
33
|
+
import { releaseVideoFrame, writeVideoFrame } from './video.js';
|
|
33
34
|
import { InputRouter } from './input.js';
|
|
34
35
|
import { WaylandTouch } from './touch.js';
|
|
35
36
|
import { WaylandTablet } from './tablet.js';
|
|
@@ -440,6 +441,32 @@ export class WaylandApp extends EventEmitter {
|
|
|
440
441
|
return new WaylandSurface(this, options);
|
|
441
442
|
}
|
|
442
443
|
|
|
444
|
+
/**
|
|
445
|
+
* The formats a `VideoFrames` sink is cheapest in here
|
|
446
|
+
* (`preferredFormats`, src/videoframes.js): all three, YCbCr first. Every
|
|
447
|
+
* one goes up as textures as it is and is converted by a shader
|
|
448
|
+
* (src/wayland/video.js), and NV12 or I420 is less than half of BGRA's
|
|
449
|
+
* bytes on the way.
|
|
450
|
+
*/
|
|
451
|
+
videoFormats() {
|
|
452
|
+
return ['NV12', 'I420', 'BGRA'];
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
/**
|
|
456
|
+
* A `<video>`'s frame into `surface`, its drawn presentation, on the GPU:
|
|
457
|
+
* planes as textures, the matrix in a fragment shader. False where the
|
|
458
|
+
* frame cannot be — a size past the GPU's textures — and the node
|
|
459
|
+
* converts it in JavaScript instead (src/nodes/video.js).
|
|
460
|
+
*/
|
|
461
|
+
writeVideoFrame(surface, sink, frame) {
|
|
462
|
+
return writeVideoFrame(this, surface, sink, frame);
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
/** The node let `surface` go: its plane textures go too. */
|
|
466
|
+
releaseVideoFrame(surface) {
|
|
467
|
+
releaseVideoFrame(this, surface);
|
|
468
|
+
}
|
|
469
|
+
|
|
443
470
|
/**
|
|
444
471
|
* What the machine can do, as a promise like the X11 and Cocoa answers
|
|
445
472
|
* (src/glbackend.js chains on it): here the answer was settled when the
|
|
@@ -0,0 +1,376 @@
|
|
|
1
|
+
// `<video frames>` on Wayland: a frame's planes go up as textures as the
|
|
2
|
+
// decoder made them, and the colour conversion is a fragment shader drawing
|
|
3
|
+
// into the `<video>`'s own surface (design/architecture/video.md §5).
|
|
4
|
+
//
|
|
5
|
+
// The backend's 2D context is already a GLES rasterizer, so this is the GLX
|
|
6
|
+
// transport of §2.3 made native: no conversion in JavaScript for any format,
|
|
7
|
+
// and NV12 and I420 cross into the GPU at 1.5 bytes a pixel. Y is an R8
|
|
8
|
+
// texture; NV12's chroma one RG8 texture at half size, I420's two R8 ones;
|
|
9
|
+
// BGRA one RGBA8 texture read back to front. The shader applies the sink's
|
|
10
|
+
// matrix and range exactly as `frameToRGBA` does — no transfer function,
|
|
11
|
+
// because nothing else on this backend is colour-managed either — so a frame
|
|
12
|
+
// drawn here and one converted in JavaScript are the same picture, chroma
|
|
13
|
+
// apart: sampled bilinearly here, as a player does, and by nearest there.
|
|
14
|
+
//
|
|
15
|
+
// What it writes into is the surface the drawn presentation already keeps
|
|
16
|
+
// (src/nodes/video.js): a render target, which the window's context then
|
|
17
|
+
// composites at the picture's rect like any `drawImage`. Nothing lifts on
|
|
18
|
+
// Wayland — no subsurfaces, by the backend's own design — so this is the
|
|
19
|
+
// whole presentation.
|
|
20
|
+
//
|
|
21
|
+
// The GL is shared with every 2D context on the app (device.js): whatever
|
|
22
|
+
// the device's owner still has buffered is drawn before the target is
|
|
23
|
+
// written, the shader's vertex state lives in a vertex array of its own,
|
|
24
|
+
// and the device is released afterwards so the next context to draw puts
|
|
25
|
+
// its own state back. `UNPACK_ROW_LENGTH`, which a decoder's padded rows
|
|
26
|
+
// need, is set back to 0 for the uploads that assume it.
|
|
27
|
+
|
|
28
|
+
import { flushDevice, releaseDevice } from './device.js';
|
|
29
|
+
|
|
30
|
+
const VERT = `
|
|
31
|
+
attribute vec2 aPos;
|
|
32
|
+
attribute vec2 aUV;
|
|
33
|
+
varying vec2 vUV;
|
|
34
|
+
void main() {
|
|
35
|
+
vUV = aUV;
|
|
36
|
+
gl_Position = vec4(aPos, 0.0, 1.0);
|
|
37
|
+
}`;
|
|
38
|
+
|
|
39
|
+
const FRAG = `
|
|
40
|
+
#ifdef GL_FRAGMENT_PRECISION_HIGH
|
|
41
|
+
precision highp float;
|
|
42
|
+
#else
|
|
43
|
+
precision mediump float;
|
|
44
|
+
#endif
|
|
45
|
+
varying vec2 vUV;
|
|
46
|
+
uniform int uLayout;
|
|
47
|
+
uniform sampler2D uY;
|
|
48
|
+
uniform sampler2D uC;
|
|
49
|
+
uniform sampler2D uV;
|
|
50
|
+
uniform vec2 uChroma;
|
|
51
|
+
uniform mat3 uMatrix;
|
|
52
|
+
uniform vec3 uOffset;
|
|
53
|
+
void main() {
|
|
54
|
+
if (uLayout == 0) {
|
|
55
|
+
// BGRA bytes uploaded as RGBA: blue is in r. Opaque, whatever the
|
|
56
|
+
// fourth byte held.
|
|
57
|
+
gl_FragColor = vec4(texture2D(uY, vUV).bgr, 1.0);
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
float y = texture2D(uY, vUV).r;
|
|
61
|
+
vec2 cuv = vUV * uChroma;
|
|
62
|
+
vec2 c = uLayout == 1
|
|
63
|
+
? texture2D(uC, cuv).rg
|
|
64
|
+
: vec2(texture2D(uC, cuv).r, texture2D(uV, cuv).r);
|
|
65
|
+
vec3 rgb = uMatrix * (vec3(y, c) - uOffset);
|
|
66
|
+
gl_FragColor = vec4(clamp(rgb, 0.0, 1.0), 1.0);
|
|
67
|
+
}`;
|
|
68
|
+
|
|
69
|
+
const LAYOUT = { BGRA: 0, NV12: 1, I420: 2 };
|
|
70
|
+
|
|
71
|
+
// Kr and Kb, as src/videoframes.js has them
|
|
72
|
+
const MATRIX = {
|
|
73
|
+
bt709: [0.2126, 0.0722],
|
|
74
|
+
bt601: [0.299, 0.114],
|
|
75
|
+
bt2020: [0.2627, 0.0593],
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
const matrices = new Map();
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* The column-major mat3 and the offset that take normalized Y'CbCr to
|
|
82
|
+
* R'G'B' for a matrix and a range — the same numbers `frameToRGBA`'s tables
|
|
83
|
+
* are made of, in units of 1 rather than of 255.
|
|
84
|
+
*/
|
|
85
|
+
export function videoMatrix(colorSpace, range) {
|
|
86
|
+
const key = `${colorSpace}/${range}`;
|
|
87
|
+
let m = matrices.get(key);
|
|
88
|
+
if (m) return m;
|
|
89
|
+
const [kr, kb] = MATRIX[colorSpace];
|
|
90
|
+
const kg = 1 - kr - kb;
|
|
91
|
+
const full = range === 'full';
|
|
92
|
+
const ys = full ? 1 : 255 / 219;
|
|
93
|
+
const cs = full ? 1 : 255 / 224;
|
|
94
|
+
m = {
|
|
95
|
+
matrix: Float32Array.of(
|
|
96
|
+
// the column Y' multiplies
|
|
97
|
+
ys,
|
|
98
|
+
ys,
|
|
99
|
+
ys,
|
|
100
|
+
// Cb
|
|
101
|
+
0,
|
|
102
|
+
-cs * ((2 * kb * (1 - kb)) / kg),
|
|
103
|
+
cs * 2 * (1 - kb),
|
|
104
|
+
// Cr
|
|
105
|
+
cs * 2 * (1 - kr),
|
|
106
|
+
-cs * ((2 * kr * (1 - kr)) / kg),
|
|
107
|
+
0,
|
|
108
|
+
),
|
|
109
|
+
offset: Float32Array.of(full ? 0 : 16 / 255, 128 / 255, 128 / 255),
|
|
110
|
+
};
|
|
111
|
+
matrices.set(key, m);
|
|
112
|
+
return m;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/** The planes of a frame as textures: format, size and the bytes per texel
|
|
116
|
+
* `UNPACK_ROW_LENGTH` counts a decoder's stride in. */
|
|
117
|
+
function planesOf(gl, format, width, height) {
|
|
118
|
+
const cw = Math.ceil(width / 2);
|
|
119
|
+
const ch = Math.ceil(height / 2);
|
|
120
|
+
const r8 = { internal: gl.R8, format: gl.RED, texel: 1 };
|
|
121
|
+
switch (format) {
|
|
122
|
+
case 'NV12':
|
|
123
|
+
return [
|
|
124
|
+
{ ...r8, width, height },
|
|
125
|
+
{ internal: gl.RG8, format: gl.RG, texel: 2, width: cw, height: ch },
|
|
126
|
+
];
|
|
127
|
+
case 'I420':
|
|
128
|
+
return [
|
|
129
|
+
{ ...r8, width, height },
|
|
130
|
+
{ ...r8, width: cw, height: ch },
|
|
131
|
+
{ ...r8, width: cw, height: ch },
|
|
132
|
+
];
|
|
133
|
+
case 'BGRA':
|
|
134
|
+
return [{ internal: gl.RGBA, format: gl.RGBA, texel: 4, width, height }];
|
|
135
|
+
}
|
|
136
|
+
return null;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/** One per GL device: the program, and the quad it draws. */
|
|
140
|
+
const programs = new WeakMap();
|
|
141
|
+
|
|
142
|
+
function programFor(gl) {
|
|
143
|
+
let p = programs.get(gl);
|
|
144
|
+
if (p) return p;
|
|
145
|
+
const vs = compile(gl, gl.VERTEX_SHADER, VERT, 'video vertex');
|
|
146
|
+
let fs;
|
|
147
|
+
try {
|
|
148
|
+
fs = compile(gl, gl.FRAGMENT_SHADER, FRAG, 'video fragment');
|
|
149
|
+
} catch (err) {
|
|
150
|
+
gl.deleteShader(vs);
|
|
151
|
+
throw err;
|
|
152
|
+
}
|
|
153
|
+
const program = gl.createProgram();
|
|
154
|
+
gl.attachShader(program, vs);
|
|
155
|
+
gl.attachShader(program, fs);
|
|
156
|
+
gl.linkProgram(program);
|
|
157
|
+
const linked = gl.getProgramParameter(program, gl.LINK_STATUS);
|
|
158
|
+
const log = linked ? '' : gl.getProgramInfoLog(program);
|
|
159
|
+
gl.deleteShader(vs);
|
|
160
|
+
gl.deleteShader(fs);
|
|
161
|
+
if (!linked) {
|
|
162
|
+
gl.deleteProgram(program);
|
|
163
|
+
throw new Error(`video program failed to link: ${log}`);
|
|
164
|
+
}
|
|
165
|
+
const u = (name) => gl.getUniformLocation(program, name);
|
|
166
|
+
const aPos = gl.getAttribLocation(program, 'aPos');
|
|
167
|
+
const aUV = gl.getAttribLocation(program, 'aUV');
|
|
168
|
+
// A strip over the whole target. The 2D context draws device row 0 at the
|
|
169
|
+
// top of clip space, so the frame's first row — texture row 0 — goes
|
|
170
|
+
// there: v = 0 at clip y = +1.
|
|
171
|
+
const buffer = gl.createBuffer();
|
|
172
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
|
|
173
|
+
gl.bufferData(
|
|
174
|
+
gl.ARRAY_BUFFER,
|
|
175
|
+
Float32Array.of(-1, 1, 0, 0, -1, -1, 0, 1, 1, 1, 1, 0, 1, -1, 1, 1),
|
|
176
|
+
gl.STATIC_DRAW,
|
|
177
|
+
);
|
|
178
|
+
// its own vertex array, so the 2D contexts' attribute state — on the
|
|
179
|
+
// default one, re-pointed every batch — is never this program's
|
|
180
|
+
const vao = gl.createVertexArray();
|
|
181
|
+
gl.bindVertexArray(vao);
|
|
182
|
+
gl.enableVertexAttribArray(aPos);
|
|
183
|
+
gl.vertexAttribPointer(aPos, 2, gl.FLOAT, false, 16, 0);
|
|
184
|
+
gl.enableVertexAttribArray(aUV);
|
|
185
|
+
gl.vertexAttribPointer(aUV, 2, gl.FLOAT, false, 16, 8);
|
|
186
|
+
gl.bindVertexArray(null);
|
|
187
|
+
p = {
|
|
188
|
+
program,
|
|
189
|
+
buffer,
|
|
190
|
+
vao,
|
|
191
|
+
maxSize: gl.getParameter(gl.MAX_TEXTURE_SIZE),
|
|
192
|
+
loc: {
|
|
193
|
+
uLayout: u('uLayout'),
|
|
194
|
+
uY: u('uY'),
|
|
195
|
+
uC: u('uC'),
|
|
196
|
+
uV: u('uV'),
|
|
197
|
+
uChroma: u('uChroma'),
|
|
198
|
+
uMatrix: u('uMatrix'),
|
|
199
|
+
uOffset: u('uOffset'),
|
|
200
|
+
},
|
|
201
|
+
};
|
|
202
|
+
programs.set(gl, p);
|
|
203
|
+
return p;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/** Each surface's plane textures, kept across frames of one size. */
|
|
207
|
+
const uploads = new WeakMap();
|
|
208
|
+
|
|
209
|
+
function texturesFor(gl, surface, format, width, height) {
|
|
210
|
+
let kept = uploads.get(surface);
|
|
211
|
+
if (
|
|
212
|
+
kept &&
|
|
213
|
+
kept.format === format &&
|
|
214
|
+
kept.width === width &&
|
|
215
|
+
kept.height === height
|
|
216
|
+
) {
|
|
217
|
+
return kept;
|
|
218
|
+
}
|
|
219
|
+
if (kept) deleteTextures(gl, kept);
|
|
220
|
+
const planes = planesOf(gl, format, width, height);
|
|
221
|
+
kept = {
|
|
222
|
+
format,
|
|
223
|
+
width,
|
|
224
|
+
height,
|
|
225
|
+
planes,
|
|
226
|
+
textures: planes.map(() => gl.createTexture()),
|
|
227
|
+
fresh: true,
|
|
228
|
+
};
|
|
229
|
+
uploads.set(surface, kept);
|
|
230
|
+
return kept;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
function deleteTextures(gl, kept) {
|
|
234
|
+
for (const tex of kept.textures) gl.deleteTexture(tex);
|
|
235
|
+
kept.textures = [];
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* A plane's rows at a stride `UNPACK_ROW_LENGTH` cannot say — one that is
|
|
240
|
+
* not a whole number of texels — packed tight. Every decoder pads to whole
|
|
241
|
+
* pixels, so this is the case that should never come.
|
|
242
|
+
*/
|
|
243
|
+
function tight(data, stride, rowBytes, rows) {
|
|
244
|
+
const out = new Uint8Array(rowBytes * rows);
|
|
245
|
+
for (let r = 0; r < rows; r++) {
|
|
246
|
+
out.set(data.subarray(r * stride, r * stride + rowBytes), r * rowBytes);
|
|
247
|
+
}
|
|
248
|
+
return out;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* `frame` from `sink` into `surface` (a WaylandSurface at the frame's size).
|
|
253
|
+
* False — and the frame is converted in JavaScript instead — where the
|
|
254
|
+
* surface is not one of this backend's, the app has no GL, or the frame is
|
|
255
|
+
* larger than a texture can be.
|
|
256
|
+
*/
|
|
257
|
+
export function writeVideoFrame(app, surface, sink, frame) {
|
|
258
|
+
const gl = app?.gl;
|
|
259
|
+
const target = surface?.target;
|
|
260
|
+
if (!gl || !target?.fbo || target.destroyed) return false;
|
|
261
|
+
if (!(sink.format in LAYOUT)) return false;
|
|
262
|
+
app.makeCurrent();
|
|
263
|
+
const p = programFor(gl);
|
|
264
|
+
if (sink.width > p.maxSize || sink.height > p.maxSize) return false;
|
|
265
|
+
// Anything a context still has buffered is drawn first: one of its draws
|
|
266
|
+
// may read this target, and must read the frame it was given (device.js).
|
|
267
|
+
flushDevice(gl);
|
|
268
|
+
const kept = texturesFor(gl, surface, sink.format, sink.width, sink.height);
|
|
269
|
+
try {
|
|
270
|
+
gl.pixelStorei(gl.UNPACK_ALIGNMENT, 1);
|
|
271
|
+
kept.planes.forEach((plane, i) => {
|
|
272
|
+
const rowBytes = plane.width * plane.texel;
|
|
273
|
+
const stride = frame.strides[i];
|
|
274
|
+
let data = frame.planes[i];
|
|
275
|
+
let rowLength = 0;
|
|
276
|
+
if (stride !== rowBytes) {
|
|
277
|
+
if (stride % plane.texel === 0) rowLength = stride / plane.texel;
|
|
278
|
+
else data = tight(data, stride, rowBytes, plane.height);
|
|
279
|
+
}
|
|
280
|
+
gl.pixelStorei(gl.UNPACK_ROW_LENGTH, rowLength);
|
|
281
|
+
gl.activeTexture(gl.TEXTURE0 + i);
|
|
282
|
+
gl.bindTexture(gl.TEXTURE_2D, kept.textures[i]);
|
|
283
|
+
if (kept.fresh) {
|
|
284
|
+
// Y and BGRA meet the target texel for texel; chroma is half size,
|
|
285
|
+
// and read between its samples as a player reads it
|
|
286
|
+
const filter = i === 0 ? gl.NEAREST : gl.LINEAR;
|
|
287
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, filter);
|
|
288
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, filter);
|
|
289
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
|
|
290
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
|
|
291
|
+
gl.texImage2D(
|
|
292
|
+
gl.TEXTURE_2D,
|
|
293
|
+
0,
|
|
294
|
+
plane.internal,
|
|
295
|
+
plane.width,
|
|
296
|
+
plane.height,
|
|
297
|
+
0,
|
|
298
|
+
plane.format,
|
|
299
|
+
gl.UNSIGNED_BYTE,
|
|
300
|
+
data,
|
|
301
|
+
);
|
|
302
|
+
} else {
|
|
303
|
+
gl.texSubImage2D(
|
|
304
|
+
gl.TEXTURE_2D,
|
|
305
|
+
0,
|
|
306
|
+
0,
|
|
307
|
+
0,
|
|
308
|
+
plane.width,
|
|
309
|
+
plane.height,
|
|
310
|
+
plane.format,
|
|
311
|
+
gl.UNSIGNED_BYTE,
|
|
312
|
+
data,
|
|
313
|
+
);
|
|
314
|
+
}
|
|
315
|
+
});
|
|
316
|
+
kept.fresh = false;
|
|
317
|
+
|
|
318
|
+
target.bind();
|
|
319
|
+
gl.disable(gl.BLEND);
|
|
320
|
+
gl.disable(gl.SCISSOR_TEST);
|
|
321
|
+
gl.disable(gl.STENCIL_TEST);
|
|
322
|
+
gl.disable(gl.DEPTH_TEST);
|
|
323
|
+
gl.disable(gl.CULL_FACE);
|
|
324
|
+
gl.colorMask(true, true, true, true);
|
|
325
|
+
gl.useProgram(p.program);
|
|
326
|
+
const { loc } = p;
|
|
327
|
+
gl.uniform1i(loc.uLayout, LAYOUT[sink.format]);
|
|
328
|
+
gl.uniform1i(loc.uY, 0);
|
|
329
|
+
gl.uniform1i(loc.uC, 1);
|
|
330
|
+
gl.uniform1i(loc.uV, 2);
|
|
331
|
+
// full-size texture coordinates onto a chroma plane rounded up: an odd
|
|
332
|
+
// frame's last chroma column covers one picture column, not two
|
|
333
|
+
gl.uniform2f(
|
|
334
|
+
loc.uChroma,
|
|
335
|
+
sink.width / (2 * Math.ceil(sink.width / 2)),
|
|
336
|
+
sink.height / (2 * Math.ceil(sink.height / 2)),
|
|
337
|
+
);
|
|
338
|
+
const { matrix, offset } = videoMatrix(sink.colorSpace, sink.range);
|
|
339
|
+
gl.uniformMatrix3fv(loc.uMatrix, false, matrix);
|
|
340
|
+
gl.uniform3fv(loc.uOffset, offset);
|
|
341
|
+
gl.bindVertexArray(p.vao);
|
|
342
|
+
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
|
|
343
|
+
gl.bindVertexArray(null);
|
|
344
|
+
} finally {
|
|
345
|
+
gl.pixelStorei(gl.UNPACK_ROW_LENGTH, 0);
|
|
346
|
+
gl.activeTexture(gl.TEXTURE0);
|
|
347
|
+
// the program, the blend and the framebuffer were the 2D contexts' to
|
|
348
|
+
// say: the next one to draw says them again
|
|
349
|
+
releaseDevice(gl);
|
|
350
|
+
app.rebindWindowTarget?.();
|
|
351
|
+
}
|
|
352
|
+
return true;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
/** `surface` is let go: its plane textures with it. */
|
|
356
|
+
export function releaseVideoFrame(app, surface) {
|
|
357
|
+
const kept = uploads.get(surface);
|
|
358
|
+
if (!kept) return;
|
|
359
|
+
uploads.delete(surface);
|
|
360
|
+
const gl = app?.gl;
|
|
361
|
+
if (!gl) return;
|
|
362
|
+
app.makeCurrent();
|
|
363
|
+
deleteTextures(gl, kept);
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
function compile(gl, type, src, what) {
|
|
367
|
+
const sh = gl.createShader(type);
|
|
368
|
+
gl.shaderSource(sh, src);
|
|
369
|
+
gl.compileShader(sh);
|
|
370
|
+
if (!gl.getShaderParameter(sh, gl.COMPILE_STATUS)) {
|
|
371
|
+
const log = gl.getShaderInfoLog(sh);
|
|
372
|
+
gl.deleteShader(sh);
|
|
373
|
+
throw new Error(`${what} shader failed to compile: ${log}`);
|
|
374
|
+
}
|
|
375
|
+
return sh;
|
|
376
|
+
}
|
package/src/win32/a11y.js
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
// property reads — so answering from JS would pace the screen reader by the
|
|
8
8
|
// application's busiest moment. The bridge therefore *pushes*, and
|
|
9
9
|
// `windows/src/uia.cc` answers from its copy. The reasoning is
|
|
10
|
-
//
|
|
10
|
+
// design/windows.md §"Accessibility: UI Automation"; what matters here is the
|
|
11
11
|
// consequence: **this file's job is a diff, not an answer.**
|
|
12
12
|
//
|
|
13
13
|
// Everything it pushes comes from `src/a11y.js`, which is pure functions over
|
package/src/win32/app.js
CHANGED
|
@@ -362,7 +362,7 @@ export class Win32App {
|
|
|
362
362
|
// `embeddable` is "created, not shown; somebody else will place me", and
|
|
363
363
|
// on this backend that cannot be a window: a composition target stops
|
|
364
364
|
// presenting the moment its window becomes a child, so an HWND is not
|
|
365
|
-
// something another process can take (
|
|
365
|
+
// something another process can take (design/windows-embedding.md). What a
|
|
366
366
|
// host can take is the **buffer**, so an embeddable window here is the
|
|
367
367
|
// same shared surface a `<Frame>` pane draws into — whether this process
|
|
368
368
|
// was forked as a pane or is a guest somebody else started.
|
|
@@ -825,7 +825,7 @@ export class Win32App {
|
|
|
825
825
|
break;
|
|
826
826
|
}
|
|
827
827
|
case 'dpichanged':
|
|
828
|
-
// The renderer cannot re-scale a live window yet (
|
|
828
|
+
// The renderer cannot re-scale a live window yet (design/windows.md
|
|
829
829
|
// §Layout, open question 8), so this is recorded and not acted on.
|
|
830
830
|
wnd.emit('statechange', { scale: event.a / 96 });
|
|
831
831
|
break;
|
package/src/win32/fonts.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// The Windows text engine: DirectWrite behind the same `app.fonts` contract
|
|
2
2
|
// ntk's FontManager answers on X11 and CoreText answers on macOS. The renderer
|
|
3
|
-
// touches exactly this surface (
|
|
3
|
+
// touches exactly this surface (design/windows.md §"Text"):
|
|
4
4
|
//
|
|
5
5
|
// fonts.layout(spans, base, { maxWidth, align, lineHeight, maxLines,
|
|
6
6
|
// overflow, direction })
|
package/src/win32/glarea.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// `<glarea>` on Windows: a WGL core context, composited as a visual.
|
|
2
2
|
//
|
|
3
|
-
//
|
|
3
|
+
// design/windows-gl.md is the report behind this, and the route is the X11
|
|
4
4
|
// model rather than ANGLE — a GL surface covers its rect and the tree draws
|
|
5
5
|
// under it, positioned from the parent's yoga rect. src/glnodes.js already
|
|
6
6
|
// expects exactly that, including the rule that the surface selects no
|
|
@@ -58,7 +58,7 @@ export class Win32GlWindow {
|
|
|
58
58
|
* vendor driver: opengl32.dll is then the 1.1 software rasterizer, with no
|
|
59
59
|
* shaders and no framebuffer objects — which is not a degraded direct
|
|
60
60
|
* backend but the indirect one's feature set without its portability, and
|
|
61
|
-
* is
|
|
61
|
+
* is design/windows-gl.md's rung 2 rather than this one.
|
|
62
62
|
*/
|
|
63
63
|
_onReady(ok, why = 0) {
|
|
64
64
|
if (!ok) {
|
|
@@ -79,7 +79,7 @@ export class Win32GlWindow {
|
|
|
79
79
|
'error',
|
|
80
80
|
new Error(
|
|
81
81
|
`react-x11: no OpenGL surface — ${REASONS[why] ?? 'the bridge refused it'}. ` +
|
|
82
|
-
'
|
|
82
|
+
'design/windows-gl.md rung 2 (ANGLE) covers this case and is not built yet.',
|
|
83
83
|
),
|
|
84
84
|
);
|
|
85
85
|
return;
|
|
@@ -540,7 +540,7 @@ export function installGl(app) {
|
|
|
540
540
|
throw new Error(
|
|
541
541
|
'react-x11: <glarea> has no GL surface — this machine has no vendor ' +
|
|
542
542
|
'OpenGL driver, so only the 1.1 software rasterizer is available, ' +
|
|
543
|
-
'which has no shaders.
|
|
543
|
+
'which has no shaders. design/windows-gl.md rung 2 (ANGLE) covers ' +
|
|
544
544
|
'this case and is not built yet.',
|
|
545
545
|
);
|
|
546
546
|
});
|
package/src/win32/overlay.js
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
// leave transparent shows the GL frame, and a translucent fill, an
|
|
14
14
|
// antialiased edge or a shadow blends with it. Text on it is grayscale
|
|
15
15
|
// rather than ClearType, which Direct2D does on any surface with alpha
|
|
16
|
-
// (
|
|
16
|
+
// (design/windows.md §Text).
|
|
17
17
|
//
|
|
18
18
|
// Drawn like the window it lives in (src/win32/window.js): a DirectComposition
|
|
19
19
|
// surface hands out a context per `BeginDraw`, valid until `EndDraw`, so the
|
package/src/win32/panewindow.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// The pane process's "window" on the Windows backend —
|
|
1
|
+
// The pane process's "window" on the Windows backend — design/frame.md's child
|
|
2
2
|
// half, with the composition engine standing in for the X server.
|
|
3
3
|
//
|
|
4
4
|
// On X11 a pane renders into a real window of its own and the host reparents
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
// the pane makes a DirectComposition surface handle, hangs a composition
|
|
7
7
|
// swapchain off it, and draws through the same verb table a window draws
|
|
8
8
|
// through; the host opens that handle and gives it to a visual of its own
|
|
9
|
-
// (src/win32/panehost.js). See
|
|
9
|
+
// (src/win32/panehost.js). See design/windows-embedding.md for why this rather
|
|
10
10
|
// than a shared DXGI texture — in one line, nothing here has to synchronise.
|
|
11
11
|
//
|
|
12
12
|
// **The present is the hand-off.** Once the host holds the handle the
|
|
@@ -360,7 +360,7 @@ export class Win32PaneWindow {
|
|
|
360
360
|
* On X11 that is the window's own id, because an XID means the same thing
|
|
361
361
|
* in every process on the display. Windows has no such number: a window
|
|
362
362
|
* cannot be embedded at all, because a composition target stops presenting
|
|
363
|
-
* the moment its window becomes a child (
|
|
363
|
+
* the moment its window becomes a child (design/windows-embedding.md, with
|
|
364
364
|
* the measurements). What crosses instead is the **buffer** — the same
|
|
365
365
|
* composition surface handle a `<Frame>` pane publishes — and the host
|
|
366
366
|
* binds it to a visual of its own rather than reparenting anything.
|
package/src/win32/shell.js
CHANGED
|
@@ -236,7 +236,7 @@ async function badgePixels(app, label, accent) {
|
|
|
236
236
|
* What this rung cannot do, and says so by not implementing it: actions,
|
|
237
237
|
* replacing a notification in place, and an icon of the app's own. Those want
|
|
238
238
|
* the Windows App SDK's notification manager and an AppUserModelID the system
|
|
239
|
-
* knows —
|
|
239
|
+
* knows — design/windows.md §"The desktop around the app". Until then the
|
|
240
240
|
* balloon is shown by the shell in the same place, with the same sound, and
|
|
241
241
|
* kept in the same action centre.
|
|
242
242
|
*/
|
package/src/win32/window.js
CHANGED
|
@@ -252,7 +252,7 @@ export class Win32Window {
|
|
|
252
252
|
}
|
|
253
253
|
|
|
254
254
|
/** The size limits Windows asks for synchronously in WM_GETMINMAXINFO, so
|
|
255
|
-
* they are pushed ahead rather than answered on demand —
|
|
255
|
+
* they are pushed ahead rather than answered on demand — design/windows.md
|
|
256
256
|
* "What Windows asks synchronously, and JS can know in advance". */
|
|
257
257
|
setSizeHints(hints = {}) {
|
|
258
258
|
this._native.setSizeHints?.(this.id, hints);
|
|
@@ -691,7 +691,7 @@ export class Win32Window {
|
|
|
691
691
|
}
|
|
692
692
|
|
|
693
693
|
/** A DirectComposition surface is write-only, so a window's pixels are not
|
|
694
|
-
* readable the way an IOSurface's or an X drawable's are.
|
|
694
|
+
* readable the way an IOSurface's or an X drawable's are. design/windows.md
|
|
695
695
|
* answers this with PrintWindow once the commit has completed; not bound
|
|
696
696
|
* yet, and it says so rather than answering with something wrong. */
|
|
697
697
|
/**
|
package/src/windowid.js
CHANGED
|
@@ -78,7 +78,7 @@ export function windowOf(target) {
|
|
|
78
78
|
* - **Windows** — a **composition surface handle**, already valid in the
|
|
79
79
|
* host process. A window cannot be embedded here at all: a composition
|
|
80
80
|
* target stops presenting the moment its window becomes a child, measured
|
|
81
|
-
* in
|
|
81
|
+
* in design/windows-embedding.md. So what crosses is the buffer, and the
|
|
82
82
|
* host binds it to a visual of its own instead of reparenting. The host is
|
|
83
83
|
* the parent process by default — `createRoot({ win32: { paneHostPid } })`
|
|
84
84
|
* when it is not.
|