Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions pixelflux/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,11 @@ crate-type = ["cdylib"]
[dependencies]
pyo3 = { version = "0.29.0", features = ["extension-module"] }
wayland-server = { version = "0.31.10", features = ["dlopen"] }
wayland-protocols = { version = "0.32", features = ["server"] }
wayland-protocols = { version = "0.32", features = ["server", "client", "staging"] }
wayland-client = "0.31"
wayland-protocols-misc = { version = "0.3", features = ["client"] }
wayland-protocols-wlr = { version = "0.3", features = ["client"] }
memmap2 = "0.9"
crossbeam-channel = "0.5"
calloop = "0.14"
turbojpeg = "1.3"
Expand All @@ -31,7 +35,7 @@ libloading = "0.9"
xcursor = "0.3.1"
# X11 host capture (replaces the C++ XShm/XFixes path): shm = XShm zero-copy grab,
# xfixes = hardware cursor image. Pure-Rust XCB; links the system libxcb.
x11rb = { version = "0.13", features = ["shm", "xfixes"] }
x11rb = { version = "0.13", features = ["shm", "xfixes", "xtest", "randr"] }
image = "=0.25.9"
# The crate's build.rs probes the FFmpeg found via pkg-config and emits
# per-version cfgs; one crate version supports FFmpeg 3.0 UP TO the release it
Expand Down
797 changes: 599 additions & 198 deletions pixelflux/src/computer_use.rs

Large diffs are not rendered by default.

11 changes: 5 additions & 6 deletions pixelflux/src/encoders/nvenc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1417,8 +1417,7 @@ impl NvencEncoder {
/// — a `0x04` tag, a picture-type byte derived from the *actual* encoded `pictureType`
/// (IDR = `0x01`, I = `0x02`, P = `0x00`) rather than the `force_idr` request, the low 16 bits
/// of the frame number, a zero field, and the width and height (all big-endian).
/// 4. **Emit and fan out**: append the encoded bytes, mirror them to the recording sink when one
/// is attached, unlock the bitstream, and return the framed buffer.
/// 4. **Emit**: append the encoded bytes, unlock the bitstream, and return the framed buffer.
unsafe fn submit_frame(
&mut self,
mapped_buffer: NV_ENC_INPUT_PTR,
Expand Down Expand Up @@ -1968,7 +1967,7 @@ mod gpu_tests {
fn gpu_resolution_reconfigure_roundtrip() {
let mut s = settings(1280, 720, 60.0);
let t0 = std::time::Instant::now();
let mut enc = NvencEncoder::new(&s, ptr::null(), None).expect("NVENC init");
let mut enc = NvencEncoder::new(&s, ptr::null()).expect("NVENC init");
let init_ms = t0.elapsed().as_secs_f64() * 1000.0;

let mut stream: Vec<u8> = Vec::new();
Expand Down Expand Up @@ -2049,7 +2048,7 @@ mod gpu_tests {
let mut s = settings(1280, 720, 60.0);
s.video_cbr_mode = true;
s.video_bitrate_kbps = 4000;
let mut enc = NvencEncoder::new(&s, ptr::null(), None).expect("NVENC init");
let mut enc = NvencEncoder::new(&s, ptr::null()).expect("NVENC init");
let f720 = frame(1280, 720, 10);
for i in 0..3u64 {
enc.encode_cpu_argb(&f720, 1280 * 4, i, 25, i == 0)
Expand Down Expand Up @@ -2082,7 +2081,7 @@ mod gpu_tests {
}
let s = settings(1920, 1080, 60.0);
let before = used_mb();
let mut enc = NvencEncoder::new(&s, ptr::null(), None).expect("init");
let mut enc = NvencEncoder::new(&s, ptr::null()).expect("init");
let f = frame(1920, 1080, 5);
for i in 0..3u64 {
enc.encode_cpu_argb(&f, 1920 * 4, i, 25, i == 0).expect("encode");
Expand All @@ -2097,7 +2096,7 @@ mod gpu_tests {
#[ignore]
fn gpu_init_above_default_headroom() {
let s = settings(2160, 4096, 30.0);
let mut enc = NvencEncoder::new(&s, ptr::null(), None).expect("NVENC init portrait 4K");
let mut enc = NvencEncoder::new(&s, ptr::null()).expect("NVENC init portrait 4K");
assert_eq!(enc.init_params.maxEncodeWidth, 4096);
assert_eq!(enc.init_params.maxEncodeHeight, 4096);
let f = frame(2160, 4096, 20);
Expand Down
36 changes: 18 additions & 18 deletions pixelflux/src/encoders/oh264.rs
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,10 @@ impl Openh264Encoder {
.skip_frames(true)
.adaptive_quantization(false)
.background_detection(false)
// Scene-change detection would inject IDRs the pipeline never asked for,
// breaking the infinite-GOP / on-demand-keyframe contract (x264 parity:
// `i_scenecut_threshold = 0`).
.scene_change_detect(false)
.debug(settings.debug_logging)
.intra_frame_period(IntraFramePeriod::from_num_frames(INFINITE_INTRA_PERIOD))
.num_threads(threads);
Expand Down Expand Up @@ -338,16 +342,12 @@ impl Openh264Encoder {
/// byte-for-byte the layout the NVENC/VAAPI/x264 full-frame paths emit, which is what lets a
/// single client demuxer serve every encoder. Its type byte is read from the *actually
/// encoded* picture type (IDR = `0x01`, I = `0x02`, P = `0x00`) rather than from `force_idr`,
/// because OpenH264's scene-change detection can emit an IDR the caller never asked for, and
/// the header must label a real decode entry point, not the request. It also carries the
/// frame number, a zero y-start, and the width/height (all big-endian). With
/// `omit_stripe_headers` the output is bare Annex-B.
/// because the encoder may re-type a frame, and the header must label a real decode entry
/// point, not the request. It also carries the frame number, a zero y-start, and the
/// width/height (all big-endian). With `omit_stripe_headers` the output is bare Annex-B.
/// 5. **Empty payload**: if the encoder produced no bitstream (e.g. a skipped frame), an empty
/// vec is returned rather than a lone header — a header with no Annex-B behind it would be a
/// malformed frame to the client, and the pipeline reads empty as "nothing to send".
/// 6. **Recording sink**: the raw Annex-B payload (past the header) is also written to the
/// recording sink when one is attached, since a recorder wants the bare elementary stream,
/// not the wire framing.
pub fn encode_host_argb(
&mut self,
argb: &[u8],
Expand Down Expand Up @@ -459,7 +459,7 @@ mod tests {
target_fps: 30.0,
..Default::default()
};
let mut enc = Openh264Encoder::new(&s, None).expect("openh264 init");
let mut enc = Openh264Encoder::new(&s).expect("openh264 init");
let stride = 128 * 4;
let idr = enc.encode_host_argb(&busy_frame(128, 96, 0), stride, 0, true, false).expect("encode idr");
assert!(idr.len() > 10, "IDR frame should produce output");
Expand Down Expand Up @@ -489,7 +489,7 @@ mod tests {
omit_stripe_headers: true,
..Default::default()
};
let mut enc = Openh264Encoder::new(&s, None).expect("openh264 init");
let mut enc = Openh264Encoder::new(&s).expect("openh264 init");
let out = enc.encode_host_argb(&busy_frame(128, 96, 0), 128 * 4, 0, true, false).expect("encode");
assert!(
out.starts_with(&[0, 0, 0, 1]) || out.starts_with(&[0, 0, 1]),
Expand All @@ -511,7 +511,7 @@ mod tests {
target_fps: 30.0,
..Default::default()
};
let mut e = Openh264Encoder::new(&s, None).unwrap();
let mut e = Openh264Encoder::new(&s).unwrap();
let _ = e.encode_host_argb(&busy_frame(w, h, 0), stride, 0, true, false).unwrap();
(1..24).map(|t| e.encode_host_argb(&busy_frame(w, h, t), stride, t as u64, false, false).unwrap().len()).sum()
};
Expand All @@ -533,7 +533,7 @@ mod tests {
target_fps: 30.0,
..Default::default()
};
let mut e = Openh264Encoder::new(&s, None).unwrap();
let mut e = Openh264Encoder::new(&s).unwrap();
let _ = e.encode_host_argb(&busy_frame(w, h, 0), stride, 0, true, false).unwrap();
let high: usize =
(1..24).map(|t| e.encode_host_argb(&busy_frame(w, h, t), stride, t as u64, false, false).unwrap().len()).sum();
Expand All @@ -557,7 +557,7 @@ mod tests {
target_fps: 30.0,
..Default::default()
};
let mut e = Openh264Encoder::new(&s, None).unwrap();
let mut e = Openh264Encoder::new(&s).unwrap();
let _ = e.encode_host_argb(&busy_frame(w, h, 0), stride, 0, true, false).unwrap();
let low: usize =
(1..24).map(|t| e.encode_host_argb(&busy_frame(w, h, t), stride, t as u64, false, false).unwrap().len()).sum();
Expand All @@ -584,7 +584,7 @@ mod tests {
target_fps: 30.0,
..Default::default()
};
let mut e = Openh264Encoder::new(&s, None).unwrap();
let mut e = Openh264Encoder::new(&s).unwrap();
let mut total = e.encode_host_argb(&gradient_frame(w, h, 0), stride, 0, true, false).unwrap().len();
total += (1..24)
.map(|t| e.encode_host_argb(&gradient_frame(w, h, t), stride, t as u64, false, false).unwrap().len())
Expand Down Expand Up @@ -614,7 +614,7 @@ mod tests {
};
let frame = busy_frame(w, h, 0);
for rgba in [false, true] {
let mut e = Openh264Encoder::new(&s, None).unwrap();
let mut e = Openh264Encoder::new(&s).unwrap();
let out = e.encode_host_argb(&frame, stride, 0, true, rgba).expect("encode");
assert!(out.len() > 10, "rgba_input={rgba} must produce output");
assert_eq!(out[0], 0x04, "rgba_input={rgba} output must carry the wire header");
Expand Down Expand Up @@ -642,7 +642,7 @@ mod slice_tests {
video_cbr_mode: true,
..Default::default()
};
let mut enc = Openh264Encoder::new(&s, None).expect("encoder");
let mut enc = Openh264Encoder::new(&s).expect("encoder");
let frame = vec![0x80u8; 640 * 480 * 4];
let out = enc.encode_host_argb(&frame, 640 * 4, 0, true, false).expect("encode");
let mut nals = 0;
Expand Down Expand Up @@ -705,7 +705,7 @@ mod slice_tests {
target_fps: 60.0,
..Default::default()
};
let mut e = Openh264Encoder::new(&s, None).unwrap();
let mut e = Openh264Encoder::new(&s).unwrap();
let mut total = 0usize;
let mut idrs = 0usize;
for t in 0..60u64 {
Expand Down Expand Up @@ -752,7 +752,7 @@ mod slice_tests {
target_fps: 60.0,
..Default::default()
};
let mut e = Openh264Encoder::new(&s, None).unwrap();
let mut e = Openh264Encoder::new(&s).unwrap();
unsafe {
let mut p: openh264_sys2::SEncParamExt = std::mem::zeroed();
let ret = e.encoder.raw_api().get_option(
Expand Down Expand Up @@ -798,7 +798,7 @@ mod rebuild_cost {
..Default::default()
};
let t = std::time::Instant::now();
let mut e = Openh264Encoder::new(&s, None).expect("init");
let mut e = Openh264Encoder::new(&s).expect("init");
let init_ms = t.elapsed().as_secs_f64() * 1000.0;
let frame = vec![128u8; 1920 * 1080 * 4];
let t = std::time::Instant::now();
Expand Down
9 changes: 6 additions & 3 deletions pixelflux/src/encoders/overlay.rs
Original file line number Diff line number Diff line change
Expand Up @@ -211,12 +211,15 @@ impl OverlayState {
}
}

/// Render element for a software cursor `image` at `pos` (logical coords).
/// Render element for a software cursor `image` at logical `pos` on an output with
/// fractional `scale`; the position converts to physical here so the composited cursor
/// lands where the damage tracker and clients (which work in physical pixels) expect it.
pub fn get_cursor_element<R>(
&self,
renderer: &mut R,
image: xcursor::parser::Image,
pos: Point<i32, smithay::utils::Logical>,
pos: Point<f64, smithay::utils::Logical>,
scale: f64,
) -> Option<MemoryRenderBufferRenderElement<R>>
where
R: Renderer + ImportMem,
Expand All @@ -233,7 +236,7 @@ impl OverlayState {

let hot: Point<i32, smithay::utils::Physical> =
(image.xhot as i32, image.yhot as i32).into();
let phys_pos: Point<i32, smithay::utils::Physical> = (pos.x, pos.y).into();
let phys_pos = pos.to_physical(smithay::utils::Scale::from(scale)).to_i32_round();

MemoryRenderBufferRenderElement::from_buffer(
renderer,
Expand Down
Loading