Skip to main content

wisp/render/
path_mask_texture.rs

1//! Generate an alpha-mask `RenderTexture` from a closed polygon
2//! (M-DYN.1 / AUT-43, freehand-path variant).
3//!
4//! Sister pipeline to [`MaskTexturePipeline`](super::mask_texture::MaskTexturePipeline);
5//! same uniform-buffered point-in-polygon as `PathClipPipeline` but
6//! emits coverage directly instead of multiplying it into a sampled
7//! foreground.
8
9use bytemuck::{Pod, Zeroable};
10use wgpu::util::DeviceExt;
11
12use crate::application::Application;
13use crate::texture::render_texture::RenderTexture;
14
15const MAX_PATH_POINTS: usize = 32;
16
17#[repr(C)]
18#[derive(Clone, Copy, Pod, Zeroable)]
19struct PathMaskTextureUniforms {
20    count: u32,
21    invert: u32,
22    _pad: [u32; 2],
23    points: [[f32; 4]; MAX_PATH_POINTS],
24}
25
26pub(crate) struct PathMaskTexturePipeline {
27    pipeline: wgpu::RenderPipeline,
28    bind_group_layout: wgpu::BindGroupLayout,
29}
30
31impl PathMaskTexturePipeline {
32    pub(crate) fn new(app: &Application, output_format: wgpu::TextureFormat) -> Self {
33        let device = app.device();
34
35        let bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
36            label: Some("wisp::path_mask_texture bg layout"),
37            entries: &[wgpu::BindGroupLayoutEntry {
38                binding: 0,
39                visibility: wgpu::ShaderStages::FRAGMENT,
40                ty: wgpu::BindingType::Buffer {
41                    ty: wgpu::BufferBindingType::Uniform,
42                    has_dynamic_offset: false,
43                    min_binding_size: None,
44                },
45                count: None,
46            }],
47        });
48
49        let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
50            label: Some("wisp::path_mask_texture pipeline layout"),
51            bind_group_layouts: &[&bind_group_layout],
52            push_constant_ranges: &[],
53        });
54
55        let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
56            label: Some("wisp::path_mask_texture shader"),
57            source: wgpu::ShaderSource::Wgsl(
58                include_str!("../../shaders/path_mask_texture.wgsl").into(),
59            ),
60        });
61
62        let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
63            label: Some("wisp::path_mask_texture pipeline"),
64            layout: Some(&pipeline_layout),
65            vertex: wgpu::VertexState {
66                module: &shader,
67                entry_point: Some("main_vs"),
68                buffers: &[],
69                compilation_options: wgpu::PipelineCompilationOptions::default(),
70            },
71            fragment: Some(wgpu::FragmentState {
72                module: &shader,
73                entry_point: Some("main_fs"),
74                targets: &[Some(wgpu::ColorTargetState {
75                    format: output_format,
76                    blend: Some(wgpu::BlendState::REPLACE),
77                    write_mask: wgpu::ColorWrites::ALL,
78                })],
79                compilation_options: wgpu::PipelineCompilationOptions::default(),
80            }),
81            primitive: wgpu::PrimitiveState::default(),
82            depth_stencil: None,
83            multisample: wgpu::MultisampleState::default(),
84            multiview: None,
85            cache: None,
86        });
87
88        Self {
89            pipeline,
90            bind_group_layout,
91        }
92    }
93
94    pub(crate) fn generate(
95        &self,
96        app: &Application,
97        points: &[glam::Vec2],
98        w: u32,
99        h: u32,
100        output_format: wgpu::TextureFormat,
101    ) -> RenderTexture {
102        let rt = RenderTexture::with_format(app, w, h, output_format);
103        self.render_into(app, points, false, &rt);
104        rt
105    }
106
107    pub(crate) fn render_into(
108        &self,
109        app: &Application,
110        points: &[glam::Vec2],
111        invert: bool,
112        output: &RenderTexture,
113    ) {
114        let mut padded = [[0.0_f32; 4]; MAX_PATH_POINTS];
115        let count = points.len().min(MAX_PATH_POINTS);
116        for (slot, p) in padded.iter_mut().zip(points.iter()).take(count) {
117            *slot = [p.x, p.y, 0.0, 0.0];
118        }
119        let count_u32 = u32::try_from(count).unwrap_or(0);
120
121        let uniforms = PathMaskTextureUniforms {
122            count: count_u32,
123            invert: u32::from(invert),
124            _pad: [0, 0],
125            points: padded,
126        };
127        let buffer = app
128            .device()
129            .create_buffer_init(&wgpu::util::BufferInitDescriptor {
130                label: Some("wisp::path_mask_texture uniforms"),
131                contents: bytemuck::bytes_of(&uniforms),
132                usage: wgpu::BufferUsages::UNIFORM,
133            });
134
135        let bg = app.device().create_bind_group(&wgpu::BindGroupDescriptor {
136            label: Some("wisp::path_mask_texture bg"),
137            layout: &self.bind_group_layout,
138            entries: &[wgpu::BindGroupEntry {
139                binding: 0,
140                resource: buffer.as_entire_binding(),
141            }],
142        });
143
144        let mut encoder = app
145            .device()
146            .create_command_encoder(&wgpu::CommandEncoderDescriptor {
147                label: Some("wisp::path_mask_texture encoder"),
148            });
149        {
150            let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
151                label: Some("wisp::path_mask_texture pass"),
152                color_attachments: &[Some(wgpu::RenderPassColorAttachment {
153                    view: output.view(),
154                    resolve_target: None,
155                    ops: wgpu::Operations {
156                        load: wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT),
157                        store: wgpu::StoreOp::Store,
158                    },
159                })],
160                depth_stencil_attachment: None,
161                timestamp_writes: None,
162                occlusion_query_set: None,
163            });
164            pass.set_pipeline(&self.pipeline);
165            pass.set_bind_group(0, &bg, &[]);
166            pass.draw(0..3, 0..1);
167        }
168        app.queue().submit(std::iter::once(encoder.finish()));
169    }
170}