use super::{
denoise::{
DenoiseScratch, FirstHitSnapshot, denoise_and_tonemap_frame, tonemap_running_average,
},
frame_helpers::{FramePayload, push_frame_to_ui},
redraw_gate::RedrawGate,
};
use crate::bridge::pixel_buffer::FramebufferTransfer;
use glam::Vec3;
use indicatrix::{color::metrics::GemOpticalMetrics, renderer::denoise::AtrousDenoiser};
use slint::{ComponentHandle, Weak};
use std::{
sync::{
Arc,
atomic::{AtomicBool, AtomicU64, Ordering},
mpsc::{Receiver, Sender, channel},
},
thread,
time::Duration,
};
#[derive(Clone, Copy)]
pub(super) struct FrameMetricsSnapshot {
pub(super) metrics: GemOpticalMetrics,
pub(super) graph_brilliance: [f32; 19],
pub(super) graph_extinction: [f32; 19],
pub(super) graph_windowing: [f32; 19],
pub(super) cam_pitch_deg: f32,
}
pub(super) struct DisplayWork {
generation: u64,
width: u32,
height: u32,
current_sample_count: u32,
denoise_enabled: bool,
accum: Vec<Vec3>,
depth: Vec<f32>,
normal: Vec<Vec3>,
facet_id: Vec<i32>,
metrics_snapshot: FrameMetricsSnapshot,
}
impl DisplayWork {
const fn empty() -> Self {
Self {
generation: 0,
width: 0,
height: 0,
current_sample_count: 0,
denoise_enabled: true,
accum: Vec::new(),
depth: Vec::new(),
normal: Vec::new(),
facet_id: Vec::new(),
metrics_snapshot: FrameMetricsSnapshot {
metrics: GemOpticalMetrics {
brilliance_pct: 0.0,
fire_index: 0.0,
scintillation_pct: 0.0,
windowing_pct: 0.0,
extinction_pct: 0.0,
},
graph_brilliance: [0.0; 19],
graph_extinction: [0.0; 19],
graph_windowing: [0.0; 19],
cam_pitch_deg: 0.0,
},
}
}
pub(super) fn fill(
&mut self,
generation: u64,
denoise_enabled: bool,
frame: FirstHitSnapshot<'_>,
metrics_snapshot: FrameMetricsSnapshot,
) {
self.generation = generation;
self.width = frame.width;
self.height = frame.height;
self.current_sample_count = frame.current_sample_count;
self.denoise_enabled = denoise_enabled;
self.accum.clear();
self.accum.extend_from_slice(frame.accum_buffer);
self.depth.clear();
self.depth.extend_from_slice(frame.first_hit_depth);
self.normal.clear();
self.normal.extend_from_slice(frame.first_hit_normal);
self.facet_id.clear();
self.facet_id.extend_from_slice(frame.first_hit_facet_id);
self.metrics_snapshot = metrics_snapshot;
}
}
pub(super) struct DisplayHandle {
work_tx: Sender<DisplayWork>,
pool_rx: Receiver<DisplayWork>,
in_flight: Arc<AtomicBool>,
generation: Arc<AtomicU64>,
}
impl DisplayHandle {
pub(super) fn busy(&self) -> bool {
self.in_flight.load(Ordering::Acquire)
}
pub(super) fn bump_generation(&self) {
self.generation.fetch_add(1, Ordering::AcqRel);
}
pub(super) fn current_generation(&self) -> u64 {
self.generation.load(Ordering::Acquire)
}
pub(super) fn reclaim(&self) -> DisplayWork {
self.pool_rx
.try_recv()
.unwrap_or_else(|_| DisplayWork::empty())
}
pub(super) fn send(&self, work: DisplayWork) {
self.in_flight.store(true, Ordering::Release);
let _ = self.work_tx.send(work);
}
}
pub(super) fn spawn_display_thread<T, F, M>(
ui_weak: Weak<T>,
update_image: F,
update_metrics: M,
) -> DisplayHandle
where
T: ComponentHandle + 'static,
F: Fn(&T, slint::SharedPixelBuffer<slint::Rgba8Pixel>) + Send + 'static + Clone,
M: Fn(&T, f32, f32, f32, f32, f32, [f32; 19], [f32; 19], [f32; 19], f32)
+ Send
+ 'static
+ Clone,
{
let (work_tx, work_rx) = channel::<DisplayWork>();
let (pool_tx, pool_rx) = channel::<DisplayWork>();
let in_flight = Arc::new(AtomicBool::new(false));
let generation = Arc::new(AtomicU64::new(0));
let in_flight_thread = Arc::clone(&in_flight);
let generation_thread = Arc::clone(&generation);
let redraw_gate: Arc<RedrawGate<FramePayload>> = Arc::new(RedrawGate::new());
thread::spawn(move || {
let mut denoiser = AtrousDenoiser::new();
let mut avg_color_buf: Vec<Vec3> = Vec::new();
let mut filtered_buf: Vec<Vec3> = Vec::new();
let mut fb_transfer = FramebufferTransfer::new(1, 1);
let mut last_width = 0u32;
let mut last_height = 0u32;
while let Ok(work) = work_rx.recv() {
let stale = work.generation != generation_thread.load(Ordering::Acquire);
if !stale {
if work.width != last_width || work.height != last_height {
fb_transfer = FramebufferTransfer::new(work.width, work.height);
last_width = work.width;
last_height = work.height;
}
let output_bytes = if work.denoise_enabled {
denoise_and_tonemap_frame(
FirstHitSnapshot {
width: work.width,
height: work.height,
current_sample_count: work.current_sample_count,
accum_buffer: &work.accum,
first_hit_depth: &work.depth,
first_hit_normal: &work.normal,
first_hit_facet_id: &work.facet_id,
},
&mut DenoiseScratch {
denoiser: &mut denoiser,
avg_color_buf: &mut avg_color_buf,
filtered_buf: &mut filtered_buf,
},
)
} else {
tonemap_running_average(
work.width,
work.height,
work.current_sample_count,
&work.accum,
)
};
let image = fb_transfer.copy_from_gpu_slice(&output_bytes);
push_frame_to_ui(
&ui_weak,
&update_image,
&update_metrics,
&redraw_gate,
image,
work.metrics_snapshot,
);
}
in_flight_thread.store(false, Ordering::Release);
let _ = pool_tx.send(work);
}
});
DisplayHandle {
work_tx,
pool_rx,
in_flight,
generation,
}
}
pub(super) const CONVERGENCE_WAIT_POLL: Duration = Duration::from_millis(1);