1use onedrop_codegen::{ShaderUniforms, USER_TEXTURE_FIRST_BINDING, USER_TEXTURE_SLOTS};
19use wgpu::util::DeviceExt;
20
21use crate::error::Result;
22
23pub const PREV_MAIN_BINDING: u32 = USER_TEXTURE_FIRST_BINDING + USER_TEXTURE_SLOTS as u32;
27
28pub struct CompAuxViews<'a> {
36 pub blur1: &'a wgpu::TextureView,
37 pub blur2: &'a wgpu::TextureView,
38 pub blur3: &'a wgpu::TextureView,
39 pub noise_lq: &'a wgpu::TextureView,
40 pub noise_mq: &'a wgpu::TextureView,
41 pub noise_hq: &'a wgpu::TextureView,
42 pub noisevol_lq: &'a wgpu::TextureView,
43 pub noisevol_hq: &'a wgpu::TextureView,
44 pub prev_main: &'a wgpu::TextureView,
50}
51
52pub struct CompPipeline {
53 default_pipeline: wgpu::RenderPipeline,
57 user_pipeline: Option<wgpu::RenderPipeline>,
61 pipeline_layout: wgpu::PipelineLayout,
62 target_format: wgpu::TextureFormat,
63 bind_group_layout: wgpu::BindGroupLayout,
64 bind_group: wgpu::BindGroup,
65 sampler: wgpu::Sampler,
69 sampler_fw: wgpu::Sampler,
76 sampler_fc: wgpu::Sampler,
77 sampler_pw: wgpu::Sampler,
78 sampler_pc: wgpu::Sampler,
79 uniforms_buffer: wgpu::Buffer,
80 fallback_user_view: wgpu::TextureView,
84 user_texture_views: [wgpu::TextureView; USER_TEXTURE_SLOTS],
89 cached_input_view: wgpu::TextureView,
93 cached_aux: OwnedCompAuxViews,
94}
95
96struct OwnedCompAuxViews {
100 blur1: wgpu::TextureView,
101 blur2: wgpu::TextureView,
102 blur3: wgpu::TextureView,
103 noise_lq: wgpu::TextureView,
104 noise_mq: wgpu::TextureView,
105 noise_hq: wgpu::TextureView,
106 noisevol_lq: wgpu::TextureView,
107 noisevol_hq: wgpu::TextureView,
108 prev_main: wgpu::TextureView,
109}
110
111impl OwnedCompAuxViews {
112 fn from_borrowed(aux: &CompAuxViews) -> Self {
113 Self {
114 blur1: aux.blur1.clone(),
115 blur2: aux.blur2.clone(),
116 blur3: aux.blur3.clone(),
117 noise_lq: aux.noise_lq.clone(),
118 noise_mq: aux.noise_mq.clone(),
119 noise_hq: aux.noise_hq.clone(),
120 noisevol_lq: aux.noisevol_lq.clone(),
121 noisevol_hq: aux.noisevol_hq.clone(),
122 prev_main: aux.prev_main.clone(),
123 }
124 }
125
126 fn as_borrowed(&self) -> CompAuxViews<'_> {
127 CompAuxViews {
128 blur1: &self.blur1,
129 blur2: &self.blur2,
130 blur3: &self.blur3,
131 noise_lq: &self.noise_lq,
132 noise_mq: &self.noise_mq,
133 noise_hq: &self.noise_hq,
134 noisevol_lq: &self.noisevol_lq,
135 noisevol_hq: &self.noisevol_hq,
136 prev_main: &self.prev_main,
137 }
138 }
139}
140
141impl CompPipeline {
142 pub fn new(
143 device: &wgpu::Device,
144 queue: &wgpu::Queue,
145 target_format: wgpu::TextureFormat,
146 input_texture_view: &wgpu::TextureView,
147 aux: &CompAuxViews,
148 ) -> Result<Self> {
149 let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
150 label: Some("Comp Sampler"),
151 address_mode_u: wgpu::AddressMode::ClampToEdge,
152 address_mode_v: wgpu::AddressMode::ClampToEdge,
153 address_mode_w: wgpu::AddressMode::ClampToEdge,
154 mag_filter: wgpu::FilterMode::Linear,
155 min_filter: wgpu::FilterMode::Linear,
156 mipmap_filter: wgpu::MipmapFilterMode::Linear,
157 ..Default::default()
158 });
159 let sampler_fw = make_md2_sampler(device, "Comp Sampler FW", true, true);
160 let sampler_fc = make_md2_sampler(device, "Comp Sampler FC", true, false);
161 let sampler_pw = make_md2_sampler(device, "Comp Sampler PW", false, true);
162 let sampler_pc = make_md2_sampler(device, "Comp Sampler PC", false, false);
163
164 let fallback_user_view = make_fallback_user_texture_view(device, queue);
165 let user_texture_views: [wgpu::TextureView; USER_TEXTURE_SLOTS] =
166 std::array::from_fn(|_| fallback_user_view.clone());
167
168 let initial = ShaderUniforms::default();
169 let uniforms_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
170 label: Some("Comp ShaderUniforms"),
171 contents: bytemuck::bytes_of(&initial),
172 usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
173 });
174
175 let bind_group_layout =
176 build_user_shader_bind_group_layout(device, "Comp Bind Group Layout");
177
178 let user_view_refs: [&wgpu::TextureView; USER_TEXTURE_SLOTS] =
179 std::array::from_fn(|i| &user_texture_views[i]);
180 let bind_group = Self::create_bind_group(
181 device,
182 &bind_group_layout,
183 &uniforms_buffer,
184 input_texture_view,
185 &sampler,
186 aux,
187 &sampler_fw,
188 &sampler_fc,
189 &sampler_pw,
190 &sampler_pc,
191 &user_view_refs,
192 );
193
194 let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
195 label: Some("Comp Pipeline Layout"),
196 bind_group_layouts: &[Some(&bind_group_layout)],
197 immediate_size: 0,
198 });
199
200 let default_pipeline = build_pipeline_from_wgsl(
201 device,
202 &pipeline_layout,
203 target_format,
204 include_str!("../shaders/comp.wgsl"),
205 "Comp Default",
206 )
207 .expect("default comp shader must compile — bug if not");
208
209 let cached_input_view = input_texture_view.clone();
210 let cached_aux = OwnedCompAuxViews::from_borrowed(aux);
211
212 Ok(Self {
213 default_pipeline,
214 user_pipeline: None,
215 pipeline_layout,
216 target_format,
217 bind_group_layout,
218 bind_group,
219 sampler,
220 sampler_fw,
221 sampler_fc,
222 sampler_pw,
223 sampler_pc,
224 uniforms_buffer,
225 fallback_user_view,
226 user_texture_views,
227 cached_input_view,
228 cached_aux,
229 })
230 }
231
232 pub fn set_user_shader(&mut self, device: &wgpu::Device, wrapped_wgsl: &str) -> Result<()> {
245 let pipeline = build_pipeline_from_wgsl(
246 device,
247 &self.pipeline_layout,
248 self.target_format,
249 wrapped_wgsl,
250 "Comp User",
251 )?;
252 self.user_pipeline = Some(pipeline);
253 Ok(())
254 }
255
256 pub fn reset_to_default(&mut self, device: &wgpu::Device) {
262 self.user_pipeline = None;
263 for slot in self.user_texture_views.iter_mut() {
264 *slot = self.fallback_user_view.clone();
265 }
266 self.rebuild_bind_group(device);
267 }
268
269 pub fn has_user_shader(&self) -> bool {
273 self.user_pipeline.is_some()
274 }
275
276 pub fn comp_aux_views(&self) -> CompAuxViews<'_> {
280 self.cached_aux.as_borrowed()
281 }
282
283 #[allow(clippy::too_many_arguments)]
284 fn create_bind_group(
285 device: &wgpu::Device,
286 layout: &wgpu::BindGroupLayout,
287 uniforms_buffer: &wgpu::Buffer,
288 input_texture_view: &wgpu::TextureView,
289 sampler: &wgpu::Sampler,
290 aux: &CompAuxViews,
291 sampler_fw: &wgpu::Sampler,
292 sampler_fc: &wgpu::Sampler,
293 sampler_pw: &wgpu::Sampler,
294 sampler_pc: &wgpu::Sampler,
295 user_textures: &[&wgpu::TextureView; USER_TEXTURE_SLOTS],
296 ) -> wgpu::BindGroup {
297 let mut entries: Vec<wgpu::BindGroupEntry> = vec![
298 wgpu::BindGroupEntry {
299 binding: 0,
300 resource: uniforms_buffer.as_entire_binding(),
301 },
302 wgpu::BindGroupEntry {
303 binding: 1,
304 resource: wgpu::BindingResource::TextureView(input_texture_view),
305 },
306 wgpu::BindGroupEntry {
307 binding: 2,
308 resource: wgpu::BindingResource::Sampler(sampler),
309 },
310 wgpu::BindGroupEntry {
311 binding: 3,
312 resource: wgpu::BindingResource::TextureView(aux.blur1),
313 },
314 wgpu::BindGroupEntry {
315 binding: 4,
316 resource: wgpu::BindingResource::TextureView(aux.blur2),
317 },
318 wgpu::BindGroupEntry {
319 binding: 5,
320 resource: wgpu::BindingResource::TextureView(aux.blur3),
321 },
322 wgpu::BindGroupEntry {
323 binding: 6,
324 resource: wgpu::BindingResource::TextureView(aux.noise_lq),
325 },
326 wgpu::BindGroupEntry {
327 binding: 7,
328 resource: wgpu::BindingResource::TextureView(aux.noise_mq),
329 },
330 wgpu::BindGroupEntry {
331 binding: 8,
332 resource: wgpu::BindingResource::TextureView(aux.noise_hq),
333 },
334 wgpu::BindGroupEntry {
335 binding: 9,
336 resource: wgpu::BindingResource::TextureView(aux.noisevol_lq),
337 },
338 wgpu::BindGroupEntry {
339 binding: 10,
340 resource: wgpu::BindingResource::TextureView(aux.noisevol_hq),
341 },
342 wgpu::BindGroupEntry {
343 binding: 11,
344 resource: wgpu::BindingResource::Sampler(sampler_fw),
345 },
346 wgpu::BindGroupEntry {
347 binding: 12,
348 resource: wgpu::BindingResource::Sampler(sampler_fc),
349 },
350 wgpu::BindGroupEntry {
351 binding: 13,
352 resource: wgpu::BindingResource::Sampler(sampler_pw),
353 },
354 wgpu::BindGroupEntry {
355 binding: 14,
356 resource: wgpu::BindingResource::Sampler(sampler_pc),
357 },
358 ];
359 for (i, view) in user_textures.iter().enumerate() {
360 entries.push(wgpu::BindGroupEntry {
361 binding: USER_TEXTURE_FIRST_BINDING + i as u32,
362 resource: wgpu::BindingResource::TextureView(view),
363 });
364 }
365 entries.push(wgpu::BindGroupEntry {
366 binding: PREV_MAIN_BINDING,
367 resource: wgpu::BindingResource::TextureView(aux.prev_main),
368 });
369 device.create_bind_group(&wgpu::BindGroupDescriptor {
370 label: Some("Comp Bind Group"),
371 layout,
372 entries: &entries,
373 })
374 }
375
376 pub fn rebind_input_texture(
381 &mut self,
382 device: &wgpu::Device,
383 input_texture_view: &wgpu::TextureView,
384 aux: &CompAuxViews,
385 ) {
386 self.cached_input_view = input_texture_view.clone();
387 self.cached_aux = OwnedCompAuxViews::from_borrowed(aux);
388 self.rebuild_bind_group(device);
389 }
390
391 fn rebuild_bind_group(&mut self, device: &wgpu::Device) {
395 let user_view_refs: [&wgpu::TextureView; USER_TEXTURE_SLOTS] =
396 std::array::from_fn(|i| &self.user_texture_views[i]);
397 let aux = self.cached_aux.as_borrowed();
398 self.bind_group = Self::create_bind_group(
399 device,
400 &self.bind_group_layout,
401 &self.uniforms_buffer,
402 &self.cached_input_view,
403 &self.sampler,
404 &aux,
405 &self.sampler_fw,
406 &self.sampler_fc,
407 &self.sampler_pw,
408 &self.sampler_pc,
409 &user_view_refs,
410 );
411 }
412
413 fn install_user_textures(&mut self, views: [Option<wgpu::TextureView>; USER_TEXTURE_SLOTS]) {
418 for (slot, opt) in views.into_iter().enumerate() {
419 self.user_texture_views[slot] = opt.unwrap_or_else(|| self.fallback_user_view.clone());
420 }
421 }
422
423 pub fn set_user_shader_with_plan(
433 &mut self,
434 device: &wgpu::Device,
435 wrapped_wgsl: &str,
436 views: [Option<wgpu::TextureView>; USER_TEXTURE_SLOTS],
437 ) -> Result<()> {
438 let pipeline = build_pipeline_from_wgsl(
439 device,
440 &self.pipeline_layout,
441 self.target_format,
442 wrapped_wgsl,
443 "Comp User",
444 )?;
445 self.install_user_textures(views);
446 self.rebuild_bind_group(device);
447 self.user_pipeline = Some(pipeline);
448 Ok(())
449 }
450
451 pub fn update_uniforms(&self, queue: &wgpu::Queue, uniforms: &ShaderUniforms) {
453 queue.write_buffer(&self.uniforms_buffer, 0, bytemuck::bytes_of(uniforms));
454 }
455
456 pub fn render(&self, encoder: &mut wgpu::CommandEncoder, output_view: &wgpu::TextureView) {
457 let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
458 label: Some("Comp Render Pass"),
459 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
460 view: output_view,
461 depth_slice: None,
462 resolve_target: None,
463 ops: wgpu::Operations {
464 load: wgpu::LoadOp::Clear(wgpu::Color::BLACK),
465 store: wgpu::StoreOp::Store,
466 },
467 })],
468 depth_stencil_attachment: None,
469 timestamp_writes: None,
470 occlusion_query_set: None,
471 multiview_mask: None,
472 });
473
474 let pipeline = self
475 .user_pipeline
476 .as_ref()
477 .unwrap_or(&self.default_pipeline);
478 pass.set_pipeline(pipeline);
479 pass.set_bind_group(0, &self.bind_group, &[]);
480 pass.draw(0..3, 0..1);
481 }
482}
483
484fn build_pipeline_from_wgsl(
492 device: &wgpu::Device,
493 pipeline_layout: &wgpu::PipelineLayout,
494 target_format: wgpu::TextureFormat,
495 source: &str,
496 label: &str,
497) -> Result<wgpu::RenderPipeline> {
498 let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
499 label: Some(&format!("{label} Shader")),
500 source: wgpu::ShaderSource::Wgsl(source.into()),
501 });
502
503 Ok(
504 device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
505 label: Some(&format!("{label} Pipeline")),
506 layout: Some(pipeline_layout),
507 vertex: wgpu::VertexState {
508 module: &shader,
509 entry_point: Some("vs_main"),
510 buffers: &[],
511 compilation_options: Default::default(),
512 },
513 fragment: Some(wgpu::FragmentState {
514 module: &shader,
515 entry_point: Some("fs_main"),
516 targets: &[Some(wgpu::ColorTargetState {
517 format: target_format,
518 blend: Some(wgpu::BlendState::REPLACE),
519 write_mask: wgpu::ColorWrites::ALL,
520 })],
521 compilation_options: Default::default(),
522 }),
523 primitive: wgpu::PrimitiveState {
524 topology: wgpu::PrimitiveTopology::TriangleList,
525 ..Default::default()
526 },
527 depth_stencil: None,
528 multisample: wgpu::MultisampleState::default(),
529 multiview_mask: None,
530 cache: None,
531 }),
532 )
533}
534
535pub(crate) fn make_fallback_user_texture_view(
540 device: &wgpu::Device,
541 queue: &wgpu::Queue,
542) -> wgpu::TextureView {
543 let texture = device.create_texture(&wgpu::TextureDescriptor {
544 label: Some("Comp User Texture Fallback"),
545 size: wgpu::Extent3d {
546 width: 1,
547 height: 1,
548 depth_or_array_layers: 1,
549 },
550 mip_level_count: 1,
551 sample_count: 1,
552 dimension: wgpu::TextureDimension::D2,
553 format: wgpu::TextureFormat::Rgba8Unorm,
554 usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
555 view_formats: &[],
556 });
557 queue.write_texture(
558 wgpu::TexelCopyTextureInfo {
559 texture: &texture,
560 mip_level: 0,
561 origin: wgpu::Origin3d::ZERO,
562 aspect: wgpu::TextureAspect::All,
563 },
564 &[255u8, 255, 255, 255],
565 wgpu::TexelCopyBufferLayout {
566 offset: 0,
567 bytes_per_row: Some(4),
568 rows_per_image: Some(1),
569 },
570 wgpu::Extent3d {
571 width: 1,
572 height: 1,
573 depth_or_array_layers: 1,
574 },
575 );
576 texture.create_view(&wgpu::TextureViewDescriptor::default())
577}
578
579pub(crate) fn build_user_shader_bind_group_layout(
595 device: &wgpu::Device,
596 label: &str,
597) -> wgpu::BindGroupLayout {
598 let mut entries: Vec<wgpu::BindGroupLayoutEntry> = vec![
599 wgpu::BindGroupLayoutEntry {
601 binding: 0,
602 visibility: wgpu::ShaderStages::FRAGMENT,
603 ty: wgpu::BindingType::Buffer {
604 ty: wgpu::BufferBindingType::Uniform,
605 has_dynamic_offset: false,
606 min_binding_size: None,
607 },
608 count: None,
609 },
610 wgpu::BindGroupLayoutEntry {
612 binding: 1,
613 visibility: wgpu::ShaderStages::FRAGMENT,
614 ty: wgpu::BindingType::Texture {
615 sample_type: wgpu::TextureSampleType::Float { filterable: true },
616 view_dimension: wgpu::TextureViewDimension::D2,
617 multisampled: false,
618 },
619 count: None,
620 },
621 wgpu::BindGroupLayoutEntry {
623 binding: 2,
624 visibility: wgpu::ShaderStages::FRAGMENT,
625 ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
626 count: None,
627 },
628 ];
629 for binding in 3..=5 {
631 entries.push(wgpu::BindGroupLayoutEntry {
632 binding,
633 visibility: wgpu::ShaderStages::FRAGMENT,
634 ty: wgpu::BindingType::Texture {
635 sample_type: wgpu::TextureSampleType::Float { filterable: true },
636 view_dimension: wgpu::TextureViewDimension::D2,
637 multisampled: false,
638 },
639 count: None,
640 });
641 }
642 for binding in 6..=8 {
644 entries.push(wgpu::BindGroupLayoutEntry {
645 binding,
646 visibility: wgpu::ShaderStages::FRAGMENT,
647 ty: wgpu::BindingType::Texture {
648 sample_type: wgpu::TextureSampleType::Float { filterable: true },
649 view_dimension: wgpu::TextureViewDimension::D2,
650 multisampled: false,
651 },
652 count: None,
653 });
654 }
655 for binding in 9..=10 {
657 entries.push(wgpu::BindGroupLayoutEntry {
658 binding,
659 visibility: wgpu::ShaderStages::FRAGMENT,
660 ty: wgpu::BindingType::Texture {
661 sample_type: wgpu::TextureSampleType::Float { filterable: true },
662 view_dimension: wgpu::TextureViewDimension::D3,
663 multisampled: false,
664 },
665 count: None,
666 });
667 }
668 for binding in 11..=14 {
670 entries.push(wgpu::BindGroupLayoutEntry {
671 binding,
672 visibility: wgpu::ShaderStages::FRAGMENT,
673 ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
674 count: None,
675 });
676 }
677 for n in 0..USER_TEXTURE_SLOTS as u32 {
679 entries.push(wgpu::BindGroupLayoutEntry {
680 binding: USER_TEXTURE_FIRST_BINDING + n,
681 visibility: wgpu::ShaderStages::FRAGMENT,
682 ty: wgpu::BindingType::Texture {
683 sample_type: wgpu::TextureSampleType::Float { filterable: true },
684 view_dimension: wgpu::TextureViewDimension::D2,
685 multisampled: false,
686 },
687 count: None,
688 });
689 }
690 entries.push(wgpu::BindGroupLayoutEntry {
692 binding: PREV_MAIN_BINDING,
693 visibility: wgpu::ShaderStages::FRAGMENT,
694 ty: wgpu::BindingType::Texture {
695 sample_type: wgpu::TextureSampleType::Float { filterable: true },
696 view_dimension: wgpu::TextureViewDimension::D2,
697 multisampled: false,
698 },
699 count: None,
700 });
701 device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
702 label: Some(label),
703 entries: &entries,
704 })
705}
706
707pub(crate) fn make_md2_sampler(
708 device: &wgpu::Device,
709 label: &str,
710 filtered: bool,
711 wrap: bool,
712) -> wgpu::Sampler {
713 let filter = if filtered {
714 wgpu::FilterMode::Linear
715 } else {
716 wgpu::FilterMode::Nearest
717 };
718 let address = if wrap {
719 wgpu::AddressMode::Repeat
720 } else {
721 wgpu::AddressMode::ClampToEdge
722 };
723 device.create_sampler(&wgpu::SamplerDescriptor {
724 label: Some(label),
725 address_mode_u: address,
726 address_mode_v: address,
727 address_mode_w: address,
728 mag_filter: filter,
729 min_filter: filter,
730 mipmap_filter: wgpu::MipmapFilterMode::Nearest,
731 ..Default::default()
732 })
733}