mittens_engine/engine/ecs/system/
render_to_texture_system.rs1use crate::engine::ecs::component::TextureComponent;
2use crate::engine::ecs::{ComponentId, World};
3use crate::engine::graphics::{TextureUploader, VisualWorld};
4use std::collections::{HashMap, HashSet};
5
6pub const INTERNAL_RENDERER_STENCIL_CLIP_DEBUG_SELECTOR: &str = "render_graph.stencil_clip.debug";
7
8#[derive(Debug, Clone, PartialEq, Eq)]
9pub enum RenderTextureConsumerKind {
10 TextureRenderImage,
11}
12
13#[derive(Debug, Clone, PartialEq, Eq)]
14pub struct RenderTextureConsumerRegistration {
15 pub component: ComponentId,
16 pub selector: String,
17 pub kind: RenderTextureConsumerKind,
18}
19
20#[derive(Debug, Clone, PartialEq, Eq)]
21pub enum RenderTextureProducerKind {
22 InternalRendererImage,
23 SceneCapture,
24 CubeCapture,
25 Mirror,
26 Portal,
27}
28
29#[derive(Debug, Clone, PartialEq, Eq)]
30pub struct RenderTextureProducerRequest {
31 pub selector: String,
32 pub kind: RenderTextureProducerKind,
33}
34
35#[derive(Debug, Default)]
36pub struct RenderToTextureSystem {
37 consumers_by_component: HashMap<ComponentId, RenderTextureConsumerRegistration>,
38 producer_requests_by_selector: HashMap<String, RenderTextureProducerRequest>,
39}
40
41impl RenderToTextureSystem {
42 pub fn new() -> Self {
43 Self::default()
44 }
45
46 pub fn register_texture(&mut self, world: &mut World, component: ComponentId) {
47 let Some(texture) = world.get_component_by_id_as::<TextureComponent>(component) else {
48 self.unregister_texture(component);
49 return;
50 };
51
52 if let Some(selector) = texture.render_image.clone() {
53 let previous = self.consumers_by_component.insert(
54 component,
55 RenderTextureConsumerRegistration {
56 component,
57 selector: selector.clone(),
58 kind: RenderTextureConsumerKind::TextureRenderImage,
59 },
60 );
61 if let Some(previous) = previous {
62 self.remove_selector_if_unused(&previous.selector);
63 }
64
65 self.producer_requests_by_selector.insert(
66 selector.clone(),
67 RenderTextureProducerRequest {
68 selector,
69 kind: producer_kind_for_selector(texture.render_image.as_deref().unwrap()),
70 },
71 );
72 } else {
73 self.unregister_texture(component);
74 }
75 }
76
77 fn unregister_texture(&mut self, component: ComponentId) {
78 if let Some(previous) = self.consumers_by_component.remove(&component) {
79 self.remove_selector_if_unused(&previous.selector);
80 }
81 }
82
83 fn remove_selector_if_unused(&mut self, selector: &str) {
84 let still_used = self
85 .consumers_by_component
86 .values()
87 .any(|registration| registration.selector == selector);
88 if !still_used {
89 self.producer_requests_by_selector.remove(selector);
90 }
91 }
92
93 pub fn consumer_registrations(
94 &self,
95 ) -> impl Iterator<Item = &RenderTextureConsumerRegistration> {
96 self.consumers_by_component.values()
97 }
98
99 pub fn producer_requests(&self) -> impl Iterator<Item = &RenderTextureProducerRequest> {
100 self.producer_requests_by_selector.values()
101 }
102
103 pub fn flush_pending(&mut self, visuals: &mut VisualWorld, uploader: &mut dyn TextureUploader) {
104 visuals.set_stencil_clip_debug_requested(
105 self.producer_requests_by_selector
106 .contains_key(INTERNAL_RENDERER_STENCIL_CLIP_DEBUG_SELECTOR),
107 );
108
109 let selectors: HashSet<String> = self
110 .producer_requests_by_selector
111 .values()
112 .map(|request| request.selector.clone())
113 .collect();
114 let mirror_capture_selectors: HashSet<String> = visuals
115 .mirrors()
116 .iter()
117 .flat_map(|mirror| {
118 mirror
119 .captures
120 .iter()
121 .map(|capture| capture.target_key.clone())
122 })
123 .collect();
124
125 for selector in selectors {
126 if visuals.runtime_texture_handle(&selector).is_some() {
127 continue;
128 }
129
130 match uploader.upload_texture_rgba8(&[0, 0, 0, 255], 1, 1) {
131 Ok(handle) => visuals.set_runtime_texture_handle(selector, handle),
132 Err(err) => {
133 println!(
134 "[RenderToTextureSystem] failed to allocate runtime texture handle: {err}"
135 );
136 }
137 }
138 }
139
140 for selector in mirror_capture_selectors {
141 if visuals.runtime_texture_handle(&selector).is_some() {
142 continue;
143 }
144
145 match uploader.upload_texture_rgba8(&[0, 0, 0, 255], 1, 1) {
146 Ok(handle) => visuals.set_runtime_texture_handle(selector, handle),
147 Err(err) => {
148 println!(
149 "[RenderToTextureSystem] failed to allocate mirror runtime texture handle: {err}"
150 );
151 }
152 }
153 }
154 }
155}
156
157fn producer_kind_for_selector(selector: &str) -> RenderTextureProducerKind {
158 if selector.starts_with("capture.mirror.") {
159 RenderTextureProducerKind::Mirror
160 } else {
161 RenderTextureProducerKind::InternalRendererImage
162 }
163}