13d1aa5738
The long-deferred Android display stage (design/stats-unification.md; plan 4.1 of design/client-parity-and-network-resilience.md): AMediaCodec_setOnFrameRenderedCallback (API 26, under the minSdk-28 floor ⇒ hard-linked via ndk-sys) reports SurfaceFlinger's per-frame render timestamp, giving the HUD the spec's `display` = decoded→displayed term and the directly-measured capture→displayed end-to-end headline on both decode loops. Falls back per spec to the v1 capture→decoded endpoint on any window without render callbacks (the platform may drop them under load), and to it permanently if registration is refused. - The render timestamp arrives on CLOCK_MONOTONIC; it's re-based onto CLOCK_REALTIME against monotonic-now at callback time, which also cancels the (batchable) callback delivery lag. - The `ndk` crate exposes neither the callback nor the codec pointer needed to bind it raw, so the workspace pins `ndk` 0.9.0 to a vendored copy (clients/android/native/ vendor/ndk) whose ONLY change makes MediaCodec::as_ptr public — the "as_ptr patch". Workspace-excluded so host builds never compile it; drop when upstream exposes either. - nativeVideoStats grows to 26 doubles (22–25: dispValid, displayP50, e2eDispP50/P95; 0–21 unchanged for older readers); StatsOverlay moves headline endpoint + equation together so the equation always tiles the headline interval. Verified: host cargo check/test/clippy, aarch64-linux-android check/clippy, Kotlin app+kit+tests compile, roborazzi HUD render shows the full 4-term equation. Device verification rides plan 4.2's phone A/B. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
144 lines
4.4 KiB
Rust
144 lines
4.4 KiB
Rust
//! Bindings for [sync functions]
|
|
//!
|
|
//! [sync functions]: https://developer.android.com/ndk/reference/group/sync
|
|
#![cfg(feature = "sync")]
|
|
|
|
use std::{
|
|
ffi::CStr,
|
|
fmt::Debug,
|
|
// TODO: Import from std::os::fd::{} since Rust 1.66
|
|
os::unix::io::{AsRawFd, BorrowedFd, FromRawFd, OwnedFd},
|
|
ptr::NonNull,
|
|
};
|
|
|
|
#[doc(alias = "sync_file_info")]
|
|
#[repr(transparent)]
|
|
pub struct SyncFileInfo {
|
|
inner: NonNull<ffi::sync_file_info>,
|
|
}
|
|
|
|
impl Debug for SyncFileInfo {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
f.debug_struct("SyncFileInfo")
|
|
.field("name", &self.name())
|
|
.field("status", &self.status())
|
|
.field("flags", &self.flags())
|
|
.field("num_fences", &self.num_fences())
|
|
.field("fence_info", &self.fence_info())
|
|
.finish()
|
|
}
|
|
}
|
|
|
|
impl SyncFileInfo {
|
|
/// Retrieve detailed information about a sync file and its fences.
|
|
#[doc(alias = "sync_file_info")]
|
|
pub fn new(fd: BorrowedFd<'_>) -> Option<Self> {
|
|
let inner = NonNull::new(unsafe { ffi::sync_file_info(fd.as_raw_fd()) })?;
|
|
Some(Self { inner })
|
|
}
|
|
|
|
pub fn name(&self) -> &CStr {
|
|
let inner = unsafe { self.inner.as_ref() };
|
|
// TODO: Switch to CStr::from_bytes_until_nul (with c_char -> u8 transmute) since MSRV 1.69
|
|
// https://github.com/ash-rs/ash/pull/746
|
|
unsafe { CStr::from_ptr(inner.name.as_ptr()) }
|
|
}
|
|
|
|
pub fn status(&self) -> i32 {
|
|
let inner = unsafe { self.inner.as_ref() };
|
|
inner.status
|
|
}
|
|
|
|
pub fn flags(&self) -> u32 {
|
|
let inner = unsafe { self.inner.as_ref() };
|
|
inner.flags
|
|
}
|
|
|
|
pub fn num_fences(&self) -> usize {
|
|
let inner = unsafe { self.inner.as_ref() };
|
|
inner.num_fences as usize
|
|
}
|
|
|
|
/// Get the array of fence infos from the sync file's info.
|
|
#[doc(alias = "sync_get_fence_info")]
|
|
pub fn fence_info(&self) -> &[SyncFenceInfo] {
|
|
let inner = unsafe { self.inner.as_ref() };
|
|
|
|
if inner.num_fences == 0 {
|
|
&[]
|
|
} else {
|
|
let sync_fence_info = NonNull::new(inner.sync_fence_info as *mut _)
|
|
.expect("sync_fence_info cannot be null if num_fences > 0");
|
|
unsafe {
|
|
std::slice::from_raw_parts(sync_fence_info.as_ptr(), inner.num_fences as usize)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Drop for SyncFileInfo {
|
|
/// Free a [`struct@ffi::sync_file_info`] structure.
|
|
#[doc(alias = "sync_file_info_free")]
|
|
fn drop(&mut self) {
|
|
unsafe { ffi::sync_file_info_free(self.inner.as_ptr()) }
|
|
}
|
|
}
|
|
|
|
#[doc(alias = "sync_fence_info")]
|
|
#[repr(transparent)]
|
|
pub struct SyncFenceInfo(ffi::sync_fence_info);
|
|
|
|
impl Debug for SyncFenceInfo {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
f.debug_struct("SyncFenceInfo")
|
|
.field("obj_name", &self.obj_name())
|
|
.field("driver_name", &self.driver_name())
|
|
.field("status", &self.status())
|
|
.field("flags", &self.flags())
|
|
.field("timestamp_ns", &self.timestamp_ns())
|
|
.finish()
|
|
}
|
|
}
|
|
|
|
impl SyncFenceInfo {
|
|
pub fn obj_name(&self) -> &CStr {
|
|
// TODO: Switch to CStr::from_bytes_until_nul (with c_char -> u8 transmute) since MSRV 1.69
|
|
unsafe { CStr::from_ptr(self.0.obj_name.as_ptr()) }
|
|
}
|
|
|
|
pub fn driver_name(&self) -> &CStr {
|
|
// TODO: Switch to CStr::from_bytes_until_nul (with c_char -> u8 transmute) since MSRV 1.69
|
|
unsafe { CStr::from_ptr(self.0.driver_name.as_ptr()) }
|
|
}
|
|
|
|
pub fn status(&self) -> i32 {
|
|
self.0.status
|
|
}
|
|
|
|
pub fn flags(&self) -> u32 {
|
|
self.0.flags
|
|
}
|
|
|
|
pub fn timestamp_ns(&self) -> u64 {
|
|
self.0.timestamp_ns
|
|
}
|
|
}
|
|
|
|
/// Merge two sync files.
|
|
///
|
|
/// This produces a new sync file with the given name which has the union of the two original sync
|
|
/// file's fences; redundant fences may be removed.
|
|
///
|
|
/// If one of the input sync files is signaled or invalid, then this function may behave like
|
|
/// `dup()`: the new file descriptor refers to the valid/unsignaled sync file with its original
|
|
/// name, rather than a new sync file.
|
|
pub fn sync_merge(name: &CStr, fd1: BorrowedFd<'_>, fd2: BorrowedFd<'_>) -> OwnedFd {
|
|
unsafe {
|
|
OwnedFd::from_raw_fd(ffi::sync_merge(
|
|
name.as_ptr(),
|
|
fd1.as_raw_fd(),
|
|
fd2.as_raw_fd(),
|
|
))
|
|
}
|
|
}
|