reui/
picture.rs

1use crate::{
2    internals::{Batch, Draw, GpuBatch, Tessellator, Vertex},
3    FillRule, Images, IntoPaint, LineJoin, Path, Pipeline, Rect, Stroke, Transform,
4};
5
6#[derive(Clone, Copy, Debug)]
7pub struct DrawIndexed {
8    pub start: u32,
9    pub end: u32,
10    pub base_vertex: i32,
11    pub instance: u32,
12}
13
14impl DrawIndexed {
15    #[inline]
16    fn new(start: u32, end: u32, base_vertex: i32, instance: u32) -> Self {
17        Self {
18            start,
19            end,
20            base_vertex,
21            instance,
22        }
23    }
24
25    #[inline]
26    fn call<'a>(
27        &self,
28        rpass: &mut wgpu::RenderBundleEncoder<'a>,
29        pipeline: &'a wgpu::RenderPipeline,
30    ) {
31        let instances = self.instance..self.instance + 1;
32        rpass.set_pipeline(pipeline);
33        rpass.draw_indexed(self.start..self.end, self.base_vertex, instances);
34    }
35}
36
37#[derive(Clone, Copy, Debug)]
38pub enum DrawCall<Key> {
39    Convex(DrawIndexed),
40    ConvexSimple(DrawIndexed),
41    Stencil(DrawIndexed),
42    FringesNonZero(DrawIndexed),
43    FringesEvenOdd(DrawIndexed),
44    QuadNonZero(DrawIndexed),
45    QuadEvenOdd(DrawIndexed),
46    ImagePremultiplied(DrawIndexed),
47    ImageUnmultiplied(DrawIndexed),
48    ImageFont(DrawIndexed),
49
50    BindImage(Key),
51    Stroke {
52        start: u32,
53        end: u32,
54        base_vertex: i32,
55        instance: u32,
56    },
57}
58
59#[cfg_attr(feature = "bevy", derive(bevy::prelude::Component))]
60pub struct Picture(pub(crate) wgpu::RenderBundle);
61
62impl<'a> std::iter::IntoIterator for &'a Picture {
63    type Item = &'a wgpu::RenderBundle;
64    type IntoIter = std::iter::Once<Self::Item>;
65
66    #[inline]
67    fn into_iter(self) -> Self::IntoIter {
68        std::iter::once(&self.0)
69    }
70}
71
72impl Picture {
73    pub fn new<Key: Eq + std::hash::Hash>(
74        device: &wgpu::Device,
75        viewport: &wgpu::BindGroup,
76        offset: u32,
77        pipeline: &Pipeline,
78        batch: &GpuBatch,
79        images: &Images<Key>,
80        calls: &[DrawCall<Key>],
81    ) -> Self {
82        let mut rpass = device.create_render_bundle_encoder(&wgpu::RenderBundleEncoderDescriptor {
83            label: Some("reui::Picture"),
84            color_formats: &[Some(wgpu::TextureFormat::Rgba8UnormSrgb)],
85            depth_stencil: Some(wgpu::RenderBundleDepthStencil {
86                format: wgpu::TextureFormat::Depth24PlusStencil8,
87                depth_read_only: true,
88                stencil_read_only: false,
89            }),
90            sample_count: 1,
91            multiview: None,
92        });
93
94        rpass.set_bind_group(0, viewport, &[offset]);
95
96        rpass.set_index_buffer(batch.indices.slice(..), wgpu::IndexFormat::Uint32);
97        rpass.set_vertex_buffer(0, batch.vertices.slice(..));
98        rpass.set_vertex_buffer(1, batch.instances.slice(..));
99
100        for call in calls {
101            match call {
102                DrawCall::BindImage(image) => rpass.set_bind_group(1, &images[image].bind, &[]),
103
104                DrawCall::Convex(draw) => draw.call(&mut rpass, &pipeline.convex),
105                DrawCall::ConvexSimple(draw) => draw.call(&mut rpass, &pipeline.convex_simple),
106                DrawCall::Stencil(draw) => draw.call(&mut rpass, &pipeline.fill_stencil),
107                DrawCall::QuadNonZero(draw) => draw.call(&mut rpass, &pipeline.fill_quad_non_zero),
108                DrawCall::QuadEvenOdd(draw) => draw.call(&mut rpass, &pipeline.fill_quad_even_odd),
109                DrawCall::FringesNonZero(draw) => draw.call(&mut rpass, &pipeline.fringes_non_zero),
110                DrawCall::FringesEvenOdd(draw) => draw.call(&mut rpass, &pipeline.fringes_even_odd),
111                DrawCall::ImagePremultiplied(draw) => {
112                    draw.call(&mut rpass, &pipeline.premultiplied)
113                }
114                DrawCall::ImageUnmultiplied(draw) => draw.call(&mut rpass, &pipeline.unmultiplied),
115                DrawCall::ImageFont(draw) => draw.call(&mut rpass, &pipeline.font),
116
117                &DrawCall::Stroke {
118                    start,
119                    end,
120                    base_vertex,
121                    instance,
122                } => {
123                    rpass.set_pipeline(&pipeline.stroke_base);
124                    rpass.draw_indexed(start..end, base_vertex, instance..instance + 1);
125
126                    rpass.set_pipeline(&pipeline.fringes_non_zero);
127                    rpass.draw_indexed(start..end, base_vertex, instance + 1..instance + 2);
128
129                    rpass.set_pipeline(&pipeline.stroke_stencil);
130                    rpass.draw_indexed(start..end, base_vertex, 0..1);
131                }
132            }
133        }
134
135        Self(rpass.finish(&wgpu::RenderBundleDescriptor {
136            label: Some("reui::Picture"),
137        }))
138    }
139}
140
141#[derive(Default)]
142#[cfg_attr(feature = "bevy", derive(bevy::prelude::Component))]
143pub struct Recorder<Key> {
144    pub(crate) calls: Vec<DrawCall<Key>>,
145    pub(crate) batch: Batch,
146    pub(crate) cache: Tessellator,
147}
148
149impl<Key> Recorder<Key> {
150    pub fn clear(&mut self) {
151        self.calls.clear();
152        self.batch.clear();
153        self.cache.clear();
154    }
155
156    pub fn stroke(
157        &mut self,
158        path: &Path,
159        paint: impl IntoPaint,
160        mut stroke: Stroke,
161        transform: Transform,
162        antialias: bool,
163    ) {
164        let mut paint = paint.into_paint(transform);
165
166        let average_scale = {
167            let sx = (transform.sx * transform.sx + transform.shx * transform.shx).sqrt();
168            let sy = (transform.shy * transform.shy + transform.sy * transform.sy).sqrt();
169            (sx + sy) * 0.5
170        };
171
172        stroke.width = (stroke.width * average_scale).max(0.0);
173
174        let fringe_width = 1.0;
175
176        if stroke.width < fringe_width {
177            // If the stroke width is less than pixel size, use alpha to emulate coverage.
178            // Since coverage is area, scale by alpha*alpha.
179            let alpha = (stroke.width / fringe_width).clamp(0.0, 1.0);
180            let coverage = alpha * alpha;
181            paint.inner_color.alpha *= coverage;
182            paint.outer_color.alpha *= coverage;
183            stroke.width = fringe_width;
184        }
185
186        let fringe_width = if antialias { fringe_width } else { 0.0 };
187
188        let commands = path.transform_iter(transform);
189        let tess_tol = 0.25;
190        self.cache.flatten(commands, tess_tol, 0.01);
191
192        stroke.width *= 0.5;
193
194        let base_vertex = self.batch.base_vertex();
195        let indices = self
196            .cache
197            .expand_stroke(&mut self.batch, stroke, fringe_width, tess_tol);
198
199        let stroke_thr = 1.0 - 0.5 / 255.0;
200        let first = paint.to_instance(stroke.width, fringe_width, stroke_thr);
201        let instance = self.batch.instance(first);
202
203        let second = paint.to_instance(stroke.width, fringe_width, -1.0);
204        let _ = self.batch.instance(second);
205
206        self.calls.push(DrawCall::Stroke {
207            start: indices.start,
208            end: indices.end,
209            base_vertex,
210            instance,
211        });
212    }
213
214    pub fn fill(
215        &mut self,
216        path: &Path,
217        paint: impl IntoPaint,
218        transform: Transform,
219        fill_rule: FillRule,
220        antialias: bool,
221    ) {
222        let paint = paint.into_paint(transform);
223
224        let fringe_width = if antialias { 1.0 } else { 0.0 };
225
226        // Setup uniforms for draw calls
227        let raw = paint.to_instance(fringe_width, fringe_width, -1.0);
228        let instance = self.batch.instance(raw);
229
230        let commands = path.transform_iter(transform);
231        self.cache.flatten(commands, 0.25, 0.01);
232
233        let draw = self
234            .cache
235            .expand_fill(&mut self.batch, fringe_width, LineJoin::Miter, 2.4);
236
237        match draw {
238            // Bounding box fill quad not needed for convex fill
239            Draw::Convex {
240                base_vertex,
241                start,
242                end,
243            } => {
244                let draw = DrawIndexed::new(start, end, base_vertex, instance);
245                if paint.inner_color == paint.outer_color {
246                    self.calls.push(DrawCall::ConvexSimple(draw));
247                } else {
248                    self.calls.push(DrawCall::Convex(draw));
249                }
250            }
251            Draw::Concave {
252                base_vertex,
253                fill,
254                stroke,
255                quad,
256            } => {
257                let stenicl = DrawIndexed::new(fill.start, fill.end, base_vertex, instance);
258
259                let stroke = DrawIndexed::new(stroke.start, stroke.end, base_vertex, instance);
260                let quad = DrawIndexed::new(quad.start, quad.end, base_vertex, instance);
261
262                self.calls.push(DrawCall::Stencil(stenicl));
263                self.calls.push(match fill_rule {
264                    FillRule::NonZero => DrawCall::FringesNonZero(stroke),
265                    FillRule::EvenOdd => DrawCall::FringesEvenOdd(stroke),
266                });
267                self.calls.push(match fill_rule {
268                    FillRule::NonZero => DrawCall::QuadNonZero(quad),
269                    FillRule::EvenOdd => DrawCall::QuadEvenOdd(quad),
270                });
271            }
272        }
273    }
274
275    pub fn blit_premultiplied(&mut self, rect: Rect, transform: Transform, image: Key) {
276        let Rect { min, max } = rect;
277        let base_vertex = self.batch.base_vertex();
278        let indices = self.batch.push_strip(
279            0,
280            &[
281                Vertex::new([max.x, max.y], [1.0, 1.0]).transform(transform),
282                Vertex::new([max.x, min.y], [1.0, 0.0]).transform(transform),
283                Vertex::new([min.x, max.y], [0.0, 1.0]).transform(transform),
284                Vertex::new([min.x, min.y], [0.0, 0.0]).transform(transform),
285            ],
286        );
287        let draw = DrawIndexed::new(indices.start, indices.end, base_vertex, 0);
288        self.calls.push(DrawCall::BindImage(image));
289        self.calls.push(DrawCall::ImagePremultiplied(draw));
290    }
291
292    pub fn blit_unmultiplied(&mut self, rect: Rect, transform: Transform, image: Key) {
293        let Rect { min, max } = rect;
294        let base_vertex = self.batch.base_vertex();
295        let indices = self.batch.push_strip(
296            0,
297            &[
298                Vertex::new([max.x, max.y], [1.0, 1.0]).transform(transform),
299                Vertex::new([max.x, min.y], [1.0, 0.0]).transform(transform),
300                Vertex::new([min.x, max.y], [0.0, 1.0]).transform(transform),
301                Vertex::new([min.x, min.y], [0.0, 0.0]).transform(transform),
302            ],
303        );
304        let draw = DrawIndexed::new(indices.start, indices.end, base_vertex, 0);
305        self.calls.push(DrawCall::BindImage(image));
306        self.calls.push(DrawCall::ImageUnmultiplied(draw));
307    }
308
309    pub fn blit_font(&mut self, rect: Rect, transform: Transform, image: Key) {
310        let Rect { min, max } = rect;
311        let base_vertex = self.batch.base_vertex();
312        let indices = self.batch.push_strip(
313            0,
314            &[
315                Vertex::new([max.x, max.y], [1.0, 1.0]).transform(transform),
316                Vertex::new([max.x, min.y], [1.0, 0.0]).transform(transform),
317                Vertex::new([min.x, max.y], [0.0, 1.0]).transform(transform),
318                Vertex::new([min.x, min.y], [0.0, 0.0]).transform(transform),
319            ],
320        );
321        let draw = DrawIndexed::new(indices.start, indices.end, base_vertex, 0);
322        self.calls.push(DrawCall::BindImage(image));
323        self.calls.push(DrawCall::ImageFont(draw));
324    }
325}