1use std::collections::HashMap;
2use std::path::Path;
3
4use anyhow::{Context, Result};
5
6use super::gpu::GpuContext;
7
8pub type TextureId = u32;
10
11struct TextureEntry {
13 _texture: wgpu::Texture,
14 bind_group: wgpu::BindGroup,
15 width: u32,
16 height: u32,
17}
18
19pub struct TextureStore {
21 textures: HashMap<TextureId, TextureEntry>,
22 path_to_id: HashMap<String, TextureId>,
23 next_id: TextureId,
24 render_target_bgs: HashMap<TextureId, (wgpu::BindGroup, u32, u32)>,
28}
29
30impl TextureStore {
31 pub fn new() -> Self {
32 Self {
33 textures: HashMap::new(),
34 path_to_id: HashMap::new(),
35 render_target_bgs: HashMap::new(),
36 next_id: 1, }
38 }
39
40 pub fn load(
43 &mut self,
44 gpu: &GpuContext,
45 bind_group_layout: &wgpu::BindGroupLayout,
46 path: &Path,
47 ) -> Result<TextureId> {
48 let path_str = path.to_string_lossy().to_string();
49
50 if let Some(&id) = self.path_to_id.get(&path_str) {
51 return Ok(id);
52 }
53
54 let img_data = std::fs::read(path)
55 .with_context(|| format!("Failed to read texture: {}", path.display()))?;
56
57 let img = image::load_from_memory(&img_data)
58 .with_context(|| format!("Failed to decode image: {}", path.display()))?
59 .to_rgba8();
60
61 let (width, height) = img.dimensions();
62
63 let texture = gpu.device.create_texture(&wgpu::TextureDescriptor {
64 label: Some(&path_str),
65 size: wgpu::Extent3d {
66 width,
67 height,
68 depth_or_array_layers: 1,
69 },
70 mip_level_count: 1,
71 sample_count: 1,
72 dimension: wgpu::TextureDimension::D2,
73 format: wgpu::TextureFormat::Rgba8UnormSrgb,
74 usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
75 view_formats: &[],
76 });
77
78 gpu.queue.write_texture(
79 wgpu::TexelCopyTextureInfo {
80 texture: &texture,
81 mip_level: 0,
82 origin: wgpu::Origin3d::ZERO,
83 aspect: wgpu::TextureAspect::All,
84 },
85 &img,
86 wgpu::TexelCopyBufferLayout {
87 offset: 0,
88 bytes_per_row: Some(4 * width),
89 rows_per_image: Some(height),
90 },
91 wgpu::Extent3d {
92 width,
93 height,
94 depth_or_array_layers: 1,
95 },
96 );
97
98 let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
99 let sampler = gpu.device.create_sampler(&wgpu::SamplerDescriptor {
100 address_mode_u: wgpu::AddressMode::ClampToEdge,
101 address_mode_v: wgpu::AddressMode::ClampToEdge,
102 mag_filter: wgpu::FilterMode::Nearest,
103 min_filter: wgpu::FilterMode::Nearest,
104 ..Default::default()
105 });
106
107 let bind_group = gpu.device.create_bind_group(&wgpu::BindGroupDescriptor {
108 label: Some(&format!("texture_bind_group_{}", self.next_id)),
109 layout: bind_group_layout,
110 entries: &[
111 wgpu::BindGroupEntry {
112 binding: 0,
113 resource: wgpu::BindingResource::TextureView(&view),
114 },
115 wgpu::BindGroupEntry {
116 binding: 1,
117 resource: wgpu::BindingResource::Sampler(&sampler),
118 },
119 ],
120 });
121
122 let id = self.next_id;
123 self.next_id += 1;
124
125 self.textures.insert(
126 id,
127 TextureEntry {
128 _texture: texture,
129 bind_group,
130 width,
131 height,
132 },
133 );
134 self.path_to_id.insert(path_str, id);
135
136 Ok(id)
137 }
138
139 pub fn create_solid_color(
141 &mut self,
142 gpu: &GpuContext,
143 bind_group_layout: &wgpu::BindGroupLayout,
144 name: &str,
145 r: u8,
146 g: u8,
147 b: u8,
148 a: u8,
149 ) -> TextureId {
150 let path_key = format!("__solid__{name}");
151 if let Some(&id) = self.path_to_id.get(&path_key) {
152 return id;
153 }
154
155 let texture = gpu.device.create_texture(&wgpu::TextureDescriptor {
156 label: Some(name),
157 size: wgpu::Extent3d {
158 width: 1,
159 height: 1,
160 depth_or_array_layers: 1,
161 },
162 mip_level_count: 1,
163 sample_count: 1,
164 dimension: wgpu::TextureDimension::D2,
165 format: wgpu::TextureFormat::Rgba8UnormSrgb,
166 usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
167 view_formats: &[],
168 });
169
170 gpu.queue.write_texture(
171 wgpu::TexelCopyTextureInfo {
172 texture: &texture,
173 mip_level: 0,
174 origin: wgpu::Origin3d::ZERO,
175 aspect: wgpu::TextureAspect::All,
176 },
177 &[r, g, b, a],
178 wgpu::TexelCopyBufferLayout {
179 offset: 0,
180 bytes_per_row: Some(4),
181 rows_per_image: Some(1),
182 },
183 wgpu::Extent3d {
184 width: 1,
185 height: 1,
186 depth_or_array_layers: 1,
187 },
188 );
189
190 let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
191 let sampler = gpu.device.create_sampler(&wgpu::SamplerDescriptor {
192 mag_filter: wgpu::FilterMode::Nearest,
193 min_filter: wgpu::FilterMode::Nearest,
194 ..Default::default()
195 });
196
197 let bind_group = gpu.device.create_bind_group(&wgpu::BindGroupDescriptor {
198 label: Some(&format!("solid_color_bind_group_{name}")),
199 layout: bind_group_layout,
200 entries: &[
201 wgpu::BindGroupEntry {
202 binding: 0,
203 resource: wgpu::BindingResource::TextureView(&view),
204 },
205 wgpu::BindGroupEntry {
206 binding: 1,
207 resource: wgpu::BindingResource::Sampler(&sampler),
208 },
209 ],
210 });
211
212 let id = self.next_id;
213 self.next_id += 1;
214
215 self.textures.insert(
216 id,
217 TextureEntry {
218 _texture: texture,
219 bind_group,
220 width: 1,
221 height: 1,
222 },
223 );
224 self.path_to_id.insert(path_key, id);
225
226 id
227 }
228
229 pub fn upload_raw(
232 &mut self,
233 gpu: &GpuContext,
234 bind_group_layout: &wgpu::BindGroupLayout,
235 id: TextureId,
236 pixels: &[u8],
237 width: u32,
238 height: u32,
239 ) {
240 let texture = gpu.device.create_texture(&wgpu::TextureDescriptor {
241 label: Some(&format!("raw_texture_{id}")),
242 size: wgpu::Extent3d {
243 width,
244 height,
245 depth_or_array_layers: 1,
246 },
247 mip_level_count: 1,
248 sample_count: 1,
249 dimension: wgpu::TextureDimension::D2,
250 format: wgpu::TextureFormat::Rgba8UnormSrgb,
251 usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
252 view_formats: &[],
253 });
254
255 gpu.queue.write_texture(
256 wgpu::TexelCopyTextureInfo {
257 texture: &texture,
258 mip_level: 0,
259 origin: wgpu::Origin3d::ZERO,
260 aspect: wgpu::TextureAspect::All,
261 },
262 pixels,
263 wgpu::TexelCopyBufferLayout {
264 offset: 0,
265 bytes_per_row: Some(4 * width),
266 rows_per_image: Some(height),
267 },
268 wgpu::Extent3d {
269 width,
270 height,
271 depth_or_array_layers: 1,
272 },
273 );
274
275 let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
276 let sampler = gpu.device.create_sampler(&wgpu::SamplerDescriptor {
277 mag_filter: wgpu::FilterMode::Nearest,
278 min_filter: wgpu::FilterMode::Nearest,
279 ..Default::default()
280 });
281
282 let bind_group = gpu.device.create_bind_group(&wgpu::BindGroupDescriptor {
283 label: Some(&format!("raw_texture_bind_group_{id}")),
284 layout: bind_group_layout,
285 entries: &[
286 wgpu::BindGroupEntry {
287 binding: 0,
288 resource: wgpu::BindingResource::TextureView(&view),
289 },
290 wgpu::BindGroupEntry {
291 binding: 1,
292 resource: wgpu::BindingResource::Sampler(&sampler),
293 },
294 ],
295 });
296
297 self.textures.insert(
298 id,
299 TextureEntry {
300 _texture: texture,
301 bind_group,
302 width,
303 height,
304 },
305 );
306 }
307
308 pub fn upload_raw_linear(
311 &mut self,
312 gpu: &GpuContext,
313 bind_group_layout: &wgpu::BindGroupLayout,
314 id: TextureId,
315 pixels: &[u8],
316 width: u32,
317 height: u32,
318 ) {
319 let texture = gpu.device.create_texture(&wgpu::TextureDescriptor {
320 label: Some(&format!("raw_linear_texture_{id}")),
321 size: wgpu::Extent3d {
322 width,
323 height,
324 depth_or_array_layers: 1,
325 },
326 mip_level_count: 1,
327 sample_count: 1,
328 dimension: wgpu::TextureDimension::D2,
329 format: wgpu::TextureFormat::Rgba8Unorm,
330 usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
331 view_formats: &[],
332 });
333
334 gpu.queue.write_texture(
335 wgpu::TexelCopyTextureInfo {
336 texture: &texture,
337 mip_level: 0,
338 origin: wgpu::Origin3d::ZERO,
339 aspect: wgpu::TextureAspect::All,
340 },
341 pixels,
342 wgpu::TexelCopyBufferLayout {
343 offset: 0,
344 bytes_per_row: Some(4 * width),
345 rows_per_image: Some(height),
346 },
347 wgpu::Extent3d {
348 width,
349 height,
350 depth_or_array_layers: 1,
351 },
352 );
353
354 let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
355 let sampler = gpu.device.create_sampler(&wgpu::SamplerDescriptor {
356 mag_filter: wgpu::FilterMode::Linear,
357 min_filter: wgpu::FilterMode::Linear,
358 ..Default::default()
359 });
360
361 let bind_group = gpu.device.create_bind_group(&wgpu::BindGroupDescriptor {
362 label: Some(&format!("raw_linear_texture_bind_group_{id}")),
363 layout: bind_group_layout,
364 entries: &[
365 wgpu::BindGroupEntry {
366 binding: 0,
367 resource: wgpu::BindingResource::TextureView(&view),
368 },
369 wgpu::BindGroupEntry {
370 binding: 1,
371 resource: wgpu::BindingResource::Sampler(&sampler),
372 },
373 ],
374 });
375
376 self.textures.insert(
377 id,
378 TextureEntry {
379 _texture: texture,
380 bind_group,
381 width,
382 height,
383 },
384 );
385 }
386
387 pub fn get_bind_group(&self, id: TextureId) -> Option<&wgpu::BindGroup> {
389 self.textures
390 .get(&id)
391 .map(|e| &e.bind_group)
392 .or_else(|| self.render_target_bgs.get(&id).map(|(bg, _, _)| bg))
393 }
394
395 pub fn get_dimensions(&self, id: TextureId) -> Option<(u32, u32)> {
397 self.textures
398 .get(&id)
399 .map(|e| (e.width, e.height))
400 .or_else(|| self.render_target_bgs.get(&id).map(|&(_, w, h)| (w, h)))
401 }
402
403 pub fn register_render_target(
409 &mut self,
410 gpu: &GpuContext,
411 bind_group_layout: &wgpu::BindGroupLayout,
412 id: TextureId,
413 view: &wgpu::TextureView,
414 width: u32,
415 height: u32,
416 ) {
417 let sampler = gpu.device.create_sampler(&wgpu::SamplerDescriptor {
418 mag_filter: wgpu::FilterMode::Linear,
419 min_filter: wgpu::FilterMode::Linear,
420 ..Default::default()
421 });
422 let bg = gpu.device.create_bind_group(&wgpu::BindGroupDescriptor {
423 label: Some(&format!("render_target_bg_{id}")),
424 layout: bind_group_layout,
425 entries: &[
426 wgpu::BindGroupEntry {
427 binding: 0,
428 resource: wgpu::BindingResource::TextureView(view),
429 },
430 wgpu::BindGroupEntry {
431 binding: 1,
432 resource: wgpu::BindingResource::Sampler(&sampler),
433 },
434 ],
435 });
436 self.render_target_bgs.insert(id, (bg, width, height));
437 }
438
439 pub fn unregister_render_target(&mut self, id: TextureId) {
441 self.render_target_bgs.remove(&id);
442 }
443}