iced_live_cast 0.1.0

Blazing-fast cross-platform display casting widgets and sources for iced
Documentation

iced_live_cast

Blazing-fast cross-platform display casting for iced.

iced_live_cast is the crate you reach for when you want a live preview in an iced app without routing frames through your application's message loop.

It gives you three layers:

  • CastView for actively rendering a live handle inside iced
  • CastHandle for callers that already own frames and want to push them
  • DisplayCapture for OS-backed display capture on macOS and Windows

The hot path stays small:

  • sources push frames into a shared handle
  • the widget owns redraw scheduling
  • the renderer reuses one GPU texture per handle instead of rebuilding per frame

Why this crate exists

If you already have frames, use CastHandle.

If you want the OS to produce frames for you, use DisplayCapture.

If you just want something on screen, use CastView.

That is the whole pitch.

What you get

  • an active iced widget with redraw scheduling
  • a reusable GPU texture per live handle
  • typed source markers and typed runtime errors
  • a BGRA upload path tuned for display capture
  • macOS support through screencapturekit
  • Windows support through windows-capture

Quick start

use iced::widget::container;
use iced::{Element, Length};
use iced_live_cast::{CastView, Display, DisplayCapture};
use std::num::NonZeroU32;

fn preview() -> Result<Element<'static, ()>, Box<dyn std::error::Error>> {
    let display = Display::new(NonZeroU32::new(1).expect("display id is non-zero"));
    let capture = DisplayCapture::start(display)?;

    Ok(container(
        CastView::new(&capture)
            .width(Length::Fill)
            .height(400),
    )
    .into())
}

For the full story, run:

cargo run --example basic -- 1

That example uses the built-in display source.

Manual sources

If your frames come from somewhere other than the built-in display source, use CastHandle directly:

use iced_live_cast::{CastHandle, Frame};

let handle = CastHandle::new();
let frame = Frame::from_bgra(1280, 720, vec![0; 1280 * 720 * 4])?;
handle.present(frame);

You can then render the same handle with CastView::new(&handle).

If you want a full running example for that path, run:

cargo run --example manual_push

And here is that manual source path running for real:

Manual push example

Benchmarks

Quick local numbers from a short Criterion run on Apple silicon with 1080p frames:

Benchmark Size Result
frame_construction/from_rgba/packed 1080p about 1.52 ms
frame_construction/from_bgra/packed 1080p about 475 µs
frame_processing/rgba_pixels/packed_bgra 1080p about 328 µs
frame_handles/frame_to_handle/full_frame 1080p about 370 µs
cast_handle_updates/present_frame/prebuilt_bgra 1080p about 19 ns
cast_handle_updates/construct_and_present/bgra 1080p about 469 µs

The GPU upload bench is included too, but treat it as a renderer baseline rather than a full end-to-end crate benchmark. It measures the same wgpu upload shape the renderer uses, not the whole widget pipeline.

Platform notes

  • macOS requires Screen Recording permission from the OS before display capture can start.
  • The crate uses screencapturekit on macOS and windows-capture on Windows.
  • Linux support is not implemented yet.

Development

Useful checks from the workspace root:

cargo check --lib --examples --benches
cargo test --lib
cargo bench --no-run