1use std::collections::{BTreeMap, HashSet, VecDeque};
2use std::num::NonZeroU64;
3use std::sync::mpsc::{Receiver, Sender, TryRecvError, channel};
4
5#[cfg(feature = "multi-viewport")]
6use crate::render::snapshot::capture_platform_io;
7use crate::render::snapshot::{
8 DetachedRendererConsumer, FrameSnapshot, PendingSnapshot, PendingTextureRequest,
9 RendererConsumerCapability, RendererConsumerError, SnapshotCompletionOutcome,
10 SnapshotCompletionProgress, SnapshotEpoch, SnapshotError, SnapshotMessage, SnapshotTextureId,
11 SynchronousRendererConsumer, TextureFeedback, TextureRequest, TextureRequestKey,
12 capture_draw_data, capture_texture_requests_only, finalize_texture_requests,
13 validate_texture_feedback,
14};
15
16use super::binding::CTX_MUTEX;
17use super::texture_registry::{
18 FontAtlasSnapshotTarget, FontAtlasTextureTarget, ManagedTextureRegistry,
19};
20use super::{Context, ContextId};
21
22#[derive(Copy, Clone, Debug, Eq, PartialEq)]
23enum ConsumerPhase {
24 Unbound,
25 Active {
26 generation: NonZeroU64,
27 mode: ConsumerMode,
28 },
29 Draining(NonZeroU64),
30}
31
32#[derive(Copy, Clone, Debug, Eq, PartialEq)]
33enum ConsumerMode {
34 Synchronous,
35 Detached,
36}
37
38fn consumer_generation_raw(consumer: &(impl RendererConsumerCapability + ?Sized)) -> NonZeroU64 {
39 NonZeroU64::new(consumer.generation())
40 .expect("renderer consumer generations are always non-zero")
41}
42
43#[derive(Debug)]
44struct OutstandingEpoch {
45 epoch: SnapshotEpoch,
46 expected: HashSet<TextureRequestKey>,
47 completion: Option<SnapshotCompletionOutcome>,
48}
49
50#[derive(Copy, Clone, Debug, Eq, PartialEq)]
51enum SynchronousFrameStatus {
52 Pending,
53 Reconciled,
54 Abandoned,
55}
56
57#[derive(Copy, Clone, Debug, Eq, PartialEq)]
58struct SynchronousFrameState {
59 native_frame_count: i32,
60 epoch: Option<SnapshotEpoch>,
61 status: SynchronousFrameStatus,
62}
63
64#[derive(Debug)]
65pub(super) struct SnapshotHub {
66 context: ContextId,
67 sender: Sender<SnapshotMessage>,
68 receiver: Receiver<SnapshotMessage>,
69 phase: ConsumerPhase,
70 next_consumer_generation: Option<NonZeroU64>,
71 next_epoch: Option<NonZeroU64>,
72 completion_watermark: u64,
73 outstanding: BTreeMap<u64, OutstandingEpoch>,
74 pending_errors: VecDeque<RendererConsumerError>,
75 synchronous_frame: Option<SynchronousFrameState>,
76}
77
78impl SnapshotHub {
79 pub(super) fn new(context: ContextId) -> Self {
80 let (sender, receiver) = channel();
81 Self {
82 context,
83 sender,
84 receiver,
85 phase: ConsumerPhase::Unbound,
86 next_consumer_generation: Some(NonZeroU64::MIN),
87 next_epoch: Some(NonZeroU64::MIN),
88 completion_watermark: 0,
89 outstanding: BTreeMap::new(),
90 pending_errors: VecDeque::new(),
91 synchronous_frame: None,
92 }
93 }
94
95 pub(super) const fn completion_watermark(&self) -> u64 {
96 self.completion_watermark
97 }
98
99 pub(super) fn begin_synchronous_native_frame(&mut self, native_frame_count: i32) {
100 self.synchronous_frame = Some(SynchronousFrameState {
101 native_frame_count,
102 epoch: None,
103 status: SynchronousFrameStatus::Pending,
104 });
105 }
106
107 #[cfg(feature = "multi-viewport")]
108 pub(super) fn is_synchronous_frame_reconciled(&self, native_frame_count: i32) -> bool {
109 self.synchronous_frame.is_some_and(|frame| {
110 frame.native_frame_count == native_frame_count
111 && frame.epoch.is_some()
112 && frame.status == SynchronousFrameStatus::Reconciled
113 })
114 }
115
116 pub(super) fn validate_consumer_admission(&self) -> Result<NonZeroU64, RendererConsumerError> {
117 if let Some(error) = self.pending_errors.front().copied() {
118 return Err(error);
119 }
120 match self.phase {
121 ConsumerPhase::Active { .. } => {
122 return Err(RendererConsumerError::ConsumerAlreadyActive);
123 }
124 ConsumerPhase::Draining(_) => return Err(RendererConsumerError::ConsumerDraining),
125 ConsumerPhase::Unbound => {}
126 }
127 self.next_consumer_generation
128 .ok_or(RendererConsumerError::ConsumerGenerationExhausted)
129 }
130
131 fn commit_consumer_admission(&mut self, generation: NonZeroU64, mode: ConsumerMode) {
132 debug_assert_eq!(
133 self.validate_consumer_admission(),
134 Ok(generation),
135 "renderer consumer admission must be validated before it is committed"
136 );
137 let claimed_generation = self
138 .next_consumer_generation
139 .take()
140 .expect("validated renderer consumer generation must remain available");
141 assert_eq!(
142 claimed_generation, generation,
143 "renderer consumer generation changed after admission validation"
144 );
145 self.next_consumer_generation = generation.get().checked_add(1).and_then(NonZeroU64::new);
146 self.phase = ConsumerPhase::Active { generation, mode };
147 }
148
149 pub(super) fn commit_synchronous_consumer_admission(
150 &mut self,
151 generation: NonZeroU64,
152 ) -> SynchronousRendererConsumer {
153 self.commit_consumer_admission(generation, ConsumerMode::Synchronous);
154 SynchronousRendererConsumer::new(self.context, generation, self.sender.clone())
155 }
156
157 pub(super) fn commit_detached_consumer_admission(
158 &mut self,
159 generation: NonZeroU64,
160 ) -> DetachedRendererConsumer {
161 self.commit_consumer_admission(generation, ConsumerMode::Detached);
162 DetachedRendererConsumer::new(self.context, generation, self.sender.clone())
163 }
164
165 pub(super) fn begin_snapshot(
166 &mut self,
167 consumer: &DetachedRendererConsumer,
168 pending: PendingSnapshot,
169 requests: Vec<PendingTextureRequest>,
170 registry: &mut ManagedTextureRegistry,
171 atlas: &FontAtlasSnapshotTarget,
172 ) -> Result<FrameSnapshot, SnapshotError> {
173 let generation = self.validate_consumer(consumer, ConsumerMode::Detached)?;
174 let sequence = self.allocate_epoch()?;
175 let epoch = SnapshotEpoch::new(self.context, generation, sequence);
176 let referenced = pending.referenced_user_textures(&requests);
177 registry.record_snapshot_references(&referenced, sequence.get())?;
178 for request in &requests {
179 if matches!(request.texture, SnapshotTextureId::FontAtlas { .. }) {
180 atlas.record_request_reference(request.texture, sequence.get());
181 }
182 }
183 let (snapshot, expected) = pending.into_frame(requests, epoch, self.sender.clone());
184 let previous = self.outstanding.insert(
185 sequence.get(),
186 OutstandingEpoch {
187 epoch,
188 expected,
189 completion: None,
190 },
191 );
192 debug_assert!(previous.is_none(), "snapshot epoch was allocated twice");
193 Ok(snapshot)
194 }
195
196 pub(super) fn begin_synchronous(
197 &mut self,
198 consumer: &SynchronousRendererConsumer,
199 pending: Vec<PendingTextureRequest>,
200 atlas: &FontAtlasSnapshotTarget,
201 ) -> Result<(SnapshotEpoch, Vec<TextureRequest>), RendererConsumerError> {
202 let generation = self.validate_consumer(consumer, ConsumerMode::Synchronous)?;
203 debug_assert!(self.outstanding.is_empty());
204 let sequence = self.allocate_epoch()?;
205 let epoch = SnapshotEpoch::new(self.context, generation, sequence);
206 if let Some(frame) = self.synchronous_frame.as_mut() {
207 frame.epoch = Some(epoch);
208 }
209 for request in &pending {
210 if matches!(request.texture, SnapshotTextureId::FontAtlas { .. }) {
211 atlas.record_request_reference(request.texture, sequence.get());
212 }
213 }
214 let (requests, expected) = finalize_texture_requests(pending, epoch);
215 self.outstanding.insert(
216 sequence.get(),
217 OutstandingEpoch {
218 epoch,
219 expected,
220 completion: None,
221 },
222 );
223 Ok((epoch, requests))
224 }
225
226 pub(super) fn complete_synchronous(
227 &mut self,
228 epoch: SnapshotEpoch,
229 feedback: Vec<TextureFeedback>,
230 registry: &mut ManagedTextureRegistry,
231 atlas: FontAtlasSnapshotTarget,
232 ) -> Result<SnapshotCompletionProgress, RendererConsumerError> {
233 let result = self
234 .set_direct_completion(epoch, SnapshotCompletionOutcome::Committed(feedback))
235 .and_then(|()| self.advance(registry, &atlas));
236 self.finish_synchronous_frame(epoch, result.is_ok());
237 result
238 }
239
240 pub(super) fn abandon_synchronous(
241 &mut self,
242 epoch: SnapshotEpoch,
243 registry: &mut ManagedTextureRegistry,
244 atlas: FontAtlasSnapshotTarget,
245 ) {
246 if self
247 .set_direct_completion(epoch, SnapshotCompletionOutcome::Abandoned)
248 .is_ok()
249 {
250 let _ = self.advance(registry, &atlas);
251 }
252 self.finish_synchronous_frame(epoch, false);
253 }
254
255 fn finish_synchronous_frame(&mut self, epoch: SnapshotEpoch, reconciled: bool) {
256 let Some(frame) = self.synchronous_frame.as_mut() else {
257 return;
258 };
259 if frame.epoch != Some(epoch) {
260 return;
261 }
262 frame.status = if reconciled {
263 SynchronousFrameStatus::Reconciled
264 } else {
265 SynchronousFrameStatus::Abandoned
266 };
267 }
268
269 fn set_direct_completion(
270 &mut self,
271 epoch: SnapshotEpoch,
272 outcome: SnapshotCompletionOutcome,
273 ) -> Result<(), RendererConsumerError> {
274 let Some(outstanding) = self.outstanding.get_mut(&epoch.sequence()) else {
275 return Err(RendererConsumerError::UnknownEpoch {
276 epoch: epoch.sequence(),
277 });
278 };
279 if outstanding.epoch != epoch {
280 return Err(RendererConsumerError::StaleConsumerGeneration {
281 expected: outstanding.epoch.consumer_generation(),
282 actual: epoch.consumer_generation(),
283 });
284 }
285 if outstanding.completion.is_some() {
286 return Err(RendererConsumerError::EpochAlreadyCompleted {
287 epoch: epoch.sequence(),
288 });
289 }
290 if let SnapshotCompletionOutcome::Committed(feedback) = &outcome {
291 validate_texture_feedback(epoch, &outstanding.expected, feedback)?;
292 }
293 outstanding.completion = Some(outcome);
294 Ok(())
295 }
296
297 fn allocate_epoch(&mut self) -> Result<NonZeroU64, RendererConsumerError> {
298 let sequence = self
299 .next_epoch
300 .take()
301 .ok_or(RendererConsumerError::EpochExhausted)?;
302 self.next_epoch = sequence.get().checked_add(1).and_then(NonZeroU64::new);
303 Ok(sequence)
304 }
305
306 fn validate_consumer(
307 &mut self,
308 consumer: &impl RendererConsumerCapability,
309 mode: ConsumerMode,
310 ) -> Result<NonZeroU64, RendererConsumerError> {
311 if consumer.context_id() != self.context {
312 return Err(RendererConsumerError::ForeignContext {
313 expected: self.context,
314 actual: consumer.context_id(),
315 });
316 }
317 let generation = match self.phase {
318 ConsumerPhase::Unbound => Err(RendererConsumerError::NoActiveConsumer),
319 ConsumerPhase::Draining(_) => Err(RendererConsumerError::ConsumerDraining),
320 ConsumerPhase::Active {
321 generation: expected,
322 ..
323 } if expected != consumer_generation_raw(consumer) => {
324 Err(RendererConsumerError::StaleConsumerGeneration {
325 expected: expected.get(),
326 actual: consumer.generation(),
327 })
328 }
329 ConsumerPhase::Active {
330 generation,
331 mode: active_mode,
332 } => {
333 debug_assert_eq!(active_mode, mode);
334 Ok(generation)
335 }
336 }?;
337 Ok(generation)
338 }
339
340 pub(super) fn validate_idle_consumer(
341 &self,
342 consumer: &impl RendererConsumerCapability,
343 ) -> Result<(), RendererConsumerError> {
344 if consumer.context_id() != self.context {
345 return Err(RendererConsumerError::ForeignContext {
346 expected: self.context,
347 actual: consumer.context_id(),
348 });
349 }
350 match self.phase {
351 ConsumerPhase::Unbound => return Err(RendererConsumerError::NoActiveConsumer),
352 ConsumerPhase::Draining(_) => return Err(RendererConsumerError::ConsumerDraining),
353 ConsumerPhase::Active { generation, .. }
354 if generation != consumer_generation_raw(consumer) =>
355 {
356 return Err(RendererConsumerError::StaleConsumerGeneration {
357 expected: generation.get(),
358 actual: consumer.generation(),
359 });
360 }
361 ConsumerPhase::Active { .. } => {}
362 }
363 if !self.outstanding.is_empty() {
364 return Err(RendererConsumerError::OutstandingEpochs {
365 count: self.outstanding.len(),
366 });
367 }
368 Ok(())
369 }
370
371 pub(super) fn poll(
372 &mut self,
373 registry: &mut ManagedTextureRegistry,
374 atlas: &FontAtlasSnapshotTarget,
375 ) -> Result<SnapshotCompletionProgress, RendererConsumerError> {
376 self.drain_messages();
377 self.advance(registry, atlas)
378 }
379
380 fn advance(
381 &mut self,
382 registry: &mut ManagedTextureRegistry,
383 atlas: &FontAtlasSnapshotTarget,
384 ) -> Result<SnapshotCompletionProgress, RendererConsumerError> {
385 let mut progress = SnapshotCompletionProgress {
386 watermark: self.completion_watermark,
387 ..Default::default()
388 };
389 let previous_watermark = self.completion_watermark;
390
391 while let Some((&sequence, outstanding)) = self.outstanding.first_key_value() {
392 if outstanding.completion.is_none() {
393 break;
394 }
395 let mut outstanding = self
396 .outstanding
397 .remove(&sequence)
398 .expect("first outstanding epoch still exists");
399 let outcome = outstanding
400 .completion
401 .take()
402 .expect("completed epoch contains an outcome");
403 match outcome {
404 SnapshotCompletionOutcome::Committed(feedback) => {
405 match validate_texture_feedback(
406 outstanding.epoch,
407 &outstanding.expected,
408 &feedback,
409 )
410 .and_then(|()| registry.apply_snapshot_feedback(&feedback, atlas, sequence))
411 {
412 Ok(applied) => {
413 progress.committed += 1;
414 progress.feedback_applied += applied;
415 }
416 Err(error) => {
417 self.pending_errors.push_back(error);
418 progress.abandoned += 1;
419 }
420 }
421 }
422 SnapshotCompletionOutcome::Abandoned => {
423 progress.abandoned += 1;
424 }
425 }
426 self.completion_watermark = sequence;
427 progress.watermark = sequence;
428 }
429
430 if self.completion_watermark != previous_watermark {
431 registry.reap_destroyed(self.completion_watermark);
432 atlas.prune_tombstones(self.completion_watermark);
433 }
434 if matches!(self.phase, ConsumerPhase::Draining(_)) && self.outstanding.is_empty() {
435 self.phase = ConsumerPhase::Unbound;
436 }
437 if let Some(error) = self.pending_errors.pop_front() {
438 Err(error)
439 } else {
440 Ok(progress)
441 }
442 }
443
444 fn drain_messages(&mut self) {
445 loop {
446 match self.receiver.try_recv() {
447 Ok(SnapshotMessage::Completion(completion)) => {
448 let sequence = completion.epoch.sequence();
449 if completion.epoch.context_id() != self.context {
450 self.pending_errors
451 .push_back(RendererConsumerError::ForeignContext {
452 expected: self.context,
453 actual: completion.epoch.context_id(),
454 });
455 continue;
456 }
457 let Some(outstanding) = self.outstanding.get_mut(&sequence) else {
458 self.pending_errors
459 .push_back(RendererConsumerError::UnknownEpoch { epoch: sequence });
460 continue;
461 };
462 if outstanding.epoch != completion.epoch {
463 self.pending_errors.push_back(
464 RendererConsumerError::StaleConsumerGeneration {
465 expected: outstanding.epoch.consumer_generation(),
466 actual: completion.epoch.consumer_generation(),
467 },
468 );
469 continue;
470 }
471 if outstanding.completion.is_some() {
472 self.pending_errors.push_back(
473 RendererConsumerError::EpochAlreadyCompleted { epoch: sequence },
474 );
475 continue;
476 }
477 outstanding.completion = Some(completion.outcome);
478 }
479 Ok(SnapshotMessage::Detach {
480 context,
481 generation,
482 }) => {
483 if context != self.context {
484 self.pending_errors
485 .push_back(RendererConsumerError::ForeignContext {
486 expected: self.context,
487 actual: context,
488 });
489 continue;
490 }
491 match self.phase {
492 ConsumerPhase::Active {
493 generation: active, ..
494 } if active == generation => {
495 self.phase = ConsumerPhase::Draining(generation);
496 }
497 ConsumerPhase::Draining(active) if active == generation => {}
498 ConsumerPhase::Active {
499 generation: active, ..
500 }
501 | ConsumerPhase::Draining(active) => {
502 self.pending_errors.push_back(
503 RendererConsumerError::StaleConsumerGeneration {
504 expected: active.get(),
505 actual: generation.get(),
506 },
507 );
508 }
509 ConsumerPhase::Unbound => {
510 self.pending_errors
511 .push_back(RendererConsumerError::NoActiveConsumer);
512 }
513 }
514 }
515 Err(TryRecvError::Empty | TryRecvError::Disconnected) => break,
516 }
517 }
518 }
519
520 pub(super) fn close(&mut self) {
521 self.outstanding.clear();
522 self.phase = ConsumerPhase::Unbound;
523 self.synchronous_frame = None;
524 }
525}
526
527#[must_use = "destroy the renderer texture map, then commit this reset permit"]
535pub struct RendererTextureReset<'context, 'consumer> {
536 context: &'context mut Context,
537 _consumer: &'consumer dyn RendererConsumerCapability,
538 watermark: u64,
539}
540
541impl RendererTextureReset<'_, '_> {
542 pub fn commit(self) {
547 let binding = self.context.binding();
548 binding.with_bound_context(|| self.commit_unlocked());
549 }
550
551 fn commit_unlocked(self) {
552 self.context
553 .commit_renderer_texture_reset_unlocked(self.watermark);
554 }
555}
556
557impl Context {
558 pub fn preflight_renderer_consumer(&self) -> Result<(), RendererConsumerError> {
569 let _guard = CTX_MUTEX.lock();
570 self.validate_renderer_consumer_admission_unlocked("Context::preflight_renderer_consumer()")
571 .map(|_| ())
572 }
573
574 pub fn create_synchronous_renderer_consumer(
584 &mut self,
585 ) -> Result<SynchronousRendererConsumer, RendererConsumerError> {
586 let _guard = CTX_MUTEX.lock();
587 let generation = self.commit_renderer_consumer_admission_unlocked(
588 "Context::create_synchronous_renderer_consumer()",
589 )?;
590 Ok(self
591 .snapshot_hub
592 .commit_synchronous_consumer_admission(generation))
593 }
594
595 pub fn create_detached_renderer_consumer(
600 &mut self,
601 ) -> Result<DetachedRendererConsumer, RendererConsumerError> {
602 let _guard = CTX_MUTEX.lock();
603 let generation = self.commit_renderer_consumer_admission_unlocked(
604 "Context::create_detached_renderer_consumer()",
605 )?;
606 Ok(self
607 .snapshot_hub
608 .commit_detached_consumer_admission(generation))
609 }
610
611 fn commit_renderer_consumer_admission_unlocked(
612 &mut self,
613 caller: &str,
614 ) -> Result<NonZeroU64, RendererConsumerError> {
615 self.assert_current_context(caller);
616 let atlas_target = self.font_atlas_snapshot_target();
617 let _ = self.poll_snapshot_completions_with_target(&atlas_target)?;
618 let (atlas, generation) = self.validate_renderer_consumer_admission_unlocked(caller)?;
619 let _ = crate::fonts::claim_validated_font_atlas_managed_renderer(atlas, self.raw);
620 Ok(generation)
621 }
622
623 fn validate_renderer_consumer_admission_unlocked(
624 &self,
625 caller: &str,
626 ) -> Result<(*mut crate::sys::ImFontAtlas, NonZeroU64), RendererConsumerError> {
627 self.assert_current_context(caller);
628 let io = self.io_ptr(caller);
629 let atlas = unsafe { (*io).Fonts };
630 crate::fonts::validate_font_atlas_managed_renderer(atlas, self.raw)?;
631 let generation = self.snapshot_hub.validate_consumer_admission()?;
632 Ok((atlas, generation))
633 }
634
635 pub fn poll_snapshot_completions(
637 &mut self,
638 ) -> Result<SnapshotCompletionProgress, RendererConsumerError> {
639 let _guard = CTX_MUTEX.lock();
640 self.poll_snapshot_completions_unlocked()
641 }
642
643 pub(super) fn poll_snapshot_completions_unlocked(
644 &mut self,
645 ) -> Result<SnapshotCompletionProgress, RendererConsumerError> {
646 self.assert_current_context("Context::poll_snapshot_completions()");
647 let atlas = self.font_atlas_snapshot_target();
648 self.poll_snapshot_completions_with_target(&atlas)
649 }
650
651 fn poll_snapshot_completions_with_target(
652 &mut self,
653 atlas: &FontAtlasSnapshotTarget,
654 ) -> Result<SnapshotCompletionProgress, RendererConsumerError> {
655 self.snapshot_hub
656 .poll(&mut self.texture_registry.borrow_mut(), atlas)
657 }
658
659 pub fn prepare_renderer_texture_reset<'context, 'consumer>(
678 &'context mut self,
679 consumer: &'consumer impl RendererConsumerCapability,
680 ) -> Result<RendererTextureReset<'context, 'consumer>, RendererConsumerError> {
681 let _guard = CTX_MUTEX.lock();
682 self.prepare_renderer_texture_reset_unlocked(consumer)
683 }
684
685 pub(super) fn prepare_renderer_texture_reset_during_teardown(
691 &mut self,
692 consumer: &impl RendererConsumerCapability,
693 ) -> Result<u64, RendererConsumerError> {
694 self.validate_renderer_texture_reset_unlocked(consumer)
695 }
696
697 fn prepare_renderer_texture_reset_unlocked<'context, 'consumer>(
698 &'context mut self,
699 consumer: &'consumer impl RendererConsumerCapability,
700 ) -> Result<RendererTextureReset<'context, 'consumer>, RendererConsumerError> {
701 let watermark = self.validate_renderer_texture_reset_unlocked(consumer)?;
702 Ok(RendererTextureReset {
703 context: self,
704 _consumer: consumer,
705 watermark,
706 })
707 }
708
709 fn validate_renderer_texture_reset_unlocked(
710 &mut self,
711 consumer: &impl RendererConsumerCapability,
712 ) -> Result<u64, RendererConsumerError> {
713 self.assert_current_context("Context::prepare_renderer_texture_reset()");
714 let atlas = self.font_atlas_snapshot_target();
715 let _ = self.poll_snapshot_completions_with_target(&atlas)?;
716 self.snapshot_hub.validate_idle_consumer(consumer)?;
717 Ok(self.snapshot_hub.completion_watermark())
718 }
719
720 pub(super) fn commit_renderer_texture_reset_during_teardown(&mut self, watermark: u64) {
721 self.commit_renderer_texture_reset_unlocked(watermark);
722 }
723
724 fn commit_renderer_texture_reset_unlocked(&mut self, watermark: u64) {
725 self.assert_current_context("RendererTextureReset::commit()");
726 let atlas = self.font_atlas_snapshot_target();
727 atlas.reset_renderer_bindings();
728 self.texture_registry
729 .borrow_mut()
730 .reset_renderer_bindings(watermark);
731 }
732
733 pub(super) fn capture_main_snapshot(
734 &mut self,
735 consumer: &DetachedRendererConsumer,
736 draw_data: *const crate::render::DrawData,
737 ) -> Result<FrameSnapshot, SnapshotError> {
738 self.snapshot_hub
739 .validate_consumer(consumer, ConsumerMode::Detached)?;
740 let atlas = self.font_atlas_snapshot_target();
741 let _ = self.poll_snapshot_completions_with_target(&atlas)?;
742 let pending = {
743 let registry = self.texture_registry.borrow();
744 let mut resolve = |native| registry.resolve_snapshot_texture(native, &atlas);
745 capture_draw_data(unsafe { &*draw_data }, &mut resolve)?
746 };
747 let requests = self
748 .texture_registry
749 .borrow_mut()
750 .stage_snapshot_operations(&pending.texture_operations, &atlas)?;
751 self.snapshot_hub.begin_snapshot(
752 consumer,
753 pending,
754 requests,
755 &mut self.texture_registry.borrow_mut(),
756 &atlas,
757 )
758 }
759
760 pub(super) fn begin_synchronous_render(
761 &mut self,
762 consumer: &SynchronousRendererConsumer,
763 draw_data: *const crate::render::DrawData,
764 ) -> Result<(SnapshotEpoch, Vec<TextureRequest>), SnapshotError> {
765 self.snapshot_hub
766 .validate_consumer(consumer, ConsumerMode::Synchronous)?;
767 let native_frame_count = unsafe { (*self.raw).FrameCount };
768 self.snapshot_hub
769 .begin_synchronous_native_frame(native_frame_count);
770 let atlas = self.font_atlas_snapshot_target();
771 let _ = self.poll_snapshot_completions_with_target(&atlas)?;
772 let captured = {
773 let registry = self.texture_registry.borrow();
774 let mut resolve = |native| registry.resolve_snapshot_texture(native, &atlas);
775 capture_texture_requests_only(unsafe { &*draw_data }, &mut resolve)?
776 };
777 let pending = self
778 .texture_registry
779 .borrow_mut()
780 .stage_snapshot_operations(&captured, &atlas)?;
781 let (epoch, requests) = self
782 .snapshot_hub
783 .begin_synchronous(consumer, pending, &atlas)?;
784 let ids = requests
785 .iter()
786 .filter_map(|request| match request.texture() {
787 crate::render::snapshot::SnapshotTextureId::User(id) => Some(id),
788 crate::render::snapshot::SnapshotTextureId::FontAtlas { .. } => None,
789 })
790 .collect();
791 self.texture_registry
792 .borrow_mut()
793 .record_snapshot_references(&ids, epoch.sequence())?;
794 Ok((epoch, requests))
795 }
796
797 pub(crate) fn complete_synchronous_render(
798 &mut self,
799 epoch: SnapshotEpoch,
800 feedback: Vec<TextureFeedback>,
801 ) -> Result<SnapshotCompletionProgress, RendererConsumerError> {
802 let atlas = self.font_atlas_snapshot_target();
803 self.snapshot_hub.complete_synchronous(
804 epoch,
805 feedback,
806 &mut self.texture_registry.borrow_mut(),
807 atlas,
808 )
809 }
810
811 pub(crate) fn abandon_synchronous_render(&mut self, epoch: SnapshotEpoch) {
812 let atlas = self.font_atlas_snapshot_target();
813 self.snapshot_hub.abandon_synchronous(
814 epoch,
815 &mut self.texture_registry.borrow_mut(),
816 atlas,
817 );
818 }
819
820 #[cfg(feature = "multi-viewport")]
821 pub(super) fn capture_platform_snapshot(
822 &mut self,
823 consumer: &DetachedRendererConsumer,
824 ) -> Result<FrameSnapshot, SnapshotError> {
825 self.snapshot_hub
826 .validate_consumer(consumer, ConsumerMode::Detached)?;
827 let atlas = self.font_atlas_snapshot_target();
828 let _ = self.poll_snapshot_completions_with_target(&atlas)?;
829 let platform_io_ptr = self.platform_io_ptr("Context::capture_platform_snapshot()");
830 let platform_io =
831 unsafe { crate::platform_io::PlatformIo::from_raw(platform_io_ptr.cast_const()) };
832 let pending = {
833 let registry = self.texture_registry.borrow();
834 let mut resolve = |native| registry.resolve_snapshot_texture(native, &atlas);
835 unsafe { capture_platform_io(platform_io, &mut resolve)? }
838 };
839 let requests = self
840 .texture_registry
841 .borrow_mut()
842 .stage_snapshot_operations(&pending.texture_operations, &atlas)?;
843 self.snapshot_hub.begin_snapshot(
844 consumer,
845 pending,
846 requests,
847 &mut self.texture_registry.borrow_mut(),
848 &atlas,
849 )
850 }
851
852 pub(super) fn font_atlas_snapshot_target(&self) -> FontAtlasSnapshotTarget {
853 let io = self.io_ptr("Context snapshot texture capture");
854 let atlas = unsafe { (*io).Fonts };
855 assert!(!atlas.is_null(), "Context has no font atlas");
856 let textures = crate::fonts::font_atlas_snapshot_identities(atlas, self.raw)
857 .into_iter()
858 .map(|identity| {
859 FontAtlasTextureTarget::new(
860 SnapshotTextureId::FontAtlas {
861 context: self.id(),
862 stamp: identity.stamp,
863 generation: identity.texture_generation,
864 },
865 identity.texture,
866 )
867 })
868 .collect();
869 FontAtlasSnapshotTarget::new(atlas, self.id(), textures)
870 }
871}
872
873#[cfg(test)]
874mod tests {
875 use super::*;
876
877 #[test]
878 fn renderer_consumer_preflight_failure_does_not_claim_the_font_atlas() {
879 let _guard = crate::test_support::imgui_context_guard();
880 let atlas = crate::SharedFontAtlas::create();
881 let first = Context::create_with_shared_font_atlas(atlas.clone());
882 let suspended = first.suspend_or_panic();
883 let second = Context::create_with_shared_font_atlas(atlas.clone());
884
885 assert_eq!(
886 second.preflight_renderer_consumer(),
887 Err(
888 RendererConsumerError::SharedFontAtlasRequiresExclusiveContext {
889 registered_contexts: 2,
890 }
891 )
892 );
893
894 drop(second);
895 let replacement = Context::try_create_with_shared_font_atlas(atlas.clone())
896 .expect("preflight must not claim the shared font atlas");
897 drop(replacement);
898 drop(suspended);
899 }
900
901 #[test]
902 fn renderer_consumer_hub_failure_does_not_claim_the_font_atlas() {
903 let _guard = crate::test_support::imgui_context_guard();
904 let atlas = crate::SharedFontAtlas::create();
905 let mut context = Context::create_with_shared_font_atlas(atlas.clone());
906 context.snapshot_hub.next_consumer_generation = None;
907
908 assert!(matches!(
909 context.create_synchronous_renderer_consumer(),
910 Err(RendererConsumerError::ConsumerGenerationExhausted)
911 ));
912
913 let suspended = context.suspend_or_panic();
914 let second = Context::try_create_with_shared_font_atlas(atlas.clone())
915 .expect("failed consumer admission must not claim the shared font atlas");
916 drop(second);
917 drop(suspended);
918 }
919}