1use std::cell::RefCell;
2use std::rc::Rc;
3
4use image::RgbaImage;
5use rootvg_core::math::PhysicalSizeU32;
6
7#[derive(Debug)]
8enum TextureSource {
9 Image {
10 data_to_upload: Option<RgbaImage>,
11 uploaded_texture: Option<wgpu::Texture>,
12 },
13 Prepass {
14 view: wgpu::TextureView,
15 },
16}
17
18#[derive(Debug)]
19pub(crate) struct TextureInner {
20 source: TextureSource,
21 pub(crate) bind_group: Option<wgpu::BindGroup>,
22}
23
24#[derive(Debug)]
29pub struct RcTexture {
30 pub(crate) inner: Rc<RefCell<TextureInner>>,
31 size: PhysicalSizeU32,
32 generation: u64,
33}
34
35impl RcTexture {
36 pub fn new(image: impl Into<RgbaImage>) -> Self {
37 let image: RgbaImage = image.into();
38
39 let dimensions = image.dimensions();
40
41 Self {
42 inner: Rc::new(RefCell::new(TextureInner {
43 source: TextureSource::Image {
44 data_to_upload: Some(image),
45 uploaded_texture: None,
46 },
47 bind_group: None,
48 })),
49 size: PhysicalSizeU32::new(dimensions.0, dimensions.1),
50 generation: 0,
51 }
52 }
53
54 pub fn from_prepass_texture(texture_view: wgpu::TextureView, size: PhysicalSizeU32) -> Self {
55 Self {
56 inner: Rc::new(RefCell::new(TextureInner {
57 source: TextureSource::Prepass { view: texture_view },
58 bind_group: None,
59 })),
60 size,
61 generation: 0,
62 }
63 }
64
65 pub fn replace_with_image(&mut self, image: impl Into<RgbaImage>) -> Result<(), ()> {
67 let image: RgbaImage = image.into();
68 let dimensions = image.dimensions();
69 let size = PhysicalSizeU32::new(dimensions.0, dimensions.1);
70
71 if size != self.size {
72 return Err(());
73 }
74
75 let mut inner = RefCell::borrow_mut(&self.inner);
76
77 let TextureSource::Image { data_to_upload, .. } = &mut inner.source else {
78 return Err(());
79 };
80
81 *data_to_upload = Some(image);
82
83 self.generation += 1;
84
85 Ok(())
86 }
87
88 pub fn replace_prepass_texture(
90 &mut self,
91 texture_view: wgpu::TextureView,
92 size: PhysicalSizeU32,
93 ) -> Result<(), ()> {
94 if self.size != size {
95 return Err(());
96 }
97
98 let mut inner = RefCell::borrow_mut(&self.inner);
99
100 let TextureSource::Prepass { view } = &mut inner.source else {
101 return Err(());
102 };
103
104 *view = texture_view;
105
106 inner.bind_group = None;
107
108 self.generation += 1;
109
110 Ok(())
111 }
112
113 pub fn mark_prepass_texture_dirty(&mut self) {
114 self.generation += 1;
115 }
116
117 pub fn size(&self) -> PhysicalSizeU32 {
118 self.size
119 }
120
121 pub(crate) fn upload_if_needed(
122 &self,
123 device: &wgpu::Device,
124 queue: &wgpu::Queue,
125 texture_bind_group_layout: &wgpu::BindGroupLayout,
126 ) {
127 let mut inner = RefCell::borrow_mut(&self.inner);
128
129 let TextureInner { source, bind_group } = &mut *inner;
130
131 match source {
132 TextureSource::Image {
133 data_to_upload,
134 uploaded_texture,
135 } => {
136 let Some(data_to_upload) = data_to_upload.take() else {
137 return;
138 };
139
140 if bind_group.is_none() {
141 let dimensions = data_to_upload.dimensions();
142 let texture_size = wgpu::Extent3d {
143 width: dimensions.0,
144 height: dimensions.1,
145 depth_or_array_layers: 1,
146 };
147
148 let texture = device.create_texture(&wgpu::TextureDescriptor {
149 size: texture_size,
152 mip_level_count: 1,
153 sample_count: 1,
154 dimension: wgpu::TextureDimension::D2,
155 format: wgpu::TextureFormat::Rgba8UnormSrgb,
156 usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
159 label: None,
160 view_formats: &[],
161 });
162
163 let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
164
165 let new_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
166 layout: &texture_bind_group_layout,
167 entries: &[wgpu::BindGroupEntry {
168 binding: 0,
169 resource: wgpu::BindingResource::TextureView(&view),
170 }],
171 label: None,
172 });
173
174 *bind_group = Some(new_bind_group);
175
176 *uploaded_texture = Some(texture);
177 };
178
179 let uploaded_texture = uploaded_texture.as_ref().unwrap();
180
181 let texture_size = wgpu::Extent3d {
182 width: self.size.width,
183 height: self.size.height,
184 depth_or_array_layers: 1,
185 };
186
187 queue.write_texture(
188 wgpu::ImageCopyTexture {
189 texture: uploaded_texture,
190 mip_level: 0,
191 origin: wgpu::Origin3d::ZERO,
192 aspect: wgpu::TextureAspect::All,
193 },
194 &data_to_upload,
195 wgpu::ImageDataLayout {
197 offset: 0,
198 bytes_per_row: Some(4 * self.size.width),
199 rows_per_image: Some(self.size.height),
200 },
201 texture_size,
202 );
203 }
204 TextureSource::Prepass { view } => {
205 if bind_group.is_some() {
206 return;
207 }
208
209 let new_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
210 layout: &texture_bind_group_layout,
211 entries: &[wgpu::BindGroupEntry {
212 binding: 0,
213 resource: wgpu::BindingResource::TextureView(view),
214 }],
215 label: None,
216 });
217
218 *bind_group = Some(new_bind_group);
219 }
220 }
221 }
222}
223
224impl Clone for RcTexture {
225 fn clone(&self) -> Self {
226 Self {
227 inner: Rc::clone(&self.inner),
228 size: self.size,
229 generation: self.generation,
230 }
231 }
232}
233
234impl PartialEq for RcTexture {
235 fn eq(&self, other: &Self) -> bool {
236 Rc::ptr_eq(&self.inner, &other.inner) && self.generation == other.generation
237 }
238}
239
240impl From<RgbaImage> for RcTexture {
241 fn from(image: RgbaImage) -> Self {
242 RcTexture::new(image)
243 }
244}