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 device: &wgpu::Device,
143 queue: &wgpu::Queue,
144 bind_group_layout: &wgpu::BindGroupLayout,
145 name: &str,
146 r: u8,
147 g: u8,
148 b: u8,
149 a: u8,
150 ) -> TextureId {
151 let path_key = format!("__solid__{name}");
152 if let Some(&id) = self.path_to_id.get(&path_key) {
153 return id;
154 }
155
156 let texture = device.create_texture(&wgpu::TextureDescriptor {
157 label: Some(name),
158 size: wgpu::Extent3d {
159 width: 1,
160 height: 1,
161 depth_or_array_layers: 1,
162 },
163 mip_level_count: 1,
164 sample_count: 1,
165 dimension: wgpu::TextureDimension::D2,
166 format: wgpu::TextureFormat::Rgba8UnormSrgb,
167 usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
168 view_formats: &[],
169 });
170
171 queue.write_texture(
172 wgpu::TexelCopyTextureInfo {
173 texture: &texture,
174 mip_level: 0,
175 origin: wgpu::Origin3d::ZERO,
176 aspect: wgpu::TextureAspect::All,
177 },
178 &[r, g, b, a],
179 wgpu::TexelCopyBufferLayout {
180 offset: 0,
181 bytes_per_row: Some(4),
182 rows_per_image: Some(1),
183 },
184 wgpu::Extent3d {
185 width: 1,
186 height: 1,
187 depth_or_array_layers: 1,
188 },
189 );
190
191 let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
192 let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
193 mag_filter: wgpu::FilterMode::Nearest,
194 min_filter: wgpu::FilterMode::Nearest,
195 ..Default::default()
196 });
197
198 let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
199 label: Some(&format!("solid_color_bind_group_{name}")),
200 layout: bind_group_layout,
201 entries: &[
202 wgpu::BindGroupEntry {
203 binding: 0,
204 resource: wgpu::BindingResource::TextureView(&view),
205 },
206 wgpu::BindGroupEntry {
207 binding: 1,
208 resource: wgpu::BindingResource::Sampler(&sampler),
209 },
210 ],
211 });
212
213 let id = self.next_id;
214 self.next_id += 1;
215
216 self.textures.insert(
217 id,
218 TextureEntry {
219 _texture: texture,
220 bind_group,
221 width: 1,
222 height: 1,
223 },
224 );
225 self.path_to_id.insert(path_key, id);
226
227 id
228 }
229
230 pub fn upload_raw(
233 &mut self,
234 device: &wgpu::Device,
235 queue: &wgpu::Queue,
236 bind_group_layout: &wgpu::BindGroupLayout,
237 id: TextureId,
238 pixels: &[u8],
239 width: u32,
240 height: u32,
241 ) {
242 let texture = device.create_texture(&wgpu::TextureDescriptor {
243 label: Some(&format!("raw_texture_{id}")),
244 size: wgpu::Extent3d {
245 width,
246 height,
247 depth_or_array_layers: 1,
248 },
249 mip_level_count: 1,
250 sample_count: 1,
251 dimension: wgpu::TextureDimension::D2,
252 format: wgpu::TextureFormat::Rgba8UnormSrgb,
253 usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
254 view_formats: &[],
255 });
256
257 queue.write_texture(
258 wgpu::TexelCopyTextureInfo {
259 texture: &texture,
260 mip_level: 0,
261 origin: wgpu::Origin3d::ZERO,
262 aspect: wgpu::TextureAspect::All,
263 },
264 pixels,
265 wgpu::TexelCopyBufferLayout {
266 offset: 0,
267 bytes_per_row: Some(4 * width),
268 rows_per_image: Some(height),
269 },
270 wgpu::Extent3d {
271 width,
272 height,
273 depth_or_array_layers: 1,
274 },
275 );
276
277 let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
278 let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
279 mag_filter: wgpu::FilterMode::Nearest,
280 min_filter: wgpu::FilterMode::Nearest,
281 ..Default::default()
282 });
283
284 let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
285 label: Some(&format!("raw_texture_bind_group_{id}")),
286 layout: bind_group_layout,
287 entries: &[
288 wgpu::BindGroupEntry {
289 binding: 0,
290 resource: wgpu::BindingResource::TextureView(&view),
291 },
292 wgpu::BindGroupEntry {
293 binding: 1,
294 resource: wgpu::BindingResource::Sampler(&sampler),
295 },
296 ],
297 });
298
299 self.textures.insert(
300 id,
301 TextureEntry {
302 _texture: texture,
303 bind_group,
304 width,
305 height,
306 },
307 );
308 }
309
310 pub fn upload_raw_linear(
313 &mut self,
314 device: &wgpu::Device,
315 queue: &wgpu::Queue,
316 bind_group_layout: &wgpu::BindGroupLayout,
317 id: TextureId,
318 pixels: &[u8],
319 width: u32,
320 height: u32,
321 ) {
322 let texture = device.create_texture(&wgpu::TextureDescriptor {
323 label: Some(&format!("raw_linear_texture_{id}")),
324 size: wgpu::Extent3d {
325 width,
326 height,
327 depth_or_array_layers: 1,
328 },
329 mip_level_count: 1,
330 sample_count: 1,
331 dimension: wgpu::TextureDimension::D2,
332 format: wgpu::TextureFormat::Rgba8Unorm,
333 usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
334 view_formats: &[],
335 });
336
337 queue.write_texture(
338 wgpu::TexelCopyTextureInfo {
339 texture: &texture,
340 mip_level: 0,
341 origin: wgpu::Origin3d::ZERO,
342 aspect: wgpu::TextureAspect::All,
343 },
344 pixels,
345 wgpu::TexelCopyBufferLayout {
346 offset: 0,
347 bytes_per_row: Some(4 * width),
348 rows_per_image: Some(height),
349 },
350 wgpu::Extent3d {
351 width,
352 height,
353 depth_or_array_layers: 1,
354 },
355 );
356
357 let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
358 let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
359 mag_filter: wgpu::FilterMode::Linear,
360 min_filter: wgpu::FilterMode::Linear,
361 ..Default::default()
362 });
363
364 let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
365 label: Some(&format!("raw_linear_texture_bind_group_{id}")),
366 layout: bind_group_layout,
367 entries: &[
368 wgpu::BindGroupEntry {
369 binding: 0,
370 resource: wgpu::BindingResource::TextureView(&view),
371 },
372 wgpu::BindGroupEntry {
373 binding: 1,
374 resource: wgpu::BindingResource::Sampler(&sampler),
375 },
376 ],
377 });
378
379 self.textures.insert(
380 id,
381 TextureEntry {
382 _texture: texture,
383 bind_group,
384 width,
385 height,
386 },
387 );
388 }
389
390 pub fn get_bind_group(&self, id: TextureId) -> Option<&wgpu::BindGroup> {
392 self.textures
393 .get(&id)
394 .map(|e| &e.bind_group)
395 .or_else(|| self.render_target_bgs.get(&id).map(|(bg, _, _)| bg))
396 }
397
398 pub fn get_dimensions(&self, id: TextureId) -> Option<(u32, u32)> {
400 self.textures
401 .get(&id)
402 .map(|e| (e.width, e.height))
403 .or_else(|| self.render_target_bgs.get(&id).map(|&(_, w, h)| (w, h)))
404 }
405
406 pub fn register_render_target(
412 &mut self,
413 device: &wgpu::Device,
414 bind_group_layout: &wgpu::BindGroupLayout,
415 id: TextureId,
416 view: &wgpu::TextureView,
417 width: u32,
418 height: u32,
419 ) {
420 let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
421 mag_filter: wgpu::FilterMode::Linear,
422 min_filter: wgpu::FilterMode::Linear,
423 ..Default::default()
424 });
425 let bg = device.create_bind_group(&wgpu::BindGroupDescriptor {
426 label: Some(&format!("render_target_bg_{id}")),
427 layout: bind_group_layout,
428 entries: &[
429 wgpu::BindGroupEntry {
430 binding: 0,
431 resource: wgpu::BindingResource::TextureView(view),
432 },
433 wgpu::BindGroupEntry {
434 binding: 1,
435 resource: wgpu::BindingResource::Sampler(&sampler),
436 },
437 ],
438 });
439 self.render_target_bgs.insert(id, (bg, width, height));
440 }
441
442 pub fn unregister_render_target(&mut self, id: TextureId) {
444 self.render_target_bgs.remove(&id);
445 }
446}