1use crate::error::RenderError;
11use std::path::Path;
12use wgpu::*;
13
14use super::CustomShaderRenderer;
15use super::builtin_textures::BuiltinTextureSpec;
16
17pub struct ChannelTexture {
19 pub texture: Option<Texture>,
22 pub view: TextureView,
24 pub sampler: Sampler,
26 pub width: u32,
28 pub height: u32,
30}
31
32impl ChannelTexture {
33 pub fn placeholder(device: &Device, queue: &Queue) -> Self {
38 let texture = device.create_texture(&TextureDescriptor {
39 label: Some("Channel Placeholder Texture"),
40 size: Extent3d {
41 width: 1,
42 height: 1,
43 depth_or_array_layers: 1,
44 },
45 mip_level_count: 1,
46 sample_count: 1,
47 dimension: TextureDimension::D2,
48 format: TextureFormat::Rgba8UnormSrgb,
49 usage: TextureUsages::TEXTURE_BINDING | TextureUsages::COPY_DST,
50 view_formats: &[],
51 });
52
53 queue.write_texture(
55 TexelCopyTextureInfo {
56 texture: &texture,
57 mip_level: 0,
58 origin: Origin3d::ZERO,
59 aspect: TextureAspect::All,
60 },
61 &[0u8, 0, 0, 0], TexelCopyBufferLayout {
63 offset: 0,
64 bytes_per_row: Some(4),
65 rows_per_image: Some(1),
66 },
67 Extent3d {
68 width: 1,
69 height: 1,
70 depth_or_array_layers: 1,
71 },
72 );
73
74 let view = texture.create_view(&TextureViewDescriptor::default());
75 let sampler = device.create_sampler(&SamplerDescriptor {
76 label: Some("Channel Placeholder Sampler"),
77 address_mode_u: AddressMode::ClampToEdge,
78 address_mode_v: AddressMode::ClampToEdge,
79 address_mode_w: AddressMode::ClampToEdge,
80 mag_filter: FilterMode::Linear,
81 min_filter: FilterMode::Linear,
82 mipmap_filter: MipmapFilterMode::Linear,
83 ..Default::default()
84 });
85
86 Self {
87 texture: Some(texture),
88 view,
89 sampler,
90 width: 1,
91 height: 1,
92 }
93 }
94
95 pub fn from_view(view: TextureView, sampler: Sampler, width: u32, height: u32) -> Self {
107 Self {
108 texture: None,
109 view,
110 sampler,
111 width,
112 height,
113 }
114 }
115
116 pub fn from_view_and_texture(
128 view: TextureView,
129 sampler: Sampler,
130 width: u32,
131 height: u32,
132 texture: Texture,
133 ) -> Self {
134 Self {
135 texture: Some(texture),
136 view,
137 sampler,
138 width,
139 height,
140 }
141 }
142
143 pub fn from_file(device: &Device, queue: &Queue, path: &Path) -> Result<Self, RenderError> {
155 let path_label = path.display().to_string();
156 if path_label.starts_with("builtin://noise/") {
157 let spec =
158 BuiltinTextureSpec::parse(&path_label).map_err(RenderError::NoActiveShader)?;
159 return Ok(Self::from_builtin(device, queue, spec, &path_label));
160 }
161
162 let img = image::open(path)
164 .map_err(|e| RenderError::ImageLoad {
165 path: path.display().to_string(),
166 source: e,
167 })?
168 .to_rgba8();
169
170 let (width, height) = img.dimensions();
171 Self::from_rgba8(
172 device,
173 queue,
174 &format!("Channel Texture: {}", path.display()),
175 width,
176 height,
177 img.as_raw(),
178 )
179 }
180
181 fn from_builtin(device: &Device, queue: &Queue, spec: BuiltinTextureSpec, label: &str) -> Self {
182 let generated = spec.generate_rgba8();
183 let texture = Self::from_rgba8(
184 device,
185 queue,
186 &format!("Channel Texture: {label}"),
187 generated.width,
188 generated.height,
189 &generated.pixels,
190 )
191 .expect("generated built-in texture byte length must match its dimensions");
192 log::info!(
193 "Loaded built-in channel texture: {} ({}x{})",
194 label,
195 generated.width,
196 generated.height
197 );
198 texture
199 }
200
201 fn from_rgba8(
202 device: &Device,
203 queue: &Queue,
204 label: &str,
205 width: u32,
206 height: u32,
207 pixels: &[u8],
208 ) -> Result<Self, RenderError> {
209 let expected = (width as usize) * (height as usize) * 4;
210 if pixels.len() != expected {
211 return Err(RenderError::InvalidTextureData {
212 expected,
213 actual: pixels.len(),
214 });
215 }
216
217 let texture = device.create_texture(&TextureDescriptor {
219 label: Some(label),
220 size: Extent3d {
221 width,
222 height,
223 depth_or_array_layers: 1,
224 },
225 mip_level_count: 1,
226 sample_count: 1,
227 dimension: TextureDimension::D2,
228 format: TextureFormat::Rgba8UnormSrgb,
229 usage: TextureUsages::TEXTURE_BINDING | TextureUsages::COPY_DST,
230 view_formats: &[],
231 });
232
233 queue.write_texture(
235 TexelCopyTextureInfo {
236 texture: &texture,
237 mip_level: 0,
238 origin: Origin3d::ZERO,
239 aspect: TextureAspect::All,
240 },
241 pixels,
242 TexelCopyBufferLayout {
243 offset: 0,
244 bytes_per_row: Some(4 * width),
245 rows_per_image: Some(height),
246 },
247 Extent3d {
248 width,
249 height,
250 depth_or_array_layers: 1,
251 },
252 );
253
254 let view = texture.create_view(&TextureViewDescriptor::default());
255
256 let sampler = device.create_sampler(&SamplerDescriptor {
258 label: Some(&format!("{label} Sampler")),
259 address_mode_u: AddressMode::Repeat,
260 address_mode_v: AddressMode::Repeat,
261 address_mode_w: AddressMode::Repeat,
262 mag_filter: FilterMode::Linear,
263 min_filter: FilterMode::Linear,
264 mipmap_filter: MipmapFilterMode::Linear,
265 ..Default::default()
266 });
267
268 log::info!("Loaded channel texture: {} ({}x{})", label, width, height);
269
270 Ok(Self {
271 texture: Some(texture),
272 view,
273 sampler,
274 width,
275 height,
276 })
277 }
278
279 pub fn resolution(&self) -> [f32; 4] {
283 [self.width as f32, self.height as f32, 1.0, 0.0]
284 }
285}
286
287pub fn load_channel_textures(
297 device: &Device,
298 queue: &Queue,
299 paths: &[Option<std::path::PathBuf>; 4],
300) -> [ChannelTexture; 4] {
301 let load_or_placeholder = |path: &Option<std::path::PathBuf>, index: usize| -> ChannelTexture {
302 match path {
303 Some(p) => match ChannelTexture::from_file(device, queue, p) {
304 Ok(tex) => tex,
305 Err(e) => {
306 log::error!(
307 "Failed to load iChannel{} texture '{}': {}",
308 index,
309 p.display(),
310 e
311 );
312 ChannelTexture::placeholder(device, queue)
313 }
314 },
315 None => ChannelTexture::placeholder(device, queue),
316 }
317 };
318
319 [
320 load_or_placeholder(&paths[0], 0),
321 load_or_placeholder(&paths[1], 1),
322 load_or_placeholder(&paths[2], 2),
323 load_or_placeholder(&paths[3], 3),
324 ]
325}
326
327impl CustomShaderRenderer {
330 pub(super) fn create_intermediate_texture(
335 device: &Device,
336 format: TextureFormat,
337 width: u32,
338 height: u32,
339 ) -> (Texture, TextureView) {
340 let texture = device.create_texture(&TextureDescriptor {
341 label: Some("Custom Shader Intermediate Texture"),
342 size: Extent3d {
343 width: width.max(1),
344 height: height.max(1),
345 depth_or_array_layers: 1,
346 },
347 mip_level_count: 1,
348 sample_count: 1,
349 dimension: TextureDimension::D2,
350 format,
351 usage: TextureUsages::RENDER_ATTACHMENT | TextureUsages::TEXTURE_BINDING,
352 view_formats: &[],
353 });
354
355 let view = texture.create_view(&TextureViewDescriptor::default());
356 (texture, view)
357 }
358
359 pub fn clear_intermediate_texture(&self, device: &Device, queue: &Queue) {
363 let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
364 label: Some("Clear Intermediate Texture Encoder"),
365 });
366
367 {
368 let _clear_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
369 label: Some("Clear Intermediate Texture Pass"),
370 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
371 view: &self.intermediate_texture_view,
372 resolve_target: None,
373 ops: wgpu::Operations {
374 load: wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT),
375 store: wgpu::StoreOp::Store,
376 },
377 depth_slice: None,
378 })],
379 depth_stencil_attachment: None,
380 timestamp_writes: None,
381 occlusion_query_set: None,
382 multiview_mask: None,
383 });
384 }
385
386 queue.submit(std::iter::once(encoder.finish()));
387 }
388
389 pub fn resize(&mut self, device: &Device, width: u32, height: u32) {
391 if width == self.texture_width && height == self.texture_height {
392 return;
393 }
394
395 self.texture_width = width;
396 self.texture_height = height;
397
398 let (texture, view) =
400 Self::create_intermediate_texture(device, self.surface_format, width, height);
401 self.intermediate_texture = texture;
402 self.intermediate_texture_view = view;
403
404 self.recreate_bind_group(device);
406 }
407
408 pub(super) fn recreate_bind_group(&mut self, device: &Device) {
421 let channel0_texture = if self.use_background_as_channel0 {
423 self.background_channel_texture
425 .as_ref()
426 .unwrap_or(&self.channel_textures[0])
427 } else if self.channel0_has_real_texture() {
428 &self.channel_textures[0]
430 } else {
431 &self.channel_textures[0]
433 };
434
435 let effective_channels = [
437 channel0_texture,
438 &self.channel_textures[1],
439 &self.channel_textures[2],
440 &self.channel_textures[3],
441 ];
442
443 self.bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
444 label: Some("Custom Shader Bind Group"),
445 layout: &self.bind_group_layout,
446 entries: &[
447 wgpu::BindGroupEntry {
448 binding: 0,
449 resource: self.uniform_buffer.as_entire_binding(),
450 },
451 wgpu::BindGroupEntry {
453 binding: 1,
454 resource: wgpu::BindingResource::TextureView(&effective_channels[0].view),
455 },
456 wgpu::BindGroupEntry {
457 binding: 2,
458 resource: wgpu::BindingResource::Sampler(&effective_channels[0].sampler),
459 },
460 wgpu::BindGroupEntry {
462 binding: 3,
463 resource: wgpu::BindingResource::TextureView(&effective_channels[1].view),
464 },
465 wgpu::BindGroupEntry {
466 binding: 4,
467 resource: wgpu::BindingResource::Sampler(&effective_channels[1].sampler),
468 },
469 wgpu::BindGroupEntry {
471 binding: 5,
472 resource: wgpu::BindingResource::TextureView(&effective_channels[2].view),
473 },
474 wgpu::BindGroupEntry {
475 binding: 6,
476 resource: wgpu::BindingResource::Sampler(&effective_channels[2].sampler),
477 },
478 wgpu::BindGroupEntry {
480 binding: 7,
481 resource: wgpu::BindingResource::TextureView(&effective_channels[3].view),
482 },
483 wgpu::BindGroupEntry {
484 binding: 8,
485 resource: wgpu::BindingResource::Sampler(&effective_channels[3].sampler),
486 },
487 wgpu::BindGroupEntry {
489 binding: 9,
490 resource: wgpu::BindingResource::TextureView(&self.intermediate_texture_view),
491 },
492 wgpu::BindGroupEntry {
493 binding: 10,
494 resource: wgpu::BindingResource::Sampler(&self.sampler),
495 },
496 wgpu::BindGroupEntry {
498 binding: 11,
499 resource: wgpu::BindingResource::TextureView(&self.cubemap.view),
500 },
501 wgpu::BindGroupEntry {
502 binding: 12,
503 resource: wgpu::BindingResource::Sampler(&self.cubemap.sampler),
504 },
505 wgpu::BindGroupEntry {
507 binding: 13,
508 resource: self.custom_uniform_buffer.as_entire_binding(),
509 },
510 ],
511 });
512 }
513
514 pub(super) fn channel0_has_real_texture(&self) -> bool {
516 let ch0 = &self.channel_textures[0];
517 ch0.width > 1 || ch0.height > 1
519 }
520
521 pub(super) fn effective_channel0_resolution(&self) -> [f32; 4] {
528 if self.use_background_as_channel0 {
529 self.background_channel_texture
530 .as_ref()
531 .map(|t| t.resolution())
532 .unwrap_or_else(|| self.channel_textures[0].resolution())
533 } else {
534 self.channel_textures[0].resolution()
535 }
536 }
537}