Skip to main content

wisp/render/
mask_compose.rs

1//! Compose a foreground `RenderTexture` against a separately-
2//! generated alpha mask texture (M-VEC.4..6 / AUT-56..58).
3//!
4//! The primitive: `output.rgba = (foreground.rgb, foreground.a *
5//! mask.a)`. Replaces the inline-SDF clip pipeline for vector-driven
6//! masks; the mask itself comes from
7//! [`MaskTexturePipeline`](super::mask_texture::MaskTexturePipeline)
8//! or
9//! [`PathMaskTexturePipeline`](super::path_mask_texture::PathMaskTexturePipeline)
10//! (or the M-DYN.2 cache).
11
12use crate::application::Application;
13use crate::texture::render_texture::RenderTexture;
14
15pub(crate) struct MaskComposePipeline {
16    pipeline: wgpu::RenderPipeline,
17    bind_group_layout: wgpu::BindGroupLayout,
18    sampler: wgpu::Sampler,
19}
20
21impl MaskComposePipeline {
22    pub(crate) fn new(app: &Application, output_format: wgpu::TextureFormat) -> Self {
23        let device = app.device();
24
25        let bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
26            label: Some("wisp::mask_compose bg layout"),
27            entries: &[
28                wgpu::BindGroupLayoutEntry {
29                    binding: 0,
30                    visibility: wgpu::ShaderStages::FRAGMENT,
31                    ty: wgpu::BindingType::Texture {
32                        sample_type: wgpu::TextureSampleType::Float { filterable: true },
33                        view_dimension: wgpu::TextureViewDimension::D2,
34                        multisampled: false,
35                    },
36                    count: None,
37                },
38                wgpu::BindGroupLayoutEntry {
39                    binding: 1,
40                    visibility: wgpu::ShaderStages::FRAGMENT,
41                    ty: wgpu::BindingType::Texture {
42                        sample_type: wgpu::TextureSampleType::Float { filterable: true },
43                        view_dimension: wgpu::TextureViewDimension::D2,
44                        multisampled: false,
45                    },
46                    count: None,
47                },
48                wgpu::BindGroupLayoutEntry {
49                    binding: 2,
50                    visibility: wgpu::ShaderStages::FRAGMENT,
51                    ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
52                    count: None,
53                },
54            ],
55        });
56
57        let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
58            label: Some("wisp::mask_compose pipeline layout"),
59            bind_group_layouts: &[&bind_group_layout],
60            push_constant_ranges: &[],
61        });
62
63        let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
64            label: Some("wisp::mask_compose shader"),
65            source: wgpu::ShaderSource::Wgsl(
66                include_str!("../../shaders/mask_compose.wgsl").into(),
67            ),
68        });
69
70        let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
71            label: Some("wisp::mask_compose pipeline"),
72            layout: Some(&pipeline_layout),
73            vertex: wgpu::VertexState {
74                module: &shader,
75                entry_point: Some("main_vs"),
76                buffers: &[],
77                compilation_options: wgpu::PipelineCompilationOptions::default(),
78            },
79            fragment: Some(wgpu::FragmentState {
80                module: &shader,
81                entry_point: Some("main_fs"),
82                targets: &[Some(wgpu::ColorTargetState {
83                    format: output_format,
84                    blend: Some(wgpu::BlendState::REPLACE),
85                    write_mask: wgpu::ColorWrites::ALL,
86                })],
87                compilation_options: wgpu::PipelineCompilationOptions::default(),
88            }),
89            primitive: wgpu::PrimitiveState::default(),
90            depth_stencil: None,
91            multisample: wgpu::MultisampleState::default(),
92            multiview: None,
93            cache: None,
94        });
95
96        let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
97            label: Some("wisp::mask_compose sampler"),
98            address_mode_u: wgpu::AddressMode::ClampToEdge,
99            address_mode_v: wgpu::AddressMode::ClampToEdge,
100            address_mode_w: wgpu::AddressMode::ClampToEdge,
101            mag_filter: wgpu::FilterMode::Linear,
102            min_filter: wgpu::FilterMode::Linear,
103            mipmap_filter: wgpu::FilterMode::Nearest,
104            ..Default::default()
105        });
106
107        Self {
108            pipeline,
109            bind_group_layout,
110            sampler,
111        }
112    }
113
114    pub(crate) fn apply(
115        &self,
116        app: &Application,
117        foreground: &RenderTexture,
118        mask: &RenderTexture,
119        output: &RenderTexture,
120    ) {
121        let bg = app.device().create_bind_group(&wgpu::BindGroupDescriptor {
122            label: Some("wisp::mask_compose bg"),
123            layout: &self.bind_group_layout,
124            entries: &[
125                wgpu::BindGroupEntry {
126                    binding: 0,
127                    resource: wgpu::BindingResource::TextureView(foreground.view()),
128                },
129                wgpu::BindGroupEntry {
130                    binding: 1,
131                    resource: wgpu::BindingResource::TextureView(mask.view()),
132                },
133                wgpu::BindGroupEntry {
134                    binding: 2,
135                    resource: wgpu::BindingResource::Sampler(&self.sampler),
136                },
137            ],
138        });
139
140        let mut encoder = app
141            .device()
142            .create_command_encoder(&wgpu::CommandEncoderDescriptor {
143                label: Some("wisp::mask_compose encoder"),
144            });
145        {
146            let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
147                label: Some("wisp::mask_compose pass"),
148                color_attachments: &[Some(wgpu::RenderPassColorAttachment {
149                    view: output.view(),
150                    resolve_target: None,
151                    ops: wgpu::Operations {
152                        load: wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT),
153                        store: wgpu::StoreOp::Store,
154                    },
155                })],
156                depth_stencil_attachment: None,
157                timestamp_writes: None,
158                occlusion_query_set: None,
159            });
160            pass.set_pipeline(&self.pipeline);
161            pass.set_bind_group(0, &bg, &[]);
162            pass.draw(0..3, 0..1);
163        }
164        app.queue().submit(std::iter::once(encoder.finish()));
165    }
166}