Three root causes, all fixed and verified on the Pixel 9 Pro: 1. Present phase (backend + renderer): VSYNC_LOCKED_RENDER defers the render to the vsync wake; RENDER_AHEAD presents the previous frame at the start of render() (phase = wake + ~1.1 ms) and holds the new frame one slot. Crash fix: a held wgpu frame must be PRESENTED, never merely dropped, before the next acquire (AlreadyAcquired panic at launch otherwise); discard_pending_frame presents too. 2. Full-res prefetch churn (src/lib.rs): the 8-slot full cache cycled through a ~20-cell viewport forever (137 decodes/20 s) and each eviction free()'d a 50 MB Vec on the UI thread (20-52 ms CPU spikes, ~7/s = the residual SLOWEV NonMain jank). Fix: 60 s tombstone cooldown on evicted paths (idle prefetch skips, urgent tap bypasses) + off-thread reaper for the freed blobs (insert: 21 ms -> 4 us). 3. (earlier) Skia final flush + Mailbox present mode (vendor, M3). Instrumentation (all behind --features perf): OTHR/INVK/INVK_TAG/FLCK lines for cross-thread closure timing and push-site tagging. Warm measured swipe, 8580-file grid: cadence median 8.33 ms (exact 120 Hz), p99 15.5 ms, 5/552 gaps >16.7 ms (0.9%, max 28.3 ms, zero >33 ms); PRES phase median ~994 us, zero late frames. Docs updated (docs/BUGS/scroll-jank-120hz.md, full scorecard + line reference). Also: vendored i-slint-core + wgpu-hal (vkshim interposition patch), vkshim profiling tooling, backend made a non-optional dep, debuggable flag for simpleperf (REMOVE before shipping).
253 lines
9.0 KiB
Rust
253 lines
9.0 KiB
Rust
#![cfg(drm)]
|
|
|
|
use alloc::{string::ToString, vec::Vec};
|
|
use core::{mem::MaybeUninit, num::NonZeroU32};
|
|
use std::os::fd::{AsFd, BorrowedFd};
|
|
|
|
use ash::{ext, khr, vk};
|
|
|
|
use drm::{
|
|
self,
|
|
control::{self, connector, Device as _},
|
|
};
|
|
|
|
struct Card(i32);
|
|
impl AsFd for Card {
|
|
fn as_fd(&self) -> BorrowedFd<'_> {
|
|
unsafe { BorrowedFd::borrow_raw(self.0) }
|
|
}
|
|
}
|
|
impl control::Device for Card {}
|
|
impl drm::Device for Card {}
|
|
|
|
macro_rules! to_u64 {
|
|
($expr:expr) => {{
|
|
#[allow(trivial_numeric_casts)]
|
|
let expr = $expr as u64;
|
|
assert!(size_of_val(&expr) <= size_of::<u64>());
|
|
expr
|
|
}};
|
|
}
|
|
|
|
impl super::Instance {
|
|
/// Creates a new surface from the given drm fd and plane, deriving the connector and mode.
|
|
///
|
|
/// # Safety
|
|
///
|
|
/// - All parameters must point to valid DRM values.
|
|
pub fn create_surface_from_drm_plane(
|
|
&self,
|
|
fd: i32,
|
|
plane: u32,
|
|
) -> Result<super::Surface, crate::InstanceError> {
|
|
let card = Card(fd);
|
|
let plane_info = card
|
|
.get_plane(
|
|
NonZeroU32::new(plane)
|
|
.ok_or(crate::InstanceError::new("Invalid drm plane".to_string()))?
|
|
.into(),
|
|
)
|
|
.map_err(|e| crate::InstanceError::with_source("drm plane not found".to_string(), e))?;
|
|
let crtc_handle = plane_info.crtc().ok_or(crate::InstanceError::new(
|
|
"No CRTC for drm plane".to_string(),
|
|
))?;
|
|
let crtc_info = card
|
|
.get_crtc(crtc_handle)
|
|
.map_err(|e| crate::InstanceError::with_source("drm CRTC not found".to_string(), e))?;
|
|
let mode = crtc_info.mode().ok_or(crate::InstanceError::new(
|
|
"No mode for drm CRTC".to_string(),
|
|
))?;
|
|
|
|
let mut connector_and_mode = None;
|
|
let resources = card.resource_handles().map_err(|e| {
|
|
crate::InstanceError::with_source("No drm resource handles found".to_string(), e)
|
|
})?;
|
|
for connector_handle in resources.connectors() {
|
|
let Ok(connector_info) = card.get_connector(*connector_handle, false) else {
|
|
continue;
|
|
};
|
|
if connector_info.state() != connector::State::Connected {
|
|
continue;
|
|
}
|
|
|
|
if let Some(encoder_handle) = connector_info.current_encoder() {
|
|
if let Ok(encoder_info) = card.get_encoder(encoder_handle) {
|
|
if encoder_info.crtc() == Some(crtc_handle) {
|
|
connector_and_mode = Some((*connector_handle, mode));
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
let (connector_handle, mode) = connector_and_mode.ok_or(crate::InstanceError::new(
|
|
"Failed to derive drm connector and mode for plane".to_string(),
|
|
))?;
|
|
let (width, height) = mode.size();
|
|
// Rate in millihertz
|
|
let refresh_rate = (((mode.clock() as f64 * 1000.0)
|
|
/ (mode.hsync().2 as f64 * mode.vsync().2 as f64))
|
|
* 1000.0)
|
|
.round() as u32;
|
|
unsafe {
|
|
self.create_surface_from_drm(
|
|
fd,
|
|
plane,
|
|
connector_handle.into(),
|
|
width as u32,
|
|
height as u32,
|
|
refresh_rate,
|
|
)
|
|
}
|
|
}
|
|
|
|
/// Creates a new surface from the given drm configuration.
|
|
///
|
|
/// # Safety
|
|
///
|
|
/// - All parameters must point to valid DRM values.
|
|
pub unsafe fn create_surface_from_drm(
|
|
&self,
|
|
fd: i32,
|
|
plane: u32,
|
|
connector_id: u32,
|
|
width: u32,
|
|
height: u32,
|
|
refresh_rate: u32,
|
|
) -> Result<super::Surface, crate::InstanceError> {
|
|
if !self
|
|
.shared
|
|
.extensions
|
|
.contains(&ext::acquire_drm_display::NAME)
|
|
{
|
|
return Err(crate::InstanceError::new(
|
|
"Vulkan driver does not support VK_EXT_acquire_drm_display".to_string(),
|
|
));
|
|
}
|
|
|
|
let drm_stat = {
|
|
let mut stat = MaybeUninit::<libc::stat>::uninit();
|
|
|
|
if unsafe { libc::fstat(fd, stat.as_mut_ptr()) } != 0 {
|
|
return Err(crate::InstanceError::new(
|
|
"Unable to fstat drm device".to_string(),
|
|
));
|
|
}
|
|
|
|
unsafe { stat.assume_init() }
|
|
};
|
|
|
|
let raw_devices = match unsafe { self.shared.raw.enumerate_physical_devices() } {
|
|
Ok(devices) => devices,
|
|
Err(err) => {
|
|
log::error!("enumerate_adapters: {err}");
|
|
Vec::new()
|
|
}
|
|
};
|
|
|
|
let mut physical_device = None;
|
|
|
|
for device in raw_devices {
|
|
let properties2 = vk::PhysicalDeviceProperties2KHR::default();
|
|
|
|
let mut drm_props = vk::PhysicalDeviceDrmPropertiesEXT::default();
|
|
let mut properties2 = properties2.push_next(&mut drm_props);
|
|
|
|
unsafe {
|
|
self.shared
|
|
.raw
|
|
.get_physical_device_properties2(device, &mut properties2)
|
|
};
|
|
|
|
/*
|
|
The makedev call is just bit manipulation to combine major and minor device numbers into a Unix device ID.
|
|
It doesn't perform any filesystem operations, only bitshifting.
|
|
See: https://github.com/rust-lang/libc/blob/268e1b3810ac07ed637d9005bc1a54e49218c958/src/unix/linux_like/linux/mod.rs#L6049
|
|
We use the resulting device IDs to check if the Vulkan raw device from enumerate_physical_devices
|
|
matches the DRM device referred to by our file descriptor.
|
|
*/
|
|
|
|
let primary_devid =
|
|
libc::makedev(drm_props.primary_major as _, drm_props.primary_minor as _);
|
|
let render_devid =
|
|
libc::makedev(drm_props.render_major as _, drm_props.render_minor as _);
|
|
|
|
// On most platforms, both `*_devid`s and `st_rdev` are `dev_t`s (which is generally
|
|
// observed to be an unsigned integral type no greater than 64 bits). However, on some
|
|
// platforms, there divergences from this pattern:
|
|
//
|
|
// - `armv7-linux-androideabi`: `dev_t` is `c_ulong`, and `*_devid`s are `dev_t`, but
|
|
// `st_rdev` is `c_ulonglong`. So, we can't just do a `==` comparison.
|
|
// - OpenBSD has `dev_t` on both sides, but is `i32` (N.B., unsigned). Therefore, we
|
|
// can't just use `u64::from`.
|
|
#[allow(clippy::useless_conversion)]
|
|
if [primary_devid, render_devid]
|
|
.map(|devid| to_u64!(devid))
|
|
.contains(&to_u64!(drm_stat.st_rdev))
|
|
{
|
|
physical_device = Some(device)
|
|
}
|
|
}
|
|
|
|
let physical_device = physical_device.ok_or(crate::InstanceError::new(
|
|
"Failed to find suitable drm device".to_string(),
|
|
))?;
|
|
|
|
let acquire_drm_display_instance =
|
|
ext::acquire_drm_display::Instance::new(&self.shared.entry, &self.shared.raw);
|
|
|
|
let display = unsafe {
|
|
acquire_drm_display_instance
|
|
.get_drm_display(physical_device, fd, connector_id)
|
|
.expect("Failed to get drm display")
|
|
};
|
|
|
|
unsafe {
|
|
acquire_drm_display_instance
|
|
.acquire_drm_display(physical_device, fd, display)
|
|
.expect("Failed to acquire drm display")
|
|
}
|
|
|
|
let display_instance = khr::display::Instance::new(&self.shared.entry, &self.shared.raw);
|
|
|
|
let modes = unsafe {
|
|
display_instance
|
|
.get_display_mode_properties(physical_device, display)
|
|
.expect("Failed to get display modes")
|
|
};
|
|
|
|
let mut mode = None;
|
|
|
|
for current_mode in modes {
|
|
log::trace!(
|
|
"Comparing mode {}x{}@{} with {width}x{height}@{refresh_rate}",
|
|
current_mode.parameters.visible_region.width,
|
|
current_mode.parameters.visible_region.height,
|
|
current_mode.parameters.refresh_rate
|
|
);
|
|
if current_mode.parameters.refresh_rate == refresh_rate
|
|
&& current_mode.parameters.visible_region.width == width
|
|
&& current_mode.parameters.visible_region.height == height
|
|
{
|
|
mode = Some(current_mode)
|
|
}
|
|
}
|
|
|
|
let mode = mode.ok_or(crate::InstanceError::new(
|
|
"Failed to find suitable display mode".to_string(),
|
|
))?;
|
|
|
|
let create_info = vk::DisplaySurfaceCreateInfoKHR::default()
|
|
.display_mode(mode.display_mode)
|
|
.image_extent(mode.parameters.visible_region)
|
|
.transform(vk::SurfaceTransformFlagsKHR::IDENTITY)
|
|
.alpha_mode(vk::DisplayPlaneAlphaFlagsKHR::OPAQUE)
|
|
.plane_index(plane);
|
|
|
|
let surface = unsafe { display_instance.create_display_plane_surface(&create_info, None) }
|
|
.expect("Failed to create DRM surface");
|
|
|
|
Ok(self.create_surface_from_vk_surface_khr(surface, None))
|
|
}
|
|
}
|