use std::sync::atomic::AtomicBool;
use glam::Vec3;
use crate::{
geometry::GpuFacetPlane,
optics::{
materials::GemMaterial,
raytracer::{Camera, EnvironmentSource, FacetFinish},
},
};
#[cfg(feature = "gpu")]
use super::gpu::{AccumulateOutcome, GpuFrameError};
#[cfg_attr(not(feature = "gpu"), allow(dead_code))]
pub struct GpuSceneRef<'a> {
pub camera: &'a Camera,
pub width: u32,
pub height: u32,
pub planes: &'a [GpuFacetPlane],
pub facet_finishes: &'a [FacetFinish],
pub material: &'a GemMaterial,
pub max_bounces: u32,
pub environment: EnvironmentSource<'a>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum GpuAccumulate {
Done,
Declined,
Cancelled,
}
#[cfg(feature = "gpu")]
pub struct GpuBackend {
renderer: Option<std::sync::Mutex<super::gpu::GpuFrameRenderer>>,
lost: AtomicBool,
}
#[cfg(feature = "gpu")]
impl GpuBackend {
#[must_use]
pub fn acquire() -> Self {
let renderer = match super::gpu::GpuFrameRenderer::new() {
Ok(r) => {
tracing::info!(adapter = r.adapter_label(), "GPU render backend active");
Some(std::sync::Mutex::new(r))
}
Err(e) => {
tracing::info!("GPU render backend unavailable, using CPU tracer: {e}");
None
}
};
Self {
renderer,
lost: AtomicBool::new(false),
}
}
#[must_use]
pub const fn disabled() -> Self {
Self {
renderer: None,
lost: AtomicBool::new(false),
}
}
#[must_use]
pub fn adapter_label(&self) -> Option<String> {
if self.lost.load(std::sync::atomic::Ordering::Relaxed) {
return None;
}
self.renderer.as_ref().map(|m| {
m.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.adapter_label()
.to_string()
})
}
pub fn try_accumulate(
&self,
scene: &GpuSceneRef<'_>,
sample_offset: u32,
spp: u32,
accum: &mut [Vec3],
) -> bool {
let never_cancel = AtomicBool::new(false);
matches!(
self.try_accumulate_cancellable(scene, sample_offset, spp, accum, &never_cancel),
GpuAccumulate::Done
)
}
pub fn try_accumulate_cancellable(
&self,
scene: &GpuSceneRef<'_>,
sample_offset: u32,
spp: u32,
accum: &mut [Vec3],
cancel: &AtomicBool,
) -> GpuAccumulate {
if self.lost.load(std::sync::atomic::Ordering::Relaxed) {
return GpuAccumulate::Declined;
}
let Some(mutex) = &self.renderer else {
return GpuAccumulate::Declined;
};
let mut renderer = mutex
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
let gpu_scene = super::gpu::GpuFrameScene {
camera: scene.camera,
width: scene.width,
height: scene.height,
planes: scene.planes,
facet_finishes: scene.facet_finishes,
material: scene.material,
max_bounces: scene.max_bounces,
environment: scene.environment,
};
match renderer.accumulate_cancellable(&gpu_scene, sample_offset, spp, accum, cancel) {
Ok(AccumulateOutcome::Done) => GpuAccumulate::Done,
Ok(AccumulateOutcome::Cancelled) => GpuAccumulate::Cancelled,
Err(GpuFrameError::DeviceLost(why)) => {
tracing::warn!(
"GPU device lost ({why}), permanently disabling the GPU backend for the \
rest of this process -- every later call falls back to the CPU tracer"
);
self.lost.store(true, std::sync::atomic::Ordering::Relaxed);
GpuAccumulate::Declined
}
Err(e) => {
tracing::debug!("GPU declined this dispatch, using CPU tracer: {e}");
GpuAccumulate::Declined
}
}
}
}
#[cfg(not(feature = "gpu"))]
pub struct GpuBackend;
#[cfg(not(feature = "gpu"))]
impl GpuBackend {
#[must_use]
pub const fn acquire() -> Self {
Self
}
#[must_use]
pub const fn disabled() -> Self {
Self
}
#[must_use]
pub const fn adapter_label(&self) -> Option<String> {
None
}
#[allow(
clippy::unused_self,
reason = "signature must match the `gpu`-gated GpuBackend::try_accumulate"
)]
pub const fn try_accumulate(
&self,
_scene: &GpuSceneRef<'_>,
_sample_offset: u32,
_spp: u32,
_accum: &mut [Vec3],
) -> bool {
false
}
pub const fn try_accumulate_cancellable(
&self,
_scene: &GpuSceneRef<'_>,
_sample_offset: u32,
_spp: u32,
_accum: &mut [Vec3],
_cancel: &AtomicBool,
) -> GpuAccumulate {
GpuAccumulate::Declined
}
}