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