use std::sync::atomic::Ordering;
use glow::HasContext;
use crate::AtomicFenceValue;
#[derive(Debug, Copy, Clone)]
struct GLFence {
sync: glow::Fence,
value: crate::FenceValue,
}
#[derive(Debug)]
pub struct Fence {
last_completed: AtomicFenceValue,
pending: Vec<GLFence>,
fence_mode: wgt::GlFenceBehavior,
}
impl crate::DynFence for Fence {}
#[cfg(send_sync)]
unsafe impl Send for Fence {}
#[cfg(send_sync)]
unsafe impl Sync for Fence {}
impl Fence {
pub fn new(options: &wgt::GlBackendOptions) -> Self {
Self {
last_completed: AtomicFenceValue::new(0),
pending: Vec::new(),
fence_mode: options.short_circuit_fences,
}
}
pub fn signal(
&mut self,
gl: &glow::Context,
value: crate::FenceValue,
) -> Result<(), crate::DeviceError> {
if self.fence_mode.is_auto_finish() {
*self.last_completed.get_mut() = value;
return Ok(());
}
let sync = unsafe { gl.fence_sync(glow::SYNC_GPU_COMMANDS_COMPLETE, 0) }
.map_err(|_| crate::DeviceError::OutOfMemory)?;
self.pending.push(GLFence { sync, value });
Ok(())
}
pub fn satisfied(&self, value: crate::FenceValue) -> bool {
self.last_completed.load(Ordering::Acquire) >= value
}
pub fn get_latest(&self, gl: &glow::Context) -> crate::FenceValue {
let mut max_value = self.last_completed.load(Ordering::Acquire);
if self.fence_mode.is_auto_finish() {
return max_value;
}
for gl_fence in self.pending.iter() {
if gl_fence.value <= max_value {
continue;
}
let status = unsafe { gl.get_sync_status(gl_fence.sync) };
if status == glow::SIGNALED {
max_value = gl_fence.value;
} else {
break;
}
}
self.last_completed.fetch_max(max_value, Ordering::AcqRel);
max_value
}
pub fn maintain(&mut self, gl: &glow::Context) {
if self.fence_mode.is_auto_finish() {
return;
}
let latest = self.get_latest(gl);
for &gl_fence in self.pending.iter() {
if gl_fence.value <= latest {
unsafe {
gl.delete_sync(gl_fence.sync);
}
}
}
self.pending.retain(|&gl_fence| gl_fence.value > latest);
}
pub fn wait(
&self,
gl: &glow::Context,
wait_value: crate::FenceValue,
timeout_ns: u64,
) -> Result<bool, crate::DeviceError> {
let last_completed = self.last_completed.load(Ordering::Acquire);
if self.fence_mode.is_auto_finish() {
return Ok(last_completed >= wait_value);
}
if last_completed >= wait_value {
return Ok(true);
}
let gl_fence = self
.pending
.iter()
.find(|gl_fence| gl_fence.value >= wait_value);
let Some(gl_fence) = gl_fence else {
log::warn!("Tried to wait for {wait_value} but that value has not been signalled yet");
return Ok(false);
};
debug_assert_eq!(gl_fence.value, wait_value);
let status = unsafe {
gl.client_wait_sync(
gl_fence.sync,
glow::SYNC_FLUSH_COMMANDS_BIT,
timeout_ns as i32,
)
};
let signalled = match status {
glow::ALREADY_SIGNALED | glow::CONDITION_SATISFIED => true,
glow::TIMEOUT_EXPIRED | glow::WAIT_FAILED => false,
_ => {
log::warn!("Unexpected result from client_wait_sync: {status}");
false
}
};
if signalled {
self.last_completed.fetch_max(wait_value, Ordering::AcqRel);
}
Ok(signalled)
}
pub fn destroy(self, gl: &glow::Context) {
if self.fence_mode.is_auto_finish() {
return;
}
for gl_fence in self.pending {
unsafe {
gl.delete_sync(gl_fence.sync);
}
}
}
}