e2c9bfd3d9
apple / swift (push) Successful in 1m4s
windows-host / package (push) Successful in 6m28s
windows-msix / package (arm64, C:\Users\Public\ffmpeg-arm64, aarch64-pc-windows-msvc, C:\t-a64) (push) Successful in 1m14s
windows-msix / package (x64, C:\Users\Public\ffmpeg, x86_64-pc-windows-msvc, C:\t) (push) Successful in 1m10s
release / apple (push) Successful in 7m53s
android / android (push) Successful in 10m33s
ci / web (push) Successful in 44s
windows / build (aarch64-pc-windows-msvc) (push) Successful in 3m4s
ci / docs-site (push) Successful in 53s
ci / rust (push) Successful in 12m22s
windows / build (x86_64-pc-windows-msvc) (push) Successful in 1m11s
apple / screenshots (push) Successful in 5m24s
deb / build-publish (push) Successful in 3m16s
decky / build-publish (push) Successful in 21s
ci / bench (push) Successful in 4m42s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 27s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 2m34s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 2m42s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 2m13s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 47s
flatpak / build-publish (push) Successful in 4m24s
rpm / build-publish (bazzite, punktfunk-fedora-rpm) (push) Successful in 8m5s
docker / deploy-docs (push) Successful in 25s
rpm / build-publish (fedora-44, punktfunk-fedora44-rpm) (push) Successful in 7m44s
HDR (display-driven, matching the WGC path): - CTA-861.3 HDR EDID (BT.2020 primaries + HDR Static Metadata block) so Windows offers "Use HDR" on the virtual display. The host FOLLOWS the display's live advanced-color state, recreating the shared ring at the matching format (FP16 in HDR / BGRA in SDR) on a toggle — no freeze. - Always emit Main10/BT.2020-PQ Rgb10a2 while the display is HDR; the client auto-detects PQ from the HEVC VUI (clients under-report VIDEO_CAP_10BIT). Generic HDR10 mastering SEI on every IDR. - Generation-tagged `latest` (gen<<40|seq<<8|slot) + driver `is_stale` re-attach kill the toggle-time garbage frame and any stale-ring read. Perf: - Pipeline the encode loop (Capturer::pipeline_depth; IDD-push = 2): submit N+1 before polling N so the convert/copy on the 3D engine overlaps the NVENC encode of N on the ASIC. PUNKTFUNK_IDD_DEPTH overrides (1 = synchronous). - Rotating host output ring (OUT_RING) so the in-flight encode and the next convert never touch the same texture. - HDR converts directly from the keyed-mutex slot's SRV into the output ring (drops the redundant slot->fp16 scratch copy); SDR copies the BGRA slot in. The slot mutex is held only across the convert/copy, not the encode. RING_LEN 3->6 for publish headroom. - Capture-health diagnostic: new_fps vs repeat_fps under PUNKTFUNK_PERF (a low new_fps at a high send rate means the source isn't compositing, not an encode stall). Validated live on the RTX box: 5120x1440@240 HDR streams; driver composes ~180 new fps, encode 240 fps @ ~4.3 ms p50. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
279 lines
8.2 KiB
Rust
279 lines
8.2 KiB
Rust
use std::env;
|
|
use std::fmt::{self, Display};
|
|
use std::path::{Path, PathBuf};
|
|
|
|
use bindgen::Abi;
|
|
use winreg::enums::HKEY_LOCAL_MACHINE;
|
|
use winreg::RegKey;
|
|
|
|
const UMDF_V: &str = "2.31";
|
|
// Bumped 1.4 -> 1.10 for HDR/FP16 support (IDDCX_ADAPTER_FLAGS_CAN_PROCESS_FP16,
|
|
// IddCxSwapChainReleaseAndAcquireBuffer2, the *2 mode/metadata DDIs). 1.10 is a superset of 1.4, so
|
|
// existing call sites keep working; the new HDR DDIs become available to bind.
|
|
const IDDCX_V: &str = "1.10";
|
|
|
|
#[derive(Debug, thiserror::Error)]
|
|
enum Error {
|
|
#[error(transparent)]
|
|
IoError(#[from] std::io::Error),
|
|
#[error("cannot find the directory")]
|
|
DirectoryNotFound,
|
|
}
|
|
|
|
/// Retrieves the path to the Windows Kits directory. The default should be
|
|
/// `C:\Program Files (x86)\Windows Kits\10`.
|
|
///
|
|
/// # Errors
|
|
/// Returns IO error if failed
|
|
fn get_windows_kits_dir() -> Result<PathBuf, Error> {
|
|
let hklm = RegKey::predef(HKEY_LOCAL_MACHINE);
|
|
let key = r"SOFTWARE\Microsoft\Windows Kits\Installed Roots";
|
|
let dir: String = hklm.open_subkey(key)?.get_value("KitsRoot10")?;
|
|
|
|
Ok(dir.into())
|
|
}
|
|
|
|
#[derive(Clone, Copy, PartialEq)]
|
|
enum DirectoryType {
|
|
Include,
|
|
Library,
|
|
}
|
|
|
|
#[derive(Clone, Copy, PartialEq)]
|
|
enum Target {
|
|
X86_64,
|
|
ARM64,
|
|
}
|
|
|
|
impl Default for Target {
|
|
fn default() -> Self {
|
|
let target = env::var("CARGO_CFG_TARGET_ARCH").unwrap();
|
|
match &*target {
|
|
"x86_64" => Self::X86_64,
|
|
"aarch64" => Self::ARM64,
|
|
_ => unimplemented!("{target} arch is unsupported"),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Display for Target {
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
match self {
|
|
Target::X86_64 => f.write_str("x64"),
|
|
Target::ARM64 => f.write_str("arm64"),
|
|
}
|
|
}
|
|
}
|
|
|
|
fn get_base_path<S: AsRef<Path>>(dir_type: DirectoryType, subs: &[S]) -> Result<PathBuf, Error> {
|
|
let mut dir = get_windows_kits_dir()?.join(match dir_type {
|
|
DirectoryType::Include => "Include",
|
|
DirectoryType::Library => "Lib",
|
|
});
|
|
|
|
dir.extend(subs);
|
|
if !dir.is_dir() {
|
|
return Err(Error::DirectoryNotFound);
|
|
}
|
|
|
|
Ok(dir)
|
|
}
|
|
|
|
fn get_sdk_path<S: AsRef<Path>>(dir_type: DirectoryType, subs: &[S]) -> Result<PathBuf, Error> {
|
|
// We first append lib to the path and read the directory..
|
|
let dir = get_windows_kits_dir()?
|
|
.join(match dir_type {
|
|
DirectoryType::Include => "Include",
|
|
DirectoryType::Library => "Lib",
|
|
})
|
|
.read_dir()?;
|
|
|
|
// In the lib directory we may have one or more directories named after the version of Windows,
|
|
// we will be looking for the highest version number.
|
|
let mut dir = dir
|
|
.filter_map(Result::ok)
|
|
.map(|dir| dir.path())
|
|
.filter(|dir| {
|
|
let is_sdk = dir
|
|
.components()
|
|
.last()
|
|
.and_then(|c| c.as_os_str().to_str())
|
|
.map_or(false, |c| c.starts_with("10."));
|
|
|
|
let mut sub_dir = dir.clone();
|
|
sub_dir.extend(subs);
|
|
|
|
is_sdk && sub_dir.is_dir()
|
|
})
|
|
.max()
|
|
.ok_or_else(|| Error::DirectoryNotFound)?;
|
|
|
|
dir.extend(subs);
|
|
if !dir.is_dir() {
|
|
return Err(Error::DirectoryNotFound);
|
|
}
|
|
|
|
// Finally append um to the path to get the path to the user mode libraries.
|
|
Ok(dir)
|
|
}
|
|
|
|
/// Retrieves the path to the user mode libraries. The path may look something like:
|
|
/// `C:\Program Files (x86)\Windows Kits\10\lib\10.0.18362.0\um`.
|
|
///
|
|
/// # Errors
|
|
/// Returns IO error if failed
|
|
fn get_um_dir(dir_type: DirectoryType) -> Result<PathBuf, Error> {
|
|
let target = Target::default().to_string();
|
|
|
|
let binding = &["um", &target];
|
|
let subs: &[&str] = match dir_type {
|
|
DirectoryType::Include => &["um"],
|
|
DirectoryType::Library => binding,
|
|
};
|
|
|
|
let dir = get_sdk_path(dir_type, subs)?;
|
|
Ok(dir)
|
|
}
|
|
|
|
/// # Errors
|
|
/// Returns IO error if failed
|
|
fn get_umdf_dir(dir_type: DirectoryType) -> Result<PathBuf, Error> {
|
|
match dir_type {
|
|
DirectoryType::Include => get_base_path(dir_type, &["wdf", "umdf", UMDF_V]),
|
|
DirectoryType::Library => get_base_path(
|
|
dir_type,
|
|
&["wdf", "umdf", &Target::default().to_string(), UMDF_V],
|
|
),
|
|
}
|
|
}
|
|
|
|
/// Retrieves the path to the shared headers. The path may look something like:
|
|
/// `C:\Program Files (x86)\Windows Kits\10\lib\10.0.18362.0\shared`.
|
|
///
|
|
/// # Errors
|
|
/// Returns IO error if failed
|
|
fn get_shared_dir() -> Result<PathBuf, Error> {
|
|
let dir = get_sdk_path(DirectoryType::Include, &["shared"])?;
|
|
Ok(dir)
|
|
}
|
|
|
|
fn build_dir() -> PathBuf {
|
|
PathBuf::from(
|
|
std::env::var_os("OUT_DIR").expect("the environment variable OUT_DIR is undefined"),
|
|
)
|
|
}
|
|
|
|
fn generate() {
|
|
// Find the include directory containing the user headers.
|
|
let include_um_dir = get_um_dir(DirectoryType::Include).unwrap();
|
|
let lib_um_dir = get_um_dir(DirectoryType::Library).unwrap();
|
|
let shared = get_shared_dir().unwrap();
|
|
|
|
println!("cargo:rustc-link-search={}", lib_um_dir.display());
|
|
|
|
// Tell Cargo to re-run this if src/wrapper.h gets changed.
|
|
println!("cargo:rerun-if-changed=c/wrapper.h");
|
|
|
|
//
|
|
// UMDF
|
|
//
|
|
|
|
let umdf_lib_dir = get_umdf_dir(DirectoryType::Library).unwrap();
|
|
|
|
println!("cargo:rustc-link-search={}", umdf_lib_dir.display());
|
|
|
|
let wdf_include_dir = get_umdf_dir(DirectoryType::Include).unwrap();
|
|
|
|
// need to link to umdf lib
|
|
println!("cargo:rustc-link-lib=static=WdfDriverStubUm");
|
|
|
|
//
|
|
// IDDCX
|
|
//
|
|
|
|
// The IddCx import lib lives only under the WDK's SDK version (e.g. 10.0.26100.0); a newer base
|
|
// SDK installed alongside it (e.g. 10.0.28000.0) has um\x64 but no iddcx subdir, so picking the
|
|
// max um\x64 version (lib_um_dir) misses it. Resolve by the version that actually contains
|
|
// iddcx — the same way the IddCx.h header path is resolved below.
|
|
let iddcx_lib_dir = get_sdk_path(
|
|
DirectoryType::Library,
|
|
&["um", &Target::default().to_string(), "iddcx", IDDCX_V],
|
|
)
|
|
.unwrap();
|
|
|
|
println!("cargo:rustc-link-search={}", iddcx_lib_dir.display());
|
|
|
|
// need to link to iddcx lib
|
|
println!("cargo:rustc-link-lib=static=IddCxStub");
|
|
|
|
//
|
|
// REST
|
|
//
|
|
|
|
// Get the build directory.
|
|
let out_path = build_dir();
|
|
|
|
// Generate the bindings
|
|
let mut builder = bindgen::Builder::default()
|
|
.derive_debug(false)
|
|
.layout_tests(false)
|
|
.default_enum_style(bindgen::EnumVariation::NewType {
|
|
is_bitfield: false,
|
|
is_global: false,
|
|
})
|
|
.merge_extern_blocks(true)
|
|
.header("c/wrapper.h")
|
|
.header(
|
|
get_sdk_path(DirectoryType::Include, &["um", "iddcx", IDDCX_V])
|
|
.unwrap()
|
|
.join("IddCx.h")
|
|
.to_string_lossy()
|
|
.to_string(),
|
|
)
|
|
// general um includes
|
|
.clang_arg(format!("-I{}", include_um_dir.display()))
|
|
// umdf includes
|
|
.clang_arg(format!("-I{}", wdf_include_dir.display()))
|
|
.clang_arg(format!("-I{}", shared.display()))
|
|
// because aarch64 needs to find excpt.h
|
|
.clang_arg(format!(
|
|
"-I{}",
|
|
get_sdk_path(DirectoryType::Include, &["km", "crt"])
|
|
.unwrap()
|
|
.display()
|
|
))
|
|
.parse_callbacks(Box::new(bindgen::CargoCallbacks::new()))
|
|
.blocklist_type("_?P?IMAGE_TLS_DIRECTORY.*")
|
|
// we will use our own custom type
|
|
.blocklist_item("NTSTATUS")
|
|
.blocklist_item("IddMinimumVersionRequired")
|
|
.blocklist_item("WdfMinimumVersionRequired")
|
|
.clang_arg("--language=c++")
|
|
.clang_arg("-fms-compatibility")
|
|
.clang_arg("-fms-extensions")
|
|
.override_abi(Abi::CUnwind, ".*")
|
|
.generate_cstr(true)
|
|
.derive_default(true);
|
|
|
|
let defines = match Target::default() {
|
|
Target::X86_64 => ["AMD64", "_AMD64_"],
|
|
Target::ARM64 => ["ARM64", "_ARM64_"],
|
|
};
|
|
|
|
for define in defines {
|
|
builder = builder.clang_arg(format!("-D{define}"));
|
|
}
|
|
|
|
// generate
|
|
let umdf = builder.generate().unwrap();
|
|
|
|
// Write the bindings to the $OUT_DIR/bindings.rs file.
|
|
umdf.write_to_file(out_path.join("umdf.rs")).unwrap();
|
|
}
|
|
|
|
fn main() {
|
|
println!("cargo:rerun-if-changed=build.rs");
|
|
|
|
generate();
|
|
}
|