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 registry: &mut ManagedTextureRegistry,
170 atlas: &FontAtlasSnapshotTarget,
171 ) -> Result<FrameSnapshot, SnapshotError> {
172 let generation = self.validate_consumer(consumer, ConsumerMode::Detached)?;
173 let sequence = self.allocate_epoch()?;
174 let epoch = SnapshotEpoch::new(self.context, generation, sequence);
175 let referenced = pending.referenced_user_textures();
176 registry.record_snapshot_references(&referenced, sequence.get())?;
177 for request in &pending.texture_requests {
178 if matches!(request.texture, SnapshotTextureId::FontAtlas { .. }) {
179 atlas.record_request_reference(request.texture, sequence.get());
180 }
181 }
182 let (snapshot, expected) = pending.into_frame(epoch, self.sender.clone());
183 let previous = self.outstanding.insert(
184 sequence.get(),
185 OutstandingEpoch {
186 epoch,
187 expected,
188 completion: None,
189 },
190 );
191 debug_assert!(previous.is_none(), "snapshot epoch was allocated twice");
192 Ok(snapshot)
193 }
194
195 pub(super) fn begin_synchronous(
196 &mut self,
197 consumer: &SynchronousRendererConsumer,
198 pending: Vec<PendingTextureRequest>,
199 atlas: &FontAtlasSnapshotTarget,
200 ) -> Result<(SnapshotEpoch, Vec<TextureRequest>), RendererConsumerError> {
201 let generation = self.validate_consumer(consumer, ConsumerMode::Synchronous)?;
202 debug_assert!(self.outstanding.is_empty());
203 let sequence = self.allocate_epoch()?;
204 let epoch = SnapshotEpoch::new(self.context, generation, sequence);
205 if let Some(frame) = self.synchronous_frame.as_mut() {
206 frame.epoch = Some(epoch);
207 }
208 for request in &pending {
209 if matches!(request.texture, SnapshotTextureId::FontAtlas { .. }) {
210 atlas.record_request_reference(request.texture, sequence.get());
211 }
212 }
213 let (requests, expected) = finalize_texture_requests(pending, epoch);
214 self.outstanding.insert(
215 sequence.get(),
216 OutstandingEpoch {
217 epoch,
218 expected,
219 completion: None,
220 },
221 );
222 Ok((epoch, requests))
223 }
224
225 pub(super) fn complete_synchronous(
226 &mut self,
227 epoch: SnapshotEpoch,
228 feedback: Vec<TextureFeedback>,
229 registry: &mut ManagedTextureRegistry,
230 atlas: FontAtlasSnapshotTarget,
231 ) -> Result<SnapshotCompletionProgress, RendererConsumerError> {
232 let result = self
233 .set_direct_completion(epoch, SnapshotCompletionOutcome::Committed(feedback))
234 .and_then(|()| self.advance(registry, &atlas));
235 self.finish_synchronous_frame(epoch, result.is_ok());
236 result
237 }
238
239 pub(super) fn abandon_synchronous(
240 &mut self,
241 epoch: SnapshotEpoch,
242 registry: &mut ManagedTextureRegistry,
243 atlas: FontAtlasSnapshotTarget,
244 ) {
245 if self
246 .set_direct_completion(epoch, SnapshotCompletionOutcome::Abandoned)
247 .is_ok()
248 {
249 let _ = self.advance(registry, &atlas);
250 }
251 self.finish_synchronous_frame(epoch, false);
252 }
253
254 fn finish_synchronous_frame(&mut self, epoch: SnapshotEpoch, reconciled: bool) {
255 let Some(frame) = self.synchronous_frame.as_mut() else {
256 return;
257 };
258 if frame.epoch != Some(epoch) {
259 return;
260 }
261 frame.status = if reconciled {
262 SynchronousFrameStatus::Reconciled
263 } else {
264 SynchronousFrameStatus::Abandoned
265 };
266 }
267
268 fn set_direct_completion(
269 &mut self,
270 epoch: SnapshotEpoch,
271 outcome: SnapshotCompletionOutcome,
272 ) -> Result<(), RendererConsumerError> {
273 let Some(outstanding) = self.outstanding.get_mut(&epoch.sequence()) else {
274 return Err(RendererConsumerError::UnknownEpoch {
275 epoch: epoch.sequence(),
276 });
277 };
278 if outstanding.epoch != epoch {
279 return Err(RendererConsumerError::StaleConsumerGeneration {
280 expected: outstanding.epoch.consumer_generation(),
281 actual: epoch.consumer_generation(),
282 });
283 }
284 if outstanding.completion.is_some() {
285 return Err(RendererConsumerError::EpochAlreadyCompleted {
286 epoch: epoch.sequence(),
287 });
288 }
289 if let SnapshotCompletionOutcome::Committed(feedback) = &outcome {
290 validate_texture_feedback(epoch, &outstanding.expected, feedback)?;
291 }
292 outstanding.completion = Some(outcome);
293 Ok(())
294 }
295
296 fn allocate_epoch(&mut self) -> Result<NonZeroU64, RendererConsumerError> {
297 let sequence = self
298 .next_epoch
299 .take()
300 .ok_or(RendererConsumerError::EpochExhausted)?;
301 self.next_epoch = sequence.get().checked_add(1).and_then(NonZeroU64::new);
302 Ok(sequence)
303 }
304
305 fn validate_consumer(
306 &mut self,
307 consumer: &impl RendererConsumerCapability,
308 mode: ConsumerMode,
309 ) -> Result<NonZeroU64, RendererConsumerError> {
310 if consumer.context_id() != self.context {
311 return Err(RendererConsumerError::ForeignContext {
312 expected: self.context,
313 actual: consumer.context_id(),
314 });
315 }
316 let generation = match self.phase {
317 ConsumerPhase::Unbound => Err(RendererConsumerError::NoActiveConsumer),
318 ConsumerPhase::Draining(_) => Err(RendererConsumerError::ConsumerDraining),
319 ConsumerPhase::Active {
320 generation: expected,
321 ..
322 } if expected != consumer_generation_raw(consumer) => {
323 Err(RendererConsumerError::StaleConsumerGeneration {
324 expected: expected.get(),
325 actual: consumer.generation(),
326 })
327 }
328 ConsumerPhase::Active {
329 generation,
330 mode: active_mode,
331 } => {
332 debug_assert_eq!(active_mode, mode);
333 Ok(generation)
334 }
335 }?;
336 Ok(generation)
337 }
338
339 pub(super) fn validate_idle_consumer(
340 &self,
341 consumer: &impl RendererConsumerCapability,
342 ) -> Result<(), RendererConsumerError> {
343 if consumer.context_id() != self.context {
344 return Err(RendererConsumerError::ForeignContext {
345 expected: self.context,
346 actual: consumer.context_id(),
347 });
348 }
349 match self.phase {
350 ConsumerPhase::Unbound => return Err(RendererConsumerError::NoActiveConsumer),
351 ConsumerPhase::Draining(_) => return Err(RendererConsumerError::ConsumerDraining),
352 ConsumerPhase::Active { generation, .. }
353 if generation != consumer_generation_raw(consumer) =>
354 {
355 return Err(RendererConsumerError::StaleConsumerGeneration {
356 expected: generation.get(),
357 actual: consumer.generation(),
358 });
359 }
360 ConsumerPhase::Active { .. } => {}
361 }
362 if !self.outstanding.is_empty() {
363 return Err(RendererConsumerError::OutstandingEpochs {
364 count: self.outstanding.len(),
365 });
366 }
367 Ok(())
368 }
369
370 pub(super) fn poll(
371 &mut self,
372 registry: &mut ManagedTextureRegistry,
373 atlas: &FontAtlasSnapshotTarget,
374 ) -> Result<SnapshotCompletionProgress, RendererConsumerError> {
375 self.drain_messages();
376 self.advance(registry, atlas)
377 }
378
379 fn advance(
380 &mut self,
381 registry: &mut ManagedTextureRegistry,
382 atlas: &FontAtlasSnapshotTarget,
383 ) -> Result<SnapshotCompletionProgress, RendererConsumerError> {
384 let mut progress = SnapshotCompletionProgress {
385 watermark: self.completion_watermark,
386 ..Default::default()
387 };
388 let previous_watermark = self.completion_watermark;
389
390 while let Some((&sequence, outstanding)) = self.outstanding.first_key_value() {
391 if outstanding.completion.is_none() {
392 break;
393 }
394 let mut outstanding = self
395 .outstanding
396 .remove(&sequence)
397 .expect("first outstanding epoch still exists");
398 let outcome = outstanding
399 .completion
400 .take()
401 .expect("completed epoch contains an outcome");
402 match outcome {
403 SnapshotCompletionOutcome::Committed(feedback) => {
404 match validate_texture_feedback(
405 outstanding.epoch,
406 &outstanding.expected,
407 &feedback,
408 )
409 .and_then(|()| registry.apply_snapshot_feedback(&feedback, atlas, sequence))
410 {
411 Ok(applied) => {
412 progress.committed += 1;
413 progress.feedback_applied += applied;
414 }
415 Err(error) => {
416 self.pending_errors.push_back(error);
417 progress.abandoned += 1;
418 }
419 }
420 }
421 SnapshotCompletionOutcome::Abandoned => {
422 progress.abandoned += 1;
423 }
424 }
425 self.completion_watermark = sequence;
426 progress.watermark = sequence;
427 }
428
429 if self.completion_watermark != previous_watermark {
430 registry.reap_destroyed(self.completion_watermark);
431 atlas.prune_tombstones(self.completion_watermark);
432 }
433 if matches!(self.phase, ConsumerPhase::Draining(_)) && self.outstanding.is_empty() {
434 self.phase = ConsumerPhase::Unbound;
435 }
436 if let Some(error) = self.pending_errors.pop_front() {
437 Err(error)
438 } else {
439 Ok(progress)
440 }
441 }
442
443 fn drain_messages(&mut self) {
444 loop {
445 match self.receiver.try_recv() {
446 Ok(SnapshotMessage::Completion(completion)) => {
447 let sequence = completion.epoch.sequence();
448 if completion.epoch.context_id() != self.context {
449 self.pending_errors
450 .push_back(RendererConsumerError::ForeignContext {
451 expected: self.context,
452 actual: completion.epoch.context_id(),
453 });
454 continue;
455 }
456 let Some(outstanding) = self.outstanding.get_mut(&sequence) else {
457 self.pending_errors
458 .push_back(RendererConsumerError::UnknownEpoch { epoch: sequence });
459 continue;
460 };
461 if outstanding.epoch != completion.epoch {
462 self.pending_errors.push_back(
463 RendererConsumerError::StaleConsumerGeneration {
464 expected: outstanding.epoch.consumer_generation(),
465 actual: completion.epoch.consumer_generation(),
466 },
467 );
468 continue;
469 }
470 if outstanding.completion.is_some() {
471 self.pending_errors.push_back(
472 RendererConsumerError::EpochAlreadyCompleted { epoch: sequence },
473 );
474 continue;
475 }
476 outstanding.completion = Some(completion.outcome);
477 }
478 Ok(SnapshotMessage::Detach {
479 context,
480 generation,
481 }) => {
482 if context != self.context {
483 self.pending_errors
484 .push_back(RendererConsumerError::ForeignContext {
485 expected: self.context,
486 actual: context,
487 });
488 continue;
489 }
490 match self.phase {
491 ConsumerPhase::Active {
492 generation: active, ..
493 } if active == generation => {
494 self.phase = ConsumerPhase::Draining(generation);
495 }
496 ConsumerPhase::Draining(active) if active == generation => {}
497 ConsumerPhase::Active {
498 generation: active, ..
499 }
500 | ConsumerPhase::Draining(active) => {
501 self.pending_errors.push_back(
502 RendererConsumerError::StaleConsumerGeneration {
503 expected: active.get(),
504 actual: generation.get(),
505 },
506 );
507 }
508 ConsumerPhase::Unbound => {
509 self.pending_errors
510 .push_back(RendererConsumerError::NoActiveConsumer);
511 }
512 }
513 }
514 Err(TryRecvError::Empty | TryRecvError::Disconnected) => break,
515 }
516 }
517 }
518
519 pub(super) fn close(&mut self) {
520 self.outstanding.clear();
521 self.phase = ConsumerPhase::Unbound;
522 self.synchronous_frame = None;
523 }
524}
525
526#[must_use = "destroy the renderer texture map, then commit this reset permit"]
534pub struct RendererTextureReset<'context, 'consumer> {
535 context: &'context mut Context,
536 _consumer: &'consumer dyn RendererConsumerCapability,
537 watermark: u64,
538}
539
540impl RendererTextureReset<'_, '_> {
541 pub fn commit(self) {
546 let binding = self.context.binding();
547 binding.with_bound_context(|| self.commit_unlocked());
548 }
549
550 fn commit_unlocked(self) {
551 self.context
552 .commit_renderer_texture_reset_unlocked(self.watermark);
553 }
554}
555
556impl Context {
557 pub fn preflight_renderer_consumer(&self) -> Result<(), RendererConsumerError> {
568 let _guard = CTX_MUTEX.lock();
569 self.validate_renderer_consumer_admission_unlocked("Context::preflight_renderer_consumer()")
570 .map(|_| ())
571 }
572
573 pub fn create_synchronous_renderer_consumer(
583 &mut self,
584 ) -> Result<SynchronousRendererConsumer, RendererConsumerError> {
585 let _guard = CTX_MUTEX.lock();
586 let generation = self.commit_renderer_consumer_admission_unlocked(
587 "Context::create_synchronous_renderer_consumer()",
588 )?;
589 Ok(self
590 .snapshot_hub
591 .commit_synchronous_consumer_admission(generation))
592 }
593
594 pub fn create_detached_renderer_consumer(
599 &mut self,
600 ) -> Result<DetachedRendererConsumer, RendererConsumerError> {
601 let _guard = CTX_MUTEX.lock();
602 let generation = self.commit_renderer_consumer_admission_unlocked(
603 "Context::create_detached_renderer_consumer()",
604 )?;
605 Ok(self
606 .snapshot_hub
607 .commit_detached_consumer_admission(generation))
608 }
609
610 fn commit_renderer_consumer_admission_unlocked(
611 &mut self,
612 caller: &str,
613 ) -> Result<NonZeroU64, RendererConsumerError> {
614 self.assert_current_context(caller);
615 let atlas_target = self.font_atlas_snapshot_target();
616 let _ = self.poll_snapshot_completions_with_target(&atlas_target)?;
617 let (atlas, generation) = self.validate_renderer_consumer_admission_unlocked(caller)?;
618 let _ = crate::fonts::claim_validated_font_atlas_managed_renderer(atlas, self.raw);
619 Ok(generation)
620 }
621
622 fn validate_renderer_consumer_admission_unlocked(
623 &self,
624 caller: &str,
625 ) -> Result<(*mut crate::sys::ImFontAtlas, NonZeroU64), RendererConsumerError> {
626 self.assert_current_context(caller);
627 let io = self.io_ptr(caller);
628 let atlas = unsafe { (*io).Fonts };
629 crate::fonts::validate_font_atlas_managed_renderer(atlas, self.raw)?;
630 let generation = self.snapshot_hub.validate_consumer_admission()?;
631 Ok((atlas, generation))
632 }
633
634 pub fn poll_snapshot_completions(
636 &mut self,
637 ) -> Result<SnapshotCompletionProgress, RendererConsumerError> {
638 let _guard = CTX_MUTEX.lock();
639 self.poll_snapshot_completions_unlocked()
640 }
641
642 pub(super) fn poll_snapshot_completions_unlocked(
643 &mut self,
644 ) -> Result<SnapshotCompletionProgress, RendererConsumerError> {
645 self.assert_current_context("Context::poll_snapshot_completions()");
646 let atlas = self.font_atlas_snapshot_target();
647 self.poll_snapshot_completions_with_target(&atlas)
648 }
649
650 fn poll_snapshot_completions_with_target(
651 &mut self,
652 atlas: &FontAtlasSnapshotTarget,
653 ) -> Result<SnapshotCompletionProgress, RendererConsumerError> {
654 self.snapshot_hub
655 .poll(&mut self.texture_registry.borrow_mut(), atlas)
656 }
657
658 pub fn prepare_renderer_texture_reset<'context, 'consumer>(
677 &'context mut self,
678 consumer: &'consumer impl RendererConsumerCapability,
679 ) -> Result<RendererTextureReset<'context, 'consumer>, RendererConsumerError> {
680 let _guard = CTX_MUTEX.lock();
681 self.prepare_renderer_texture_reset_unlocked(consumer)
682 }
683
684 pub(super) fn prepare_renderer_texture_reset_during_teardown(
690 &mut self,
691 consumer: &impl RendererConsumerCapability,
692 ) -> Result<u64, RendererConsumerError> {
693 self.validate_renderer_texture_reset_unlocked(consumer)
694 }
695
696 fn prepare_renderer_texture_reset_unlocked<'context, 'consumer>(
697 &'context mut self,
698 consumer: &'consumer impl RendererConsumerCapability,
699 ) -> Result<RendererTextureReset<'context, 'consumer>, RendererConsumerError> {
700 let watermark = self.validate_renderer_texture_reset_unlocked(consumer)?;
701 Ok(RendererTextureReset {
702 context: self,
703 _consumer: consumer,
704 watermark,
705 })
706 }
707
708 fn validate_renderer_texture_reset_unlocked(
709 &mut self,
710 consumer: &impl RendererConsumerCapability,
711 ) -> Result<u64, RendererConsumerError> {
712 self.assert_current_context("Context::prepare_renderer_texture_reset()");
713 let atlas = self.font_atlas_snapshot_target();
714 let _ = self.poll_snapshot_completions_with_target(&atlas)?;
715 self.snapshot_hub.validate_idle_consumer(consumer)?;
716 Ok(self.snapshot_hub.completion_watermark())
717 }
718
719 pub(super) fn commit_renderer_texture_reset_during_teardown(&mut self, watermark: u64) {
720 self.commit_renderer_texture_reset_unlocked(watermark);
721 }
722
723 fn commit_renderer_texture_reset_unlocked(&mut self, watermark: u64) {
724 self.assert_current_context("RendererTextureReset::commit()");
725 let atlas = self.font_atlas_snapshot_target();
726 atlas.reset_renderer_bindings();
727 self.texture_registry
728 .borrow_mut()
729 .reset_renderer_bindings(watermark);
730 }
731
732 pub(super) fn capture_main_snapshot(
733 &mut self,
734 consumer: &DetachedRendererConsumer,
735 draw_data: *const crate::render::DrawData,
736 ) -> Result<FrameSnapshot, SnapshotError> {
737 let atlas = self.font_atlas_snapshot_target();
738 let _ = self.poll_snapshot_completions_with_target(&atlas)?;
739 let mut pending = {
740 let registry = self.texture_registry.borrow();
741 let mut resolve = |native| registry.resolve_snapshot_texture(native, &atlas);
742 capture_draw_data(unsafe { &*draw_data }, &mut resolve)?
743 };
744 self.texture_registry
745 .borrow_mut()
746 .track_snapshot_operations(&mut pending.texture_requests, &atlas)?;
747 self.snapshot_hub.begin_snapshot(
748 consumer,
749 pending,
750 &mut self.texture_registry.borrow_mut(),
751 &atlas,
752 )
753 }
754
755 pub(super) fn begin_synchronous_render(
756 &mut self,
757 consumer: &SynchronousRendererConsumer,
758 draw_data: *const crate::render::DrawData,
759 ) -> Result<(SnapshotEpoch, Vec<TextureRequest>), SnapshotError> {
760 let native_frame_count = unsafe { (*self.raw).FrameCount };
761 self.snapshot_hub
762 .begin_synchronous_native_frame(native_frame_count);
763 let atlas = self.font_atlas_snapshot_target();
764 let _ = self.poll_snapshot_completions_with_target(&atlas)?;
765 let mut pending = {
766 let registry = self.texture_registry.borrow();
767 let mut resolve = |native| registry.resolve_snapshot_texture(native, &atlas);
768 capture_texture_requests_only(unsafe { &*draw_data }, &mut resolve)?
769 };
770 self.texture_registry
771 .borrow_mut()
772 .track_snapshot_operations(&mut pending, &atlas)?;
773 Ok(self
774 .snapshot_hub
775 .begin_synchronous(consumer, pending, &atlas)?)
776 }
777
778 pub(crate) fn complete_synchronous_render(
779 &mut self,
780 epoch: SnapshotEpoch,
781 feedback: Vec<TextureFeedback>,
782 ) -> Result<SnapshotCompletionProgress, RendererConsumerError> {
783 let atlas = self.font_atlas_snapshot_target();
784 self.snapshot_hub.complete_synchronous(
785 epoch,
786 feedback,
787 &mut self.texture_registry.borrow_mut(),
788 atlas,
789 )
790 }
791
792 pub(crate) fn abandon_synchronous_render(&mut self, epoch: SnapshotEpoch) {
793 let atlas = self.font_atlas_snapshot_target();
794 self.snapshot_hub.abandon_synchronous(
795 epoch,
796 &mut self.texture_registry.borrow_mut(),
797 atlas,
798 );
799 }
800
801 #[cfg(feature = "multi-viewport")]
802 pub(super) fn capture_platform_snapshot(
803 &mut self,
804 consumer: &DetachedRendererConsumer,
805 ) -> Result<FrameSnapshot, SnapshotError> {
806 let atlas = self.font_atlas_snapshot_target();
807 let _ = self.poll_snapshot_completions_with_target(&atlas)?;
808 let platform_io_ptr = self.platform_io_ptr("Context::capture_platform_snapshot()");
809 let platform_io =
810 unsafe { crate::platform_io::PlatformIo::from_raw(platform_io_ptr.cast_const()) };
811 let mut pending = {
812 let registry = self.texture_registry.borrow();
813 let mut resolve = |native| registry.resolve_snapshot_texture(native, &atlas);
814 unsafe { capture_platform_io(platform_io, &mut resolve)? }
817 };
818 self.texture_registry
819 .borrow_mut()
820 .track_snapshot_operations(&mut pending.texture_requests, &atlas)?;
821 self.snapshot_hub.begin_snapshot(
822 consumer,
823 pending,
824 &mut self.texture_registry.borrow_mut(),
825 &atlas,
826 )
827 }
828
829 pub(super) fn font_atlas_snapshot_target(&self) -> FontAtlasSnapshotTarget {
830 let io = self.io_ptr("Context snapshot texture capture");
831 let atlas = unsafe { (*io).Fonts };
832 assert!(!atlas.is_null(), "Context has no font atlas");
833 let textures = crate::fonts::font_atlas_snapshot_identities(atlas, self.raw)
834 .into_iter()
835 .map(|identity| {
836 FontAtlasTextureTarget::new(
837 SnapshotTextureId::FontAtlas {
838 context: self.id(),
839 stamp: identity.stamp,
840 generation: identity.texture_generation,
841 },
842 identity.revision,
843 identity.texture,
844 )
845 })
846 .collect();
847 FontAtlasSnapshotTarget::new(atlas, self.id(), textures)
848 }
849}
850
851#[cfg(test)]
852mod tests {
853 use super::*;
854
855 #[test]
856 fn renderer_consumer_preflight_failure_does_not_claim_the_font_atlas() {
857 let _guard = crate::test_support::imgui_context_guard();
858 let atlas = crate::SharedFontAtlas::create();
859 let first = Context::create_with_shared_font_atlas(atlas.clone());
860 let suspended = first.suspend_or_panic();
861 let second = Context::create_with_shared_font_atlas(atlas.clone());
862
863 assert_eq!(
864 second.preflight_renderer_consumer(),
865 Err(
866 RendererConsumerError::SharedFontAtlasRequiresExclusiveContext {
867 registered_contexts: 2,
868 }
869 )
870 );
871
872 drop(second);
873 let replacement = Context::try_create_with_shared_font_atlas(atlas.clone())
874 .expect("preflight must not claim the shared font atlas");
875 drop(replacement);
876 drop(suspended);
877 }
878
879 #[test]
880 fn renderer_consumer_hub_failure_does_not_claim_the_font_atlas() {
881 let _guard = crate::test_support::imgui_context_guard();
882 let atlas = crate::SharedFontAtlas::create();
883 let mut context = Context::create_with_shared_font_atlas(atlas.clone());
884 context.snapshot_hub.next_consumer_generation = None;
885
886 assert!(matches!(
887 context.create_synchronous_renderer_consumer(),
888 Err(RendererConsumerError::ConsumerGenerationExhausted)
889 ));
890
891 let suspended = context.suspend_or_panic();
892 let second = Context::try_create_with_shared_font_atlas(atlas.clone())
893 .expect("failed consumer admission must not claim the shared font atlas");
894 drop(second);
895 drop(suspended);
896 }
897}