Skip to content

Commit

Permalink
scaffold for universal-capture crate (#5)
Browse files Browse the repository at this point in the history
* Added memcpy benchmark

* Added clone assign benchmark

* Added Source and Dest traits with Vec<u8>

* Added benchmarks for in-memory capture

* Fixed platform specific dependency

* Added swap

* use tabs

* Mark vaporware
  • Loading branch information
TheButlah authored Nov 24, 2023
1 parent 5a712a7 commit 966f139
Show file tree
Hide file tree
Showing 13 changed files with 701 additions and 6 deletions.
453 changes: 448 additions & 5 deletions Cargo.lock

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ members = [
"apps/rvid/client",
"apps/rvid/server",

"crates/nexus-voicechat"
"crates/nexus-voicechat",
"crates/universal-capture",
]
# These settings will apply to all members of the workspace that opt in to them
[workspace.package]
Expand Down
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@ libraries and `apps` for application-specific crates.

- [rvid](apps/rvid) - Remote Virtual Display, a rust based PCVR solution.

## Libraries

- [universal-capture](crates/universal-capture) - A cross platform solution for
window capture.

## First Time Setup

- Install [rustup](https://rustup.rs)
Expand Down
28 changes: 28 additions & 0 deletions crates/universal-capture/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
[package]
name = "universal-capture"
version.workspace = true
license.workspace = true
repository.workspace = true
edition.workspace = true
rust-version.workspace = true

[features]
# Sources are prefixed with `source-`
# Destinations are prefixed with `dest-`
# In-memory sources and destinations are always supported.
source-dx11 = ["dep:dxcapture"]

[target.'cfg(windows)'.dependencies]
dxcapture = { version = "1.1.3", optional = true }

[dev-dependencies]
criterion = "0.5"
rand = "0.8.5"

[[bench]]
name = "common_mem_ops"
harness = false

[[bench]]
name = "in_mem_capture"
harness = false
8 changes: 8 additions & 0 deletions crates/universal-capture/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# universal-capture

Provides a number of different ways to capture windows on various different
operating systems and with various different representations and APIs.

## Project Status

Vaporware.
31 changes: 31 additions & 0 deletions crates/universal-capture/benches/common/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
use rand::Fill;

pub struct Dims {
width: usize,
height: usize,
}

impl Dims {
pub const fn size(&self) -> usize {
self.width * self.height
}
}

pub const RES_1080: Dims = Dims {
width: 1920,
height: 1080,
};

pub const RES_1440: Dims = Dims {
width: 2560,
height: 1440,
};

pub fn random_frames(res: Dims, num_frames: usize) -> Vec<Vec<u8>> {
let mut rng = rand::thread_rng();
let mut frames = vec![vec![0; res.size()]; num_frames];
for v in frames.iter_mut() {
v.as_mut_slice().try_fill(&mut rng).unwrap();
}
frames
}
91 changes: 91 additions & 0 deletions crates/universal-capture/benches/common_mem_ops.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
//! Benchmarks common memory operations like memcpy, malloc, free, etc.
mod common;

use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion};
use std::hint::black_box;

use common::{random_frames, RES_1080, RES_1440};

fn bench_frame_copy(c: &mut Criterion) {
/// Number of source frames we hold in memory as inputs.
const N_FRAMES_IN_MEMORY: usize = 128;

let mut frames_1080 = random_frames(RES_1080, N_FRAMES_IN_MEMORY);
let mut frames_1440 = random_frames(RES_1440, N_FRAMES_IN_MEMORY);
let mut dest_1080 = vec![0; RES_1080.size()];
let mut dest_1440 = vec![0; RES_1440.size()];

let mut group = c.benchmark_group("frame copy");
group.throughput(criterion::Throughput::Elements(
N_FRAMES_IN_MEMORY.try_into().unwrap(),
));

#[inline]
fn swap_frames(source: &mut [Vec<u8>], mut dest: &mut Vec<u8>) {
let source = black_box(source);
for f in source {
std::mem::swap(dest, f);
dest = black_box(dest);
}
}

#[inline]
fn memcpy_frames(source: &[Vec<u8>], mut dest: &mut [u8]) {
let source = black_box(source);
for f in source {
dest.copy_from_slice(f.as_slice());
dest = black_box(dest);
}
}

#[inline]
fn clone_assign_frames(source: &[Vec<u8>], mut dest: &mut Vec<u8>) {
let source = black_box(source);
for f in source {
// This allocates and copies
let cloned_vec = f.clone();
// This deallocates the vec that used to be in `dest`
*dest = cloned_vec;
dest = black_box(dest);
}
}

group.bench_with_input(BenchmarkId::new("memcpy", "1080p"), &(), |b, &()| {
let source = frames_1080.as_slice();
let dest = dest_1080.as_mut_slice();
b.iter(|| memcpy_frames(source, dest))
});
group.bench_with_input(BenchmarkId::new("memcpy", "1440p"), &(), |b, &()| {
let source = frames_1440.as_slice();
let dest = dest_1440.as_mut_slice();
b.iter(|| memcpy_frames(source, dest))
});

group.bench_with_input(BenchmarkId::new("clone assign", "1080p"), &(), |b, &()| {
let source = frames_1080.as_slice();
let dest = &mut dest_1080;
b.iter(|| clone_assign_frames(source, dest))
});
group.bench_with_input(BenchmarkId::new("clone assign", "1440p"), &(), |b, &()| {
let source = frames_1440.as_slice();
let dest = &mut dest_1440;
b.iter(|| clone_assign_frames(source, dest))
});

group.bench_with_input(BenchmarkId::new("swap", "1080p"), &(), |b, &()| {
let source = &mut frames_1080;
let dest = &mut dest_1080;
b.iter(|| swap_frames(source, dest))
});
group.bench_with_input(BenchmarkId::new("swap", "1440p"), &(), |b, &()| {
let source = &mut frames_1440;
let dest = &mut dest_1440;
b.iter(|| swap_frames(source, dest))
});

group.finish();
}

criterion_group!(benches, bench_frame_copy);
criterion_main!(benches);
49 changes: 49 additions & 0 deletions crates/universal-capture/benches/in_mem_capture.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
//! Benchmarks capture via in-memory `Vec<u8>`s.
mod common;

use criterion::{criterion_group, criterion_main, Criterion};
use std::hint::black_box;
use universal_capture::Source;

use common::{random_frames, RES_1080, RES_1440};

fn bench_capture(c: &mut Criterion) {
/// Number of source frames we hold in memory as inputs.
const N_FRAMES_IN_MEMORY: usize = 128;

let mut frames_1080 = random_frames(RES_1080, N_FRAMES_IN_MEMORY);
let mut frames_1440 = random_frames(RES_1440, N_FRAMES_IN_MEMORY);
let mut dest_1080 = vec![0; RES_1080.size()];
let mut dest_1440 = vec![0; RES_1440.size()];

let mut group = c.benchmark_group("capture");
group.throughput(criterion::Throughput::Elements(
N_FRAMES_IN_MEMORY.try_into().unwrap(),
));

#[inline]
fn do_capture(source: &mut [Vec<u8>], mut dest: &mut Vec<u8>) {
let source = black_box(source);
for f in source {
f.capture(dest).unwrap();
dest = black_box(dest);
}
}

group.bench_function("1080p", |b| {
let source = &mut frames_1080;
let dest = &mut dest_1080;
b.iter(|| do_capture(source, dest))
});
group.bench_function("1440p", |b| {
let source = &mut frames_1440;
let dest = &mut dest_1440;
b.iter(|| do_capture(source, dest))
});

group.finish();
}

criterion_group!(benches, bench_capture);
criterion_main!(benches);
3 changes: 3 additions & 0 deletions crates/universal-capture/src/dests/in_memory.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
use crate::Dest;

impl<T> Dest for Vec<T> {}
3 changes: 3 additions & 0 deletions crates/universal-capture/src/dests/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
mod in_memory;

pub use in_memory::*;
18 changes: 18 additions & 0 deletions crates/universal-capture/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
pub mod dests;
pub mod sources;

pub trait Dest: private::Sealed {}

pub trait Source<Dest>: private::Sealed {
/// Error returned by [`Self::capture`]
type Error;

/// Captures a frame from the `Source` and puts it in `Dest`.
fn capture(&mut self, dest: &mut Dest) -> Result<(), Self::Error>;
}

mod private {
pub trait Sealed {}

impl<T> Sealed for Vec<T> {}
}
12 changes: 12 additions & 0 deletions crates/universal-capture/src/sources/in_memory.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
use std::convert::Infallible;

use crate::Source;

impl<T> Source<Vec<T>> for Vec<T> {
type Error = Infallible;

fn capture(&mut self, dest: &mut Vec<T>) -> Result<(), Self::Error> {
std::mem::swap(dest, self);
Ok(())
}
}
3 changes: 3 additions & 0 deletions crates/universal-capture/src/sources/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
mod in_memory;

pub use in_memory::*;

0 comments on commit 966f139

Please sign in to comment.