Commit 6dfb54e27e7f
tarball6 files changed+155-31
@@ -1,12 +1,18 @@
11
use ash::vk;22
use smithay::backend::allocator::Fourcc;3
+use std::sync::atomic::{AtomicBool, AtomicU32, Ordering};4
+use std::time::Duration;35
46
#[derive(Debug, thiserror::Error)]57
pub enum Error {68
#[error("failed to load the Vulkan loader: {0}")]79
Loading(#[from] ash::LoadingError),810
#[error("Vulkan call failed: {0}")]9
- Vulkan(#[from] vk::Result),11
+ Vulkan(vk::Result),12
+ #[error("the GPU was lost")]13
+ DeviceLost,14
+ #[error("the GPU did not finish in time")]15
+ Timeout,1016
#[error("GPU memory allocation failed: {0}")]1117
Allocation(#[from] gpu_allocator::AllocationError),1218
#[error("no Vulkan 1.3 device with dynamic rendering, synchronization2 and push descriptors")]@@ -30,3 +36,85 @@ pub enum Error {
3036
#[error("invalid size {0}x{1}")]3137
InvalidSize(i32, i32),3238
}39
+40
+/// How long any wait for the GPU may last.41
+pub(crate) const WAIT: Duration = Duration::from_secs(3);42
+43
+/// Consecutive timed out waits after which the GPU counts as hung.44
+const HANG_AFTER: u32 = 3;45
+46
+/// Set once a call reports the device lost, and never cleared. A compositor47
+/// has one device, so the state is process wide, which lets every `?` on a48
+/// `vk::Result` record it without threading a handle through.49
+static LOST: AtomicBool = AtomicBool::new(false);50
+static HUNG: AtomicBool = AtomicBool::new(false);51
+static TIMEOUTS: AtomicU32 = AtomicU32::new(0);52
+53
+/// Whether the GPU is gone or stopped answering, so rendering cannot go on.54
+pub fn device_lost() -> bool {55
+ LOST.load(Ordering::Relaxed) || HUNG.load(Ordering::Relaxed)56
+}57
+58
+/// Whether waiting for the GPU timed out repeatedly. Tearing the device down59
+/// then would wait for work that never ends.60
+pub(crate) fn hung() -> bool {61
+ HUNG.load(Ordering::Relaxed)62
+}63
+64
+/// Records what a failed call says about the device.65
+pub(crate) fn observe(result: vk::Result) {66
+ match result {67
+ vk::Result::ERROR_DEVICE_LOST => LOST.store(true, Ordering::Relaxed),68
+ vk::Result::TIMEOUT => {69
+ let timeouts = TIMEOUTS.fetch_add(1, Ordering::Relaxed) + 1;70
+ HUNG.fetch_or(timeouts >= HANG_AFTER, Ordering::Relaxed);71
+ }72
+ _ => {}73
+ }74
+}75
+76
+/// Records that a wait for the GPU finished.77
+pub(crate) fn waited() {78
+ TIMEOUTS.store(0, Ordering::Relaxed);79
+}80
+81
+impl From<vk::Result> for Error {82
+ fn from(result: vk::Result) -> Self {83
+ observe(result);84
+ match result {85
+ vk::Result::ERROR_DEVICE_LOST => Self::DeviceLost,86
+ vk::Result::TIMEOUT => Self::Timeout,87
+ other => Self::Vulkan(other),88
+ }89
+ }90
+}91
+92
+/// What a failed `SyncPoint::wait`, which says nothing itself, meant.93
+pub(crate) fn wait_error() -> Error {94
+ if device_lost() {95
+ Error::DeviceLost96
+ } else {97
+ Error::Interrupted98
+ }99
+}100
+101
+#[cfg(test)]102
+mod tests {103
+ use super::*;104
+105
+ #[test]106
+ fn lost_device_and_repeated_timeouts_are_fatal() {107
+ assert!(!device_lost());108
+ assert!(matches!(Error::from(vk::Result::TIMEOUT), Error::Timeout));109
+ waited();110
+ assert!(matches!(Error::from(vk::Result::TIMEOUT), Error::Timeout));111
+ assert!(matches!(Error::from(vk::Result::TIMEOUT), Error::Timeout));112
+ assert!(!device_lost());113
+ assert!(matches!(Error::from(vk::Result::TIMEOUT), Error::Timeout));114
+ assert!(hung() && device_lost());115
+ assert!(matches!(116
+ Error::from(vk::Result::ERROR_DEVICE_LOST),117
+ Error::DeviceLost118
+ ));119
+ }120
+}@@ -302,7 +302,7 @@ impl Frame for VulkanFrame<'_, '_> {
302302
}303303
304304
fn wait(&mut self, sync: &SyncPoint) -> Result<(), Error> {305
- sync.wait().map_err(|_| Error::Interrupted)305
+ sync.wait().map_err(|_| crate::error::wait_error())306306
}307307
308308
fn finish(mut self) -> Result<SyncPoint, Error> {@@ -23,7 +23,7 @@ mod sync;
2323
mod transform;2424
2525
pub use dmabuf::VulkanAllocator;26
-pub use error::Error;26
+pub use error::{Error, device_lost};2727
pub use frame::{Clip, Effect, Transition, VulkanFrame};2828
pub use image::{VulkanImage, VulkanMapping, VulkanTarget, VulkanTexture};2929
pub use renderer::{DmabufImporter, ShmStager, StagedShm, VulkanRenderer};@@ -209,19 +209,27 @@ impl VulkanRenderer {
209209
self.samplers[(idx(self.min_filter) << 1) | idx(self.mag_filter)]210210
}211211
212
- fn reap(&mut self) {212
+ /// Recycles finished work. A fence that cannot be queried stays213
+ /// pending and fails the call, since its work may never have finished.214
+ fn reap(&mut self) -> Result<(), Error> {213215
let spare = &mut self.spare_cmds;214
- self.pending.retain(|p| {215
- if !p.fence.signaled() {216
- return true;216
+ let mut failed = None;217
+ self.pending.retain(|p| match p.fence.status() {218
+ Ok(false) => true,219
+ Ok(true) => {220
+ spare.push(p.cmd);221
+ false222
+ }223
+ Err(e) => {224
+ failed.get_or_insert(e);225
+ true217226
}218
- spare.push(p.cmd);219
- false220227
});221228
self.dmabuf_textures.retain(|k, _| !k.is_gone());222229
self.dmabuf_targets.retain(|k, _| !k.is_gone());223230
self.staged.retain(|_, (b, _)| b.is_alive());224231
self.staging_pool.trim();232
+ failed.map_or(Ok(()), |e| Err(e.into()))225233
}226234
227235
fn begin(&mut self) -> Result<vk::CommandBuffer, Error> {@@ -414,7 +422,7 @@ impl VulkanRenderer {
414422
target: &mut VulkanTarget<'_>,415423
ops: Vec<Op>,416424
) -> Result<SyncPoint, Error> {417
- self.reap();425
+ self.reap()?;418426
let image = target.0.clone();419427
let mut sampled: Vec<Arc<ImageInner>> = Vec::new();420428
for op in &ops {@@ -605,7 +613,7 @@ impl VulkanRenderer {
605613
if image.external {606614
return Err(Error::ForeignTexture);607615
}608
- self.reap();616
+ self.reap()?;609617
if region.size.w <= 0 || region.size.h <= 0 {610618
return Ok(());611619
}@@ -749,7 +757,9 @@ impl Drop for VulkanRenderer {
749757
fn drop(&mut self) {750758
let d = &self.shared.device.device;751759
unsafe {752
- let _ = d.device_wait_idle();760
+ if !crate::error::hung() {761
+ let _ = d.device_wait_idle();762
+ }753763
for p in self.pending.drain(..) {754764
d.free_command_buffers(self.pool, &[p.cmd]);755765
}@@ -823,7 +833,7 @@ impl Renderer for VulkanRenderer {
823833
'buffer: 'frame,824834
{825835
self.own(&framebuffer.0)?;826
- self.reap();836
+ self.reap()?;827837
Ok(VulkanFrame::new(828838
self,829839
framebuffer,@@ -833,16 +843,16 @@ impl Renderer for VulkanRenderer {
833843
}834844
835845
fn wait(&mut self, sync: &SyncPoint) -> Result<(), Error> {836
- sync.wait().map_err(|_| Error::Interrupted)846
+ sync.wait().map_err(|_| crate::error::wait_error())837847
}838848
839849
fn cleanup_texture_cache(&mut self) -> Result<(), Error> {840
- self.reap();850
+ self.reap()?;841851
Ok(())842852
}843853
844854
fn invalidate_caches(&mut self) -> Result<(), Error> {845
- self.reap();855
+ self.reap()?;846856
self.dmabuf_textures.clear();847857
self.dmabuf_targets.clear();848858
Ok(())@@ -49,7 +49,7 @@ impl VulkanRenderer {
4949
/// right after a flip. NVIDIA stalls for milliseconds on every 1024th5050
/// submission, see research/nvidia.md.5151
pub fn absorb_submit_stall(&mut self) -> Result<(), Error> {52
- self.reap();52
+ self.reap()?;5353
let Some(phase) = self.stall.get().phase else {5454
return Ok(());5555
};@@ -1,4 +1,5 @@
11
use crate::device::Shared;2
+use crate::error::{WAIT, observe, waited};23
use ash::vk;34
use rustix::event::{PollFd, PollFlags, poll};45
use smithay::backend::renderer::sync::{Fence, Interrupted};@@ -55,36 +56,58 @@ impl FenceInner {
5556
self.fence5657
}5758
58
- pub fn signaled(&self) -> bool {59
+ /// Whether the GPU has signalled, or the error the query failed with.60
+ pub fn status(&self) -> Result<bool, vk::Result> {5961
match &*self.state.lock().unwrap_or_else(|e| e.into_inner()) {60
- State::Pending => {61
- unsafe { self.shared.device.device.get_fence_status(self.fence) }.unwrap_or(true)62
- }63
- State::Exported(None) => true,64
- State::Exported(Some(fd)) => readable(fd, Some(&Default::default())),62
+ State::Pending => unsafe { self.shared.device.device.get_fence_status(self.fence) },63
+ State::Exported(None) => Ok(true),64
+ State::Exported(Some(fd)) => Ok(readable(fd, Some(&Default::default()))),6565
}6666
}6767
68
+ /// Like `status` for callers that cannot report a failure. A failed query69
+ /// counts as signalled so nobody waits on a fence that cannot signal.70
+ pub fn signaled(&self) -> bool {71
+ self.status().unwrap_or_else(|e| {72
+ observe(e);73
+ true74
+ })75
+ }76
+6877
/// Blocks until the GPU signals. A pending fence is first turned into its6978
/// sync_file and polled, because waiting on the Vulkan fence itself sleeps7079
/// in 10 ms steps on NVIDIA even when the work is done in microseconds.80
+ /// Gives up with `TIMEOUT` after [`WAIT`], so a hung GPU cannot freeze the81
+ /// caller.7182
pub fn wait(&self) -> Result<(), vk::Result> {7283
if self.exportable {7384
drop(self.export());7485
}75
- match &*self.state.lock().unwrap_or_else(|e| e.into_inner()) {86
+ let waited_for = match &*self.state.lock().unwrap_or_else(|e| e.into_inner()) {7687
State::Pending => unsafe {77
- self.shared78
- .device79
- .device80
- .wait_for_fences(&[self.fence], true, u64::MAX)88
+ self.shared.device.device.wait_for_fences(89
+ &[self.fence],90
+ true,91
+ WAIT.as_nanos() as u64,92
+ )8193
},8294
State::Exported(None) => Ok(()),8395
State::Exported(Some(fd)) => {84
- readable(fd, None);85
- Ok(())96
+ let timeout = rustix::event::Timespec {97
+ tv_sec: WAIT.as_secs() as _,98
+ tv_nsec: 0,99
+ };100
+ if readable(fd, Some(&timeout)) {101
+ Ok(())102
+ } else {103
+ Err(vk::Result::TIMEOUT)104
+ }86105
}106
+ };107
+ if waited_for.is_ok() {108
+ waited();87109
}110
+ waited_for88111
}89112
90113
#[profiling::function]@@ -157,7 +180,10 @@ impl Fence for VkFence {
157180
}158181
159182
fn wait(&self) -> Result<(), Interrupted> {160
- self.0.wait().map_err(|_| Interrupted)183
+ self.0.wait().map_err(|e| {184
+ observe(e);185
+ Interrupted186
+ })161187
}162188
163189
fn is_exportable(&self) -> bool {