molgfx_render/engine/
sequence.rs1use super::image::{ImagePurpose, PendingImage};
4use super::{Engine, Image, ImageConfig};
5use crate::RenderError;
6use molgfx_core::Scene;
7use molgfx_gpu::{Device, Queue as _};
8use molgfx_math::Camera;
9use std::collections::VecDeque;
10use std::fmt;
11
12#[derive(Clone, Copy, PartialEq, Eq, Debug)]
14pub struct SequenceConfig {
15 pub image: ImageConfig,
17 pub timebase_nanoseconds: u64,
19 pub max_in_flight: u8,
21}
22
23impl SequenceConfig {
24 pub fn at_fps(
31 image: ImageConfig,
32 frames_per_second: u32,
33 max_in_flight: u8,
34 ) -> Result<Self, RenderError> {
35 let timebase_nanoseconds = 1_000_000_000_u64
36 .checked_div(u64::from(frames_per_second))
37 .filter(|value| *value > 0)
38 .ok_or(RenderError::InvalidSequence {
39 reason: "frame rate must map to a positive nanosecond interval",
40 })?;
41 let config = Self {
42 image,
43 timebase_nanoseconds,
44 max_in_flight,
45 };
46 config.validate()?;
47 Ok(config)
48 }
49
50 fn validate(self) -> Result<(), RenderError> {
51 if self.timebase_nanoseconds == 0 {
52 return Err(RenderError::InvalidSequence {
53 reason: "timebase must be positive",
54 });
55 }
56 if !(2..=3).contains(&self.max_in_flight) {
57 return Err(RenderError::InvalidSequence {
58 reason: "max_in_flight must be two or three",
59 });
60 }
61 Ok(())
62 }
63}
64
65#[derive(Clone, Copy, PartialEq, Eq, Debug)]
67pub struct FrameTicket {
68 pub index: u64,
70 pub timestamp: u64,
72}
73
74#[derive(Clone, PartialEq, Eq, Debug)]
76pub struct SequenceFrame {
77 pub ticket: FrameTicket,
79 pub image: Image,
81}
82
83struct PendingSequence<D: Device> {
84 ticket: FrameTicket,
85 image: PendingImage<D>,
86}
87
88pub struct SequenceRenderer<D: Device> {
93 config: SequenceConfig,
94 pending: VecDeque<PendingSequence<D>>,
95 next_index: u64,
96 last_timestamp: Option<u64>,
97}
98
99impl<D: Device> fmt::Debug for SequenceRenderer<D> {
100 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
101 formatter
102 .debug_struct("SequenceRenderer")
103 .field("config", &self.config)
104 .field("pending", &self.pending.len())
105 .field("next_index", &self.next_index)
106 .field("last_timestamp", &self.last_timestamp)
107 .finish_non_exhaustive()
108 }
109}
110
111impl<D: Device> Engine<D> {
112 pub fn sequence(&self, config: SequenceConfig) -> Result<SequenceRenderer<D>, RenderError> {
118 config.validate()?;
119 config
120 .image
121 .validate(self.device.capabilities().max_texture_dim)?;
122 Ok(SequenceRenderer {
123 config,
124 pending: VecDeque::with_capacity(usize::from(config.max_in_flight)),
125 next_index: 0,
126 last_timestamp: None,
127 })
128 }
129}
130
131impl<D: Device> SequenceRenderer<D> {
132 pub fn submit(
139 &mut self,
140 engine: &mut Engine<D>,
141 scene: &Scene,
142 camera: &Camera,
143 timestamp: u64,
144 ) -> Result<FrameTicket, RenderError> {
145 if self.pending.len() == usize::from(self.config.max_in_flight) {
146 return Err(RenderError::SequenceBackpressure {
147 max_in_flight: self.config.max_in_flight,
148 });
149 }
150 if self
151 .last_timestamp
152 .is_some_and(|previous| timestamp <= previous)
153 {
154 return Err(RenderError::InvalidSequence {
155 reason: "timestamps must increase strictly",
156 });
157 }
158 let ticket = FrameTicket {
159 index: self.next_index,
160 timestamp,
161 };
162 self.next_index = self
163 .next_index
164 .checked_add(1)
165 .ok_or(RenderError::InvalidSequence {
166 reason: "frame index exhausted",
167 })?;
168 let image = engine.render_image_to_buffer(
169 scene,
170 camera,
171 self.config.image,
172 ImagePurpose::SequenceFrame,
173 )?;
174 self.pending.push_back(PendingSequence { ticket, image });
175 self.last_timestamp = Some(timestamp);
176 Ok(ticket)
177 }
178
179 pub fn poll(&mut self, engine: &mut Engine<D>) -> Result<Option<SequenceFrame>, RenderError> {
185 let Some(front) = self.pending.front() else {
186 return Ok(None);
187 };
188 let completed = engine.queue.completed_fence(&engine.device)?;
189 if completed < front.image.completion() {
190 return Ok(None);
191 }
192 self.resolve_front(engine).map(Some)
193 }
194
195 pub fn finish(mut self, engine: &mut Engine<D>) -> Result<Vec<SequenceFrame>, RenderError> {
201 let mut frames = Vec::with_capacity(self.pending.len());
202 while !self.pending.is_empty() {
203 frames.push(self.resolve_front(engine)?);
204 }
205 Ok(frames)
206 }
207
208 #[must_use]
210 pub fn pending(&self) -> usize {
211 self.pending.len()
212 }
213
214 fn resolve_front(&mut self, engine: &mut Engine<D>) -> Result<SequenceFrame, RenderError> {
215 let Some(pending) = self.pending.pop_front() else {
216 return Err(RenderError::InvalidSequence {
217 reason: "no sequence frame is pending",
218 });
219 };
220 let (buffer, size) = pending.image.readback();
221 let mapped = engine
222 .queue
223 .read_buffer_blocking(&engine.device, buffer, 0, size)?;
224 let image = pending.image.resolve(mapped, engine.target_format)?;
225 Ok(SequenceFrame {
226 ticket: pending.ticket,
227 image,
228 })
229 }
230}
231
232#[cfg(test)]
233#[path = "sequence_tests.rs"]
234mod tests;