1use crate::error::RenderError;
11use std::path::Path;
12use wgpu::*;
13
14use super::CustomShaderRenderer;
15
16pub struct ChannelTexture {
18 pub texture: Option<Texture>,
21 pub view: TextureView,
23 pub sampler: Sampler,
25 pub width: u32,
27 pub height: u32,
29}
30
31impl ChannelTexture {
32 pub fn placeholder(device: &Device, queue: &Queue) -> Self {
37 let texture = device.create_texture(&TextureDescriptor {
38 label: Some("Channel Placeholder Texture"),
39 size: Extent3d {
40 width: 1,
41 height: 1,
42 depth_or_array_layers: 1,
43 },
44 mip_level_count: 1,
45 sample_count: 1,
46 dimension: TextureDimension::D2,
47 format: TextureFormat::Rgba8UnormSrgb,
48 usage: TextureUsages::TEXTURE_BINDING | TextureUsages::COPY_DST,
49 view_formats: &[],
50 });
51
52 queue.write_texture(
54 TexelCopyTextureInfo {
55 texture: &texture,
56 mip_level: 0,
57 origin: Origin3d::ZERO,
58 aspect: TextureAspect::All,
59 },
60 &[0u8, 0, 0, 0], TexelCopyBufferLayout {
62 offset: 0,
63 bytes_per_row: Some(4),
64 rows_per_image: Some(1),
65 },
66 Extent3d {
67 width: 1,
68 height: 1,
69 depth_or_array_layers: 1,
70 },
71 );
72
73 let view = texture.create_view(&TextureViewDescriptor::default());
74 let sampler = device.create_sampler(&SamplerDescriptor {
75 label: Some("Channel Placeholder Sampler"),
76 address_mode_u: AddressMode::ClampToEdge,
77 address_mode_v: AddressMode::ClampToEdge,
78 address_mode_w: AddressMode::ClampToEdge,
79 mag_filter: FilterMode::Linear,
80 min_filter: FilterMode::Linear,
81 mipmap_filter: FilterMode::Linear,
82 ..Default::default()
83 });
84
85 Self {
86 texture: Some(texture),
87 view,
88 sampler,
89 width: 1,
90 height: 1,
91 }
92 }
93
94 pub fn from_view(view: TextureView, sampler: Sampler, width: u32, height: u32) -> Self {
106 Self {
107 texture: None,
108 view,
109 sampler,
110 width,
111 height,
112 }
113 }
114
115 pub fn from_view_and_texture(
127 view: TextureView,
128 sampler: Sampler,
129 width: u32,
130 height: u32,
131 texture: Texture,
132 ) -> Self {
133 Self {
134 texture: Some(texture),
135 view,
136 sampler,
137 width,
138 height,
139 }
140 }
141
142 pub fn from_file(device: &Device, queue: &Queue, path: &Path) -> Result<Self, RenderError> {
154 let img = image::open(path)
156 .map_err(|e| RenderError::ImageLoad {
157 path: path.display().to_string(),
158 source: e,
159 })?
160 .to_rgba8();
161
162 let (width, height) = img.dimensions();
163
164 let texture = device.create_texture(&TextureDescriptor {
166 label: Some(&format!("Channel Texture: {}", path.display())),
167 size: Extent3d {
168 width,
169 height,
170 depth_or_array_layers: 1,
171 },
172 mip_level_count: 1,
173 sample_count: 1,
174 dimension: TextureDimension::D2,
175 format: TextureFormat::Rgba8UnormSrgb,
176 usage: TextureUsages::TEXTURE_BINDING | TextureUsages::COPY_DST,
177 view_formats: &[],
178 });
179
180 queue.write_texture(
182 TexelCopyTextureInfo {
183 texture: &texture,
184 mip_level: 0,
185 origin: Origin3d::ZERO,
186 aspect: TextureAspect::All,
187 },
188 &img,
189 TexelCopyBufferLayout {
190 offset: 0,
191 bytes_per_row: Some(4 * width),
192 rows_per_image: Some(height),
193 },
194 Extent3d {
195 width,
196 height,
197 depth_or_array_layers: 1,
198 },
199 );
200
201 let view = texture.create_view(&TextureViewDescriptor::default());
202
203 let sampler = device.create_sampler(&SamplerDescriptor {
205 label: Some(&format!("Channel Sampler: {}", path.display())),
206 address_mode_u: AddressMode::Repeat,
207 address_mode_v: AddressMode::Repeat,
208 address_mode_w: AddressMode::Repeat,
209 mag_filter: FilterMode::Linear,
210 min_filter: FilterMode::Linear,
211 mipmap_filter: FilterMode::Linear,
212 ..Default::default()
213 });
214
215 log::info!(
216 "Loaded channel texture: {} ({}x{})",
217 path.display(),
218 width,
219 height
220 );
221
222 Ok(Self {
223 texture: Some(texture),
224 view,
225 sampler,
226 width,
227 height,
228 })
229 }
230
231 pub fn resolution(&self) -> [f32; 4] {
235 [self.width as f32, self.height as f32, 1.0, 0.0]
236 }
237}
238
239pub fn load_channel_textures(
249 device: &Device,
250 queue: &Queue,
251 paths: &[Option<std::path::PathBuf>; 4],
252) -> [ChannelTexture; 4] {
253 let load_or_placeholder = |path: &Option<std::path::PathBuf>, index: usize| -> ChannelTexture {
254 match path {
255 Some(p) => match ChannelTexture::from_file(device, queue, p) {
256 Ok(tex) => tex,
257 Err(e) => {
258 log::error!(
259 "Failed to load iChannel{} texture '{}': {}",
260 index,
261 p.display(),
262 e
263 );
264 ChannelTexture::placeholder(device, queue)
265 }
266 },
267 None => ChannelTexture::placeholder(device, queue),
268 }
269 };
270
271 [
272 load_or_placeholder(&paths[0], 0),
273 load_or_placeholder(&paths[1], 1),
274 load_or_placeholder(&paths[2], 2),
275 load_or_placeholder(&paths[3], 3),
276 ]
277}
278
279impl CustomShaderRenderer {
282 pub(super) fn create_intermediate_texture(
287 device: &Device,
288 format: TextureFormat,
289 width: u32,
290 height: u32,
291 ) -> (Texture, TextureView) {
292 let texture = device.create_texture(&TextureDescriptor {
293 label: Some("Custom Shader Intermediate Texture"),
294 size: Extent3d {
295 width: width.max(1),
296 height: height.max(1),
297 depth_or_array_layers: 1,
298 },
299 mip_level_count: 1,
300 sample_count: 1,
301 dimension: TextureDimension::D2,
302 format,
303 usage: TextureUsages::RENDER_ATTACHMENT | TextureUsages::TEXTURE_BINDING,
304 view_formats: &[],
305 });
306
307 let view = texture.create_view(&TextureViewDescriptor::default());
308 (texture, view)
309 }
310
311 pub fn clear_intermediate_texture(&self, device: &Device, queue: &Queue) {
315 let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
316 label: Some("Clear Intermediate Texture Encoder"),
317 });
318
319 {
320 let _clear_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
321 label: Some("Clear Intermediate Texture Pass"),
322 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
323 view: &self.intermediate_texture_view,
324 resolve_target: None,
325 ops: wgpu::Operations {
326 load: wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT),
327 store: wgpu::StoreOp::Store,
328 },
329 depth_slice: None,
330 })],
331 depth_stencil_attachment: None,
332 timestamp_writes: None,
333 occlusion_query_set: None,
334 });
335 }
336
337 queue.submit(std::iter::once(encoder.finish()));
338 }
339
340 pub fn resize(&mut self, device: &Device, width: u32, height: u32) {
342 if width == self.texture_width && height == self.texture_height {
343 return;
344 }
345
346 self.texture_width = width;
347 self.texture_height = height;
348
349 let (texture, view) =
351 Self::create_intermediate_texture(device, self.surface_format, width, height);
352 self.intermediate_texture = texture;
353 self.intermediate_texture_view = view;
354
355 self.recreate_bind_group(device);
357 }
358
359 pub(super) fn recreate_bind_group(&mut self, device: &Device) {
372 let channel0_texture = if self.use_background_as_channel0 {
374 self.background_channel_texture
376 .as_ref()
377 .unwrap_or(&self.channel_textures[0])
378 } else if self.channel0_has_real_texture() {
379 &self.channel_textures[0]
381 } else {
382 &self.channel_textures[0]
384 };
385
386 let effective_channels = [
388 channel0_texture,
389 &self.channel_textures[1],
390 &self.channel_textures[2],
391 &self.channel_textures[3],
392 ];
393
394 self.bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
395 label: Some("Custom Shader Bind Group"),
396 layout: &self.bind_group_layout,
397 entries: &[
398 wgpu::BindGroupEntry {
399 binding: 0,
400 resource: self.uniform_buffer.as_entire_binding(),
401 },
402 wgpu::BindGroupEntry {
404 binding: 1,
405 resource: wgpu::BindingResource::TextureView(&effective_channels[0].view),
406 },
407 wgpu::BindGroupEntry {
408 binding: 2,
409 resource: wgpu::BindingResource::Sampler(&effective_channels[0].sampler),
410 },
411 wgpu::BindGroupEntry {
413 binding: 3,
414 resource: wgpu::BindingResource::TextureView(&effective_channels[1].view),
415 },
416 wgpu::BindGroupEntry {
417 binding: 4,
418 resource: wgpu::BindingResource::Sampler(&effective_channels[1].sampler),
419 },
420 wgpu::BindGroupEntry {
422 binding: 5,
423 resource: wgpu::BindingResource::TextureView(&effective_channels[2].view),
424 },
425 wgpu::BindGroupEntry {
426 binding: 6,
427 resource: wgpu::BindingResource::Sampler(&effective_channels[2].sampler),
428 },
429 wgpu::BindGroupEntry {
431 binding: 7,
432 resource: wgpu::BindingResource::TextureView(&effective_channels[3].view),
433 },
434 wgpu::BindGroupEntry {
435 binding: 8,
436 resource: wgpu::BindingResource::Sampler(&effective_channels[3].sampler),
437 },
438 wgpu::BindGroupEntry {
440 binding: 9,
441 resource: wgpu::BindingResource::TextureView(&self.intermediate_texture_view),
442 },
443 wgpu::BindGroupEntry {
444 binding: 10,
445 resource: wgpu::BindingResource::Sampler(&self.sampler),
446 },
447 wgpu::BindGroupEntry {
449 binding: 11,
450 resource: wgpu::BindingResource::TextureView(&self.cubemap.view),
451 },
452 wgpu::BindGroupEntry {
453 binding: 12,
454 resource: wgpu::BindingResource::Sampler(&self.cubemap.sampler),
455 },
456 ],
457 });
458 }
459
460 pub(super) fn channel0_has_real_texture(&self) -> bool {
462 let ch0 = &self.channel_textures[0];
463 ch0.width > 1 || ch0.height > 1
465 }
466
467 pub(super) fn effective_channel0_resolution(&self) -> [f32; 4] {
474 if self.use_background_as_channel0 {
475 self.background_channel_texture
476 .as_ref()
477 .map(|t| t.resolution())
478 .unwrap_or_else(|| self.channel_textures[0].resolution())
479 } else {
480 self.channel_textures[0].resolution()
481 }
482 }
483}