@desklink/host 0.2.0 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/NOTICE +26 -0
- package/README.md +134 -56
- package/bin/desklink-host.mjs +13 -5
- package/dist/bridge.d.ts +1 -0
- package/dist/bridge.d.ts.map +1 -1
- package/dist/bridge.js +12 -4
- package/dist/bridge.js.map +1 -1
- package/dist/engineProcess.d.ts +6 -1
- package/dist/engineProcess.d.ts.map +1 -1
- package/dist/engineProcess.js +35 -4
- package/dist/engineProcess.js.map +1 -1
- package/dist/protocol.d.ts +58 -0
- package/dist/protocol.d.ts.map +1 -1
- package/dist/protocol.js.map +1 -1
- package/dist/resolveEngine.d.ts +7 -1
- package/dist/resolveEngine.d.ts.map +1 -1
- package/dist/resolveEngine.js +66 -14
- package/dist/resolveEngine.js.map +1 -1
- package/docs/BOUNDARY.md +15 -7
- package/docs/PROTOCOL.md +267 -27
- package/engine/Cargo.lock +47 -2
- package/engine/Cargo.toml +18 -13
- package/engine/build.rs +86 -4
- package/engine/native/agent_overlay_mac.m +51 -4
- package/engine/native/agent_overlay_wayland.c +145 -12
- package/engine/native/agent_overlay_x11.c +72 -0
- package/engine/native/nvenc_shim.c +453 -0
- package/engine/native/vaapi_shim.c +727 -0
- package/engine/native/vpx_shim.c +54 -4
- package/engine/native/vt_shim.c +448 -0
- package/engine/src/capture.rs +167 -58
- package/engine/src/capture_mac.rs +24 -14
- package/engine/src/capture_win.rs +23 -0
- package/engine/src/clipboard_win.rs +8 -0
- package/engine/src/convert.rs +286 -61
- package/engine/src/cursor.rs +118 -0
- package/engine/src/encoder.rs +122 -13
- package/engine/src/h264.rs +188 -0
- package/engine/src/h264_nvenc.rs +308 -0
- package/engine/src/h264_openh264.rs +735 -0
- package/engine/src/h264_vaapi.rs +263 -0
- package/engine/src/h264_vt.rs +368 -0
- package/engine/src/indicator.rs +104 -3
- package/engine/src/input_win.rs +91 -0
- package/engine/src/keymap_win.rs +39 -0
- package/engine/src/mac.rs +34 -9
- package/engine/src/main.rs +186 -26
- package/engine/src/peer.rs +466 -28
- package/engine/src/portal.rs +42 -4
- package/engine/src/portal_win.rs +37 -0
- package/engine/src/protocol.rs +64 -2
- package/engine/src/session.rs +1252 -105
- package/engine/src/win.rs +110 -0
- package/engine/src/x11.rs +225 -9
- package/engine/src/x11_mac.rs +12 -0
- package/engine/vendor/libva/COPYING +19 -0
- package/engine/vendor/libva/va/va.h +5450 -0
- package/engine/vendor/libva/va/va_dec_av1.h +695 -0
- package/engine/vendor/libva/va/va_dec_hevc.h +616 -0
- package/engine/vendor/libva/va/va_dec_jpeg.h +157 -0
- package/engine/vendor/libva/va/va_dec_vp8.h +249 -0
- package/engine/vendor/libva/va/va_dec_vp9.h +312 -0
- package/engine/vendor/libva/va/va_dec_vvc.h +688 -0
- package/engine/vendor/libva/va/va_drm.h +61 -0
- package/engine/vendor/libva/va/va_enc_av1.h +1010 -0
- package/engine/vendor/libva/va/va_enc_h264.h +659 -0
- package/engine/vendor/libva/va/va_enc_hevc.h +958 -0
- package/engine/vendor/libva/va/va_enc_jpeg.h +162 -0
- package/engine/vendor/libva/va/va_enc_mpeg2.h +308 -0
- package/engine/vendor/libva/va/va_enc_vp8.h +350 -0
- package/engine/vendor/libva/va/va_enc_vp9.h +639 -0
- package/engine/vendor/libva/va/va_fei.h +170 -0
- package/engine/vendor/libva/va/va_fei_h264.h +505 -0
- package/engine/vendor/libva/va/va_fei_hevc.h +254 -0
- package/engine/vendor/libva/va/va_prot.h +525 -0
- package/engine/vendor/libva/va/va_version.h +87 -0
- package/engine/vendor/libva/va/va_vpp.h +1590 -0
- package/engine/vendor/nv-codec-headers/LICENSE +24 -0
- package/engine/vendor/nv-codec-headers/README +7 -0
- package/engine/vendor/nv-codec-headers/include/ffnvcodec/nvEncodeAPI.h +3825 -0
- package/examples/reference-client.html +1 -1
- package/package.json +4 -3
package/engine/src/convert.rs
CHANGED
|
@@ -181,6 +181,9 @@ pub fn to_i420(
|
|
|
181
181
|
let pairs = dst_h / 2;
|
|
182
182
|
let per_band = pairs.div_ceil(bands());
|
|
183
183
|
let cw = dst_w / 2;
|
|
184
|
+
let (columns, rows) = (spans(src_w, dst_w), spans(src_h, dst_h));
|
|
185
|
+
let widest = |spans: &[(usize, usize)]| spans.iter().map(|(a, b)| b - a).max().unwrap_or(1);
|
|
186
|
+
let largest_box = (widest(&columns) * widest(&rows)).min(4095) as u64;
|
|
184
187
|
let source = Source {
|
|
185
188
|
data: src,
|
|
186
189
|
width: src_w,
|
|
@@ -188,6 +191,11 @@ pub fn to_i420(
|
|
|
188
191
|
stride: src_stride,
|
|
189
192
|
bytes_per_pixel,
|
|
190
193
|
order,
|
|
194
|
+
columns,
|
|
195
|
+
rows,
|
|
196
|
+
reciprocals: (0..=largest_box)
|
|
197
|
+
.map(|n| (1 << 32) / n.max(1) + 1)
|
|
198
|
+
.collect(),
|
|
191
199
|
};
|
|
192
200
|
std::thread::scope(|scope| {
|
|
193
201
|
let y_bands = y_plane.chunks_mut(per_band * 2 * dst_w);
|
|
@@ -196,21 +204,84 @@ pub fn to_i420(
|
|
|
196
204
|
for (band, ((y, u), v)) in y_bands.zip(u_bands).zip(v_bands).enumerate() {
|
|
197
205
|
let source = &source;
|
|
198
206
|
scope.spawn(move || {
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
y[pair * 2 * dst_w..(pair + 1) * 2 * dst_w].split_at_mut(dst_w);
|
|
204
|
-
let u_row = &mut u[pair * cw..(pair + 1) * cw];
|
|
205
|
-
let v_row = &mut v[pair * cw..(pair + 1) * cw];
|
|
206
|
-
source.row_pair(dy, dst_w, dst_h, top, bottom, u_row, v_row);
|
|
207
|
+
#[cfg(target_arch = "x86_64")]
|
|
208
|
+
if std::arch::is_x86_feature_detected!("avx2") {
|
|
209
|
+
// SAFETY: the CPU was just checked for AVX2.
|
|
210
|
+
return unsafe { band_avx2(source, band * per_band, dst_w, dst_h, y, u, v) };
|
|
207
211
|
}
|
|
212
|
+
convert_band(source, band * per_band, dst_w, dst_h, y, u, v)
|
|
208
213
|
});
|
|
209
214
|
}
|
|
210
215
|
});
|
|
211
216
|
Some(out)
|
|
212
217
|
}
|
|
213
218
|
|
|
219
|
+
/// Rows `first_pair * 2..` of the destination into this band's planes.
|
|
220
|
+
#[inline(always)]
|
|
221
|
+
fn convert_band(
|
|
222
|
+
source: &Source,
|
|
223
|
+
first_pair: usize,
|
|
224
|
+
dst_w: usize,
|
|
225
|
+
dst_h: usize,
|
|
226
|
+
y: &mut [u8],
|
|
227
|
+
u: &mut [u8],
|
|
228
|
+
v: &mut [u8],
|
|
229
|
+
) {
|
|
230
|
+
let cw = dst_w / 2;
|
|
231
|
+
// A destination row pair of box averages and a row of box-row sums, made
|
|
232
|
+
// once per band and only when this is not the native path.
|
|
233
|
+
let mut rgb = Vec::new();
|
|
234
|
+
let mut sums = Vec::new();
|
|
235
|
+
for pair in 0..u.len() / cw {
|
|
236
|
+
let dy = (first_pair + pair) * 2;
|
|
237
|
+
let (top, bottom) = y[pair * 2 * dst_w..(pair + 1) * 2 * dst_w].split_at_mut(dst_w);
|
|
238
|
+
let u_row = &mut u[pair * cw..(pair + 1) * cw];
|
|
239
|
+
let v_row = &mut v[pair * cw..(pair + 1) * cw];
|
|
240
|
+
if source.native(dst_w, dst_h) && source.bytes_per_pixel == 4 {
|
|
241
|
+
source.native_pair(dy, top, bottom, u_row, v_row);
|
|
242
|
+
} else {
|
|
243
|
+
rgb.resize(dst_w * 2, [0i32; 3]);
|
|
244
|
+
let (a, b) = rgb.split_at_mut(dst_w);
|
|
245
|
+
if source.bytes_per_pixel == 4 {
|
|
246
|
+
source.box_row::<4>(dy, a, &mut sums);
|
|
247
|
+
source.box_row::<4>(dy + 1, b, &mut sums);
|
|
248
|
+
} else {
|
|
249
|
+
source.box_row::<3>(dy, a, &mut sums);
|
|
250
|
+
source.box_row::<3>(dy + 1, b, &mut sums);
|
|
251
|
+
}
|
|
252
|
+
pack(a, b, top, bottom, u_row, v_row);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/// `convert_band` compiled for AVX2, for the x86-64 machines that have it; the
|
|
258
|
+
/// build itself targets the x86-64 baseline.
|
|
259
|
+
#[cfg(target_arch = "x86_64")]
|
|
260
|
+
#[target_feature(enable = "avx2")]
|
|
261
|
+
unsafe fn band_avx2(
|
|
262
|
+
source: &Source,
|
|
263
|
+
first_pair: usize,
|
|
264
|
+
dst_w: usize,
|
|
265
|
+
dst_h: usize,
|
|
266
|
+
y: &mut [u8],
|
|
267
|
+
u: &mut [u8],
|
|
268
|
+
v: &mut [u8],
|
|
269
|
+
) {
|
|
270
|
+
convert_band(source, first_pair, dst_w, dst_h, y, u, v)
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/// The source span `[start, end)` each of `dst` destination pixels covers: the
|
|
274
|
+
/// box a downscale averages, or at least the one nearest pixel when the
|
|
275
|
+
/// destination is a little larger than the source.
|
|
276
|
+
fn spans(src: usize, dst: usize) -> Vec<(usize, usize)> {
|
|
277
|
+
(0..dst)
|
|
278
|
+
.map(|d| {
|
|
279
|
+
let start = d * src / dst;
|
|
280
|
+
(start, ((d + 1) * src).div_ceil(dst).clamp(start + 1, src))
|
|
281
|
+
})
|
|
282
|
+
.collect()
|
|
283
|
+
}
|
|
284
|
+
|
|
214
285
|
struct Source<'a> {
|
|
215
286
|
data: &'a [u8],
|
|
216
287
|
width: usize,
|
|
@@ -218,80 +289,126 @@ struct Source<'a> {
|
|
|
218
289
|
stride: usize,
|
|
219
290
|
bytes_per_pixel: usize,
|
|
220
291
|
order: [usize; 3],
|
|
292
|
+
columns: Vec<(usize, usize)>,
|
|
293
|
+
rows: Vec<(usize, usize)>,
|
|
294
|
+
/// `2^32 / n + 1` for each box area `n` up to this conversion's largest,
|
|
295
|
+
/// capped at 4095; a larger box divides.
|
|
296
|
+
reciprocals: Vec<u64>,
|
|
221
297
|
}
|
|
222
298
|
|
|
223
299
|
impl Source<'_> {
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
let i = y * self.stride + x * self.bytes_per_pixel;
|
|
227
|
-
let p = &self.data[i..i + 3];
|
|
228
|
-
(
|
|
229
|
-
p[self.order[0]] as i32,
|
|
230
|
-
p[self.order[1]] as i32,
|
|
231
|
-
p[self.order[2]] as i32,
|
|
232
|
-
)
|
|
300
|
+
fn native(&self, dst_w: usize, dst_h: usize) -> bool {
|
|
301
|
+
dst_w == self.width && dst_h == self.height
|
|
233
302
|
}
|
|
234
303
|
|
|
235
|
-
///
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
let
|
|
242
|
-
let
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
let y0 = dy * self.height / dst_h;
|
|
246
|
-
let y1 = ((dy + 1) * self.height)
|
|
247
|
-
.div_ceil(dst_h)
|
|
248
|
-
.clamp(y0 + 1, self.height);
|
|
249
|
-
let (mut r, mut g, mut b) = (0i32, 0i32, 0i32);
|
|
304
|
+
/// Destination row `dy` as the average colour of the source box each pixel
|
|
305
|
+
/// covers (at the source's own size, a box of one pixel: its exact colour).
|
|
306
|
+
/// The box's rows are summed byte for byte first, which vectorises, so the
|
|
307
|
+
/// per-pixel work is only the few columns of each box.
|
|
308
|
+
#[inline(always)]
|
|
309
|
+
fn box_row<const BPP: usize>(&self, dy: usize, out: &mut [[i32; 3]], sums: &mut Vec<u32>) {
|
|
310
|
+
let (y0, y1) = self.rows[dy];
|
|
311
|
+
let bytes = self.width * BPP;
|
|
312
|
+
sums.clear();
|
|
313
|
+
sums.resize(bytes, 0);
|
|
250
314
|
for y in y0..y1 {
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
315
|
+
let line = &self.data[y * self.stride..y * self.stride + bytes];
|
|
316
|
+
for (sum, &byte) in sums.iter_mut().zip(line) {
|
|
317
|
+
*sum += byte as u32;
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
let height = (y1 - y0) as u32;
|
|
321
|
+
let [r, g, b] = self.order;
|
|
322
|
+
for (sum, &(x0, x1)) in out.iter_mut().zip(&self.columns) {
|
|
323
|
+
let mut rgb = [0u32; 3];
|
|
324
|
+
for pixel in sums[x0 * BPP..x1 * BPP].chunks_exact(BPP) {
|
|
325
|
+
rgb = [rgb[0] + pixel[r], rgb[1] + pixel[g], rgb[2] + pixel[b]];
|
|
256
326
|
}
|
|
327
|
+
let n = (x1 - x0) * height as usize;
|
|
328
|
+
*sum = match self.reciprocals.get(n) {
|
|
329
|
+
// floor(c / n) as a multiply by 2^32 / n + 1, exact while
|
|
330
|
+
// c * n < 2^32; c is at most 255 * n, and 255 * 4095^2 < 2^32.
|
|
331
|
+
Some(&m) => rgb.map(|c| ((c as u64 * m) >> 32) as i32),
|
|
332
|
+
None => rgb.map(|c| (c / n as u32) as i32),
|
|
333
|
+
};
|
|
257
334
|
}
|
|
258
|
-
let n = ((x1 - x0) * (y1 - y0)) as i32;
|
|
259
|
-
(r / n, g / n, b / n)
|
|
260
335
|
}
|
|
261
336
|
|
|
262
|
-
///
|
|
263
|
-
|
|
264
|
-
|
|
337
|
+
/// A 4-byte source at its own size: the path every full-size desktop takes,
|
|
338
|
+
/// so it reads the two rows directly and in a shape the compiler vectorises.
|
|
339
|
+
#[inline(always)]
|
|
340
|
+
fn native_pair(
|
|
265
341
|
&self,
|
|
266
342
|
dy: usize,
|
|
267
|
-
dst_w: usize,
|
|
268
|
-
dst_h: usize,
|
|
269
343
|
top: &mut [u8],
|
|
270
344
|
bottom: &mut [u8],
|
|
271
345
|
u: &mut [u8],
|
|
272
346
|
v: &mut [u8],
|
|
273
347
|
) {
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
top[dx] = luma(a.0, a.1, a.2);
|
|
281
|
-
top[dx + 1] = luma(b.0, b.1, b.2);
|
|
282
|
-
bottom[dx] = luma(c.0, c.1, c.2);
|
|
283
|
-
bottom[dx + 1] = luma(d.0, d.1, d.2);
|
|
284
|
-
let (cu, cv) = chroma(
|
|
285
|
-
(a.0 + b.0 + c.0 + d.0 + 2) >> 2,
|
|
286
|
-
(a.1 + b.1 + c.1 + d.1 + 2) >> 2,
|
|
287
|
-
(a.2 + b.2 + c.2 + d.2 + 2) >> 2,
|
|
288
|
-
);
|
|
289
|
-
u[cx] = cu;
|
|
290
|
-
v[cx] = cv;
|
|
348
|
+
let width = top.len();
|
|
349
|
+
let row = |y: usize| &self.data[y * self.stride..y * self.stride + width * 4];
|
|
350
|
+
let (upper, lower) = (row(dy), row(dy + 1));
|
|
351
|
+
match self.order {
|
|
352
|
+
[2, 1, 0] => pair4::<2, 1, 0>(upper, lower, top, bottom, u, v),
|
|
353
|
+
_ => pair4::<0, 1, 2>(upper, lower, top, bottom, u, v),
|
|
291
354
|
}
|
|
292
355
|
}
|
|
293
356
|
}
|
|
294
357
|
|
|
358
|
+
#[inline(always)]
|
|
359
|
+
fn pair4<const R: usize, const G: usize, const B: usize>(
|
|
360
|
+
upper: &[u8],
|
|
361
|
+
lower: &[u8],
|
|
362
|
+
top: &mut [u8],
|
|
363
|
+
bottom: &mut [u8],
|
|
364
|
+
u: &mut [u8],
|
|
365
|
+
v: &mut [u8],
|
|
366
|
+
) {
|
|
367
|
+
// The same integer formulas as `luma` and `chroma`, in 16 bits: every
|
|
368
|
+
// intermediate fits (luma below 2^16 unsigned, chroma within ±2^15) and the
|
|
369
|
+
// results need no clamp, which lets the compiler use 16-bit vector lanes.
|
|
370
|
+
for (src, dst) in [(upper, &mut *top), (lower, &mut *bottom)] {
|
|
371
|
+
for (pixel, y) in src.chunks_exact(4).zip(dst.iter_mut()) {
|
|
372
|
+
let (r, g, b) = (pixel[R] as u16, pixel[G] as u16, pixel[B] as u16);
|
|
373
|
+
*y = (((66 * r + 129 * g + 25 * b + 128) >> 8) + 16) as u8;
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
let blocks = upper.chunks_exact(8).zip(lower.chunks_exact(8));
|
|
377
|
+
for ((a, b), (u, v)) in blocks.zip(u.iter_mut().zip(v.iter_mut())) {
|
|
378
|
+
let sum = |c: usize| {
|
|
379
|
+
((a[c] as u16 + a[c + 4] as u16 + b[c] as u16 + b[c + 4] as u16 + 2) >> 2) as i16
|
|
380
|
+
};
|
|
381
|
+
let (r, g, b) = (sum(R), sum(G), sum(B));
|
|
382
|
+
*u = (((-38 * r - 74 * g + 112 * b + 128) >> 8) + 128) as u8;
|
|
383
|
+
*v = (((112 * r - 94 * g - 18 * b + 128) >> 8) + 128) as u8;
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
/// Two rows of RGB into two luma rows and the chroma row they share, chroma
|
|
388
|
+
/// being the average of each 2x2 block.
|
|
389
|
+
#[inline(always)]
|
|
390
|
+
fn pack(
|
|
391
|
+
a: &[[i32; 3]],
|
|
392
|
+
b: &[[i32; 3]],
|
|
393
|
+
top: &mut [u8],
|
|
394
|
+
bottom: &mut [u8],
|
|
395
|
+
u: &mut [u8],
|
|
396
|
+
v: &mut [u8],
|
|
397
|
+
) {
|
|
398
|
+
for (p, y) in a
|
|
399
|
+
.iter()
|
|
400
|
+
.zip(top.iter_mut())
|
|
401
|
+
.chain(b.iter().zip(bottom.iter_mut()))
|
|
402
|
+
{
|
|
403
|
+
*y = luma(p[0], p[1], p[2]);
|
|
404
|
+
}
|
|
405
|
+
for (cx, (u, v)) in u.iter_mut().zip(v.iter_mut()).enumerate() {
|
|
406
|
+
let sum =
|
|
407
|
+
|c: usize| (a[cx * 2][c] + a[cx * 2 + 1][c] + b[cx * 2][c] + b[cx * 2 + 1][c] + 2) >> 2;
|
|
408
|
+
(*u, *v) = chroma(sum(0), sum(1), sum(2));
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
|
|
295
412
|
#[cfg(test)]
|
|
296
413
|
mod tests {
|
|
297
414
|
use super::*;
|
|
@@ -348,6 +465,114 @@ mod tests {
|
|
|
348
465
|
);
|
|
349
466
|
}
|
|
350
467
|
|
|
468
|
+
/// The per-pixel conversion this module used to run, kept as the definition
|
|
469
|
+
/// the fast paths must reproduce bit for bit.
|
|
470
|
+
fn reference(
|
|
471
|
+
src: &[u8],
|
|
472
|
+
sw: usize,
|
|
473
|
+
sh: usize,
|
|
474
|
+
stride: usize,
|
|
475
|
+
format: PixelFormat,
|
|
476
|
+
dw: usize,
|
|
477
|
+
dh: usize,
|
|
478
|
+
) -> Vec<u8> {
|
|
479
|
+
let (bpp, o) = layout(format).unwrap();
|
|
480
|
+
let rgb = |x: usize, y: usize| {
|
|
481
|
+
let p = &src[y * stride + x * bpp..];
|
|
482
|
+
(p[o[0]] as i32, p[o[1]] as i32, p[o[2]] as i32)
|
|
483
|
+
};
|
|
484
|
+
let sample = |dx: usize, dy: usize| {
|
|
485
|
+
let x0 = dx * sw / dw;
|
|
486
|
+
let x1 = ((dx + 1) * sw).div_ceil(dw).clamp(x0 + 1, sw);
|
|
487
|
+
let y0 = dy * sh / dh;
|
|
488
|
+
let y1 = ((dy + 1) * sh).div_ceil(dh).clamp(y0 + 1, sh);
|
|
489
|
+
let (mut r, mut g, mut b) = (0, 0, 0);
|
|
490
|
+
for y in y0..y1 {
|
|
491
|
+
for x in x0..x1 {
|
|
492
|
+
let p = rgb(x, y);
|
|
493
|
+
(r, g, b) = (r + p.0, g + p.1, b + p.2);
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
let n = ((x1 - x0) * (y1 - y0)) as i32;
|
|
497
|
+
(r / n, g / n, b / n)
|
|
498
|
+
};
|
|
499
|
+
let (mut y, mut u, mut v) = (vec![0; dw * dh], vec![], vec![]);
|
|
500
|
+
for cy in 0..dh / 2 {
|
|
501
|
+
for cx in 0..dw / 2 {
|
|
502
|
+
let px = [(0, 0), (1, 0), (0, 1), (1, 1)]
|
|
503
|
+
.map(|(i, j)| (cx * 2 + i, cy * 2 + j, sample(cx * 2 + i, cy * 2 + j)));
|
|
504
|
+
for (x, yy, p) in px {
|
|
505
|
+
y[yy * dw + x] = luma(p.0, p.1, p.2);
|
|
506
|
+
}
|
|
507
|
+
let s = |f: fn(&(i32, i32, i32)) -> i32| {
|
|
508
|
+
(px.iter().map(|p| f(&p.2)).sum::<i32>() + 2) >> 2
|
|
509
|
+
};
|
|
510
|
+
let (cu, cv) = chroma(s(|p| p.0), s(|p| p.1), s(|p| p.2));
|
|
511
|
+
u.push(cu);
|
|
512
|
+
v.push(cv);
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
[y, u, v].concat()
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
#[test]
|
|
519
|
+
fn every_path_matches_the_per_pixel_definition_exactly() {
|
|
520
|
+
let mut seed = 0x9e3779b9u32;
|
|
521
|
+
let mut noise = |n: usize| {
|
|
522
|
+
(0..n)
|
|
523
|
+
.map(|_| {
|
|
524
|
+
seed ^= seed << 13;
|
|
525
|
+
seed ^= seed >> 17;
|
|
526
|
+
seed ^= seed << 5;
|
|
527
|
+
seed as u8
|
|
528
|
+
})
|
|
529
|
+
.collect::<Vec<u8>>()
|
|
530
|
+
};
|
|
531
|
+
let formats = [
|
|
532
|
+
PixelFormat::Bgrx,
|
|
533
|
+
PixelFormat::Rgba,
|
|
534
|
+
PixelFormat::Bgr,
|
|
535
|
+
PixelFormat::Rgb,
|
|
536
|
+
];
|
|
537
|
+
// Native, a few pixels off, box downscales, and boxes near and past the
|
|
538
|
+
// largest area the reciprocal table covers.
|
|
539
|
+
for (sw, sh, dw, dh) in [
|
|
540
|
+
(256, 126, 4, 2),
|
|
541
|
+
(400, 200, 4, 2),
|
|
542
|
+
(64, 36, 64, 36),
|
|
543
|
+
(67, 41, 66, 40),
|
|
544
|
+
(100, 60, 38, 22),
|
|
545
|
+
(90, 50, 92, 50),
|
|
546
|
+
(33, 17, 8, 6),
|
|
547
|
+
] {
|
|
548
|
+
for format in formats {
|
|
549
|
+
let bpp = layout(format).unwrap().0;
|
|
550
|
+
let stride = sw * bpp + 12;
|
|
551
|
+
let src = noise(stride * sh);
|
|
552
|
+
let fast = to_i420(&src, sw, sh, stride, format, dw, dh).unwrap();
|
|
553
|
+
assert!(
|
|
554
|
+
fast.data == reference(&src, sw, sh, stride, format, dw & !1, dh & !1),
|
|
555
|
+
"{format:?} {sw}x{sh} -> {dw}x{dh}"
|
|
556
|
+
);
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
#[test]
|
|
562
|
+
fn padded_bgra_rows_remain_aligned_in_stills_and_video() {
|
|
563
|
+
// ScreenCaptureKit pads 3420 BGRA pixels from 13680 to 13696 bytes/row.
|
|
564
|
+
let src = [
|
|
565
|
+
0, 0, 255, 255, 0, 0, 255, 255, 0, 255, 0, 0, // red row + green padding
|
|
566
|
+
255, 0, 0, 255, 255, 0, 0, 255, 0, 255, 0, 0, // blue row + green padding
|
|
567
|
+
];
|
|
568
|
+
assert_eq!(
|
|
569
|
+
to_bgrx(&src, 2, 2, 12, PixelFormat::Bgra, 2, 2).unwrap(),
|
|
570
|
+
[src[0..8].to_vec(), src[12..20].to_vec()].concat()
|
|
571
|
+
);
|
|
572
|
+
let video = to_i420(&src, 2, 2, 12, PixelFormat::Bgra, 2, 2).unwrap();
|
|
573
|
+
assert_eq!(video.y_plane(), &[82, 82, 41, 41]);
|
|
574
|
+
}
|
|
575
|
+
|
|
351
576
|
#[test]
|
|
352
577
|
fn unsupported_format_is_reported_not_guessed() {
|
|
353
578
|
assert!(to_i420(&vec![0u8; 64], 4, 4, 16, PixelFormat::Unsupported, 2, 2).is_none());
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
//! Cursor metadata is independent of pixel damage and never painted into video.
|
|
2
|
+
use crate::protocol::{timestamp_us, CursorPosition};
|
|
3
|
+
use std::time::Instant;
|
|
4
|
+
|
|
5
|
+
pub type CursorSink = Box<dyn Fn(CursorPosition) + Send + 'static>;
|
|
6
|
+
|
|
7
|
+
pub struct Reporter {
|
|
8
|
+
sink: CursorSink,
|
|
9
|
+
last: Option<(i32, i32, bool)>,
|
|
10
|
+
bitmap_visible: bool,
|
|
11
|
+
}
|
|
12
|
+
impl Reporter {
|
|
13
|
+
pub fn new(sink: CursorSink) -> Self {
|
|
14
|
+
Self {
|
|
15
|
+
sink,
|
|
16
|
+
last: None,
|
|
17
|
+
bitmap_visible: true,
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
pub fn update_spa(
|
|
21
|
+
&mut self,
|
|
22
|
+
meta: &pipewire::spa::sys::spa_meta_cursor,
|
|
23
|
+
bitmap: Option<&pipewire::spa::sys::spa_meta_bitmap>,
|
|
24
|
+
width: usize,
|
|
25
|
+
height: usize,
|
|
26
|
+
) {
|
|
27
|
+
// SPA id zero is no new data, not a visibility change.
|
|
28
|
+
if meta.id == 0 {
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
if let Some(bitmap) = bitmap.filter(|b| b.format != 0) {
|
|
32
|
+
self.bitmap_visible = bitmap.offset != 0;
|
|
33
|
+
}
|
|
34
|
+
let (x, y) = (meta.position.x, meta.position.y);
|
|
35
|
+
self.update(
|
|
36
|
+
x,
|
|
37
|
+
y,
|
|
38
|
+
self.bitmap_visible
|
|
39
|
+
&& x >= 0
|
|
40
|
+
&& y >= 0
|
|
41
|
+
&& (x as usize) < width
|
|
42
|
+
&& (y as usize) < height,
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
pub fn update(&mut self, x: i32, y: i32, visible: bool) {
|
|
46
|
+
if self.last == Some((x, y, visible)) {
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
self.last = Some((x, y, visible));
|
|
50
|
+
(self.sink)(CursorPosition {
|
|
51
|
+
x,
|
|
52
|
+
y,
|
|
53
|
+
visible,
|
|
54
|
+
timestamp_us: timestamp_us(Instant::now()),
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
#[cfg(test)]
|
|
60
|
+
mod tests {
|
|
61
|
+
use super::*;
|
|
62
|
+
#[test]
|
|
63
|
+
fn spa_no_update_off_source_and_invisible_bitmap_are_distinct() {
|
|
64
|
+
use pipewire::spa::sys::{spa_meta_bitmap, spa_meta_cursor, spa_point, spa_rectangle};
|
|
65
|
+
let (tx, rx) = std::sync::mpsc::channel();
|
|
66
|
+
let mut reporter = Reporter::new(Box::new(move |p| {
|
|
67
|
+
tx.send(p).unwrap();
|
|
68
|
+
}));
|
|
69
|
+
let mut meta = spa_meta_cursor {
|
|
70
|
+
id: 1,
|
|
71
|
+
flags: 0,
|
|
72
|
+
position: spa_point { x: 40, y: 60 },
|
|
73
|
+
hotspot: spa_point { x: 2, y: 3 },
|
|
74
|
+
bitmap_offset: 0,
|
|
75
|
+
};
|
|
76
|
+
reporter.update_spa(&meta, None, 800, 600);
|
|
77
|
+
meta.id = 0;
|
|
78
|
+
reporter.update_spa(&meta, None, 800, 600);
|
|
79
|
+
assert_eq!(rx.try_iter().count(), 1);
|
|
80
|
+
meta.id = 1;
|
|
81
|
+
meta.position.x = -1;
|
|
82
|
+
reporter.update_spa(&meta, None, 800, 600);
|
|
83
|
+
assert!(!rx.try_recv().unwrap().visible);
|
|
84
|
+
meta.position.x = 40;
|
|
85
|
+
let bitmap = spa_meta_bitmap {
|
|
86
|
+
format: 1,
|
|
87
|
+
size: spa_rectangle {
|
|
88
|
+
width: 16,
|
|
89
|
+
height: 16,
|
|
90
|
+
},
|
|
91
|
+
stride: 64,
|
|
92
|
+
offset: 0,
|
|
93
|
+
};
|
|
94
|
+
reporter.update_spa(&meta, Some(&bitmap), 800, 600);
|
|
95
|
+
assert!(!rx.try_recv().unwrap().visible);
|
|
96
|
+
reporter.update_spa(&meta, None, 800, 600);
|
|
97
|
+
assert!(rx.try_recv().is_err(), "no new bitmap retains invisibility");
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
#[test]
|
|
101
|
+
fn reports_initial_motion_and_visibility_but_not_unchanged_polls() {
|
|
102
|
+
let (tx, rx) = std::sync::mpsc::channel();
|
|
103
|
+
let mut reporter = Reporter::new(Box::new(move |p| {
|
|
104
|
+
tx.send(p).unwrap();
|
|
105
|
+
}));
|
|
106
|
+
reporter.update(10, 20, true);
|
|
107
|
+
reporter.update(10, 20, true);
|
|
108
|
+
reporter.update(30, 40, true);
|
|
109
|
+
reporter.update(30, 40, false);
|
|
110
|
+
let notices: Vec<_> = rx.try_iter().collect();
|
|
111
|
+
assert_eq!(notices.len(), 3);
|
|
112
|
+
assert_eq!((notices[1].x, notices[1].y), (30, 40));
|
|
113
|
+
assert!(!notices[2].visible);
|
|
114
|
+
assert!(notices
|
|
115
|
+
.windows(2)
|
|
116
|
+
.all(|p| p[0].timestamp_us <= p[1].timestamp_us));
|
|
117
|
+
}
|
|
118
|
+
}
|