kas_wgpu/draw/
draw_pipe.rs

1// Licensed under the Apache License, Version 2.0 (the "License");
2// you may not use this file except in compliance with the License.
3// You may obtain a copy of the License in the LICENSE-APACHE file or at:
4//     https://www.apache.org/licenses/LICENSE-2.0
5
6//! Drawing API for `kas_wgpu`
7
8use futures_lite::future::block_on;
9use std::f32::consts::FRAC_PI_2;
10use wgpu::util::DeviceExt;
11
12use super::*;
13use crate::DrawShadedImpl;
14use crate::Options;
15use kas::cast::traits::*;
16use kas::config::RasterConfig;
17use kas::draw::color::Rgba;
18use kas::draw::*;
19use kas::geom::{Quad, Size, Vec2};
20use kas::runner::Error;
21use kas::text::{Effect, TextDisplay};
22
23impl<C: CustomPipe> DrawPipe<C> {
24    /// Construct
25    pub fn new<CB: CustomPipeBuilder<Pipe = C>>(
26        instance: &wgpu::Instance,
27        custom: &mut CB,
28        options: &Options,
29        surface: Option<&wgpu::Surface>,
30    ) -> Result<Self, Error> {
31        let mut adapter_options = options.adapter_options();
32        adapter_options.compatible_surface = surface;
33        let req = instance.request_adapter(&adapter_options);
34        let adapter = match block_on(req) {
35            Ok(a) => a,
36            Err(e) => return Err(Error::Graphics(Box::new(e))),
37        };
38        log::info!("Using graphics adapter: {}", adapter.get_info().name);
39
40        // Use adapter texture size limits to support the largest window surface possible
41        let mut desc = CB::device_descriptor();
42        desc.required_limits = desc.required_limits.using_resolution(adapter.limits());
43
44        let req = adapter.request_device(&desc);
45        let (device, queue) = block_on(req).map_err(|e| Error::Graphics(Box::new(e)))?;
46
47        let shaders = ShaderManager::new(&device);
48
49        // Create staging belt and a local pool
50        let staging_belt = wgpu::util::StagingBelt::new(1024);
51
52        let bgl_common = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
53            label: Some("common bind group layout"),
54            entries: &[
55                wgpu::BindGroupLayoutEntry {
56                    binding: 0,
57                    visibility: wgpu::ShaderStages::VERTEX,
58                    ty: wgpu::BindingType::Buffer {
59                        ty: wgpu::BufferBindingType::Uniform,
60                        has_dynamic_offset: false,
61                        min_binding_size: wgpu::BufferSize::new(16),
62                    },
63                    count: None,
64                },
65                wgpu::BindGroupLayoutEntry {
66                    binding: 1,
67                    visibility: wgpu::ShaderStages::FRAGMENT,
68                    ty: wgpu::BindingType::Buffer {
69                        ty: wgpu::BufferBindingType::Uniform,
70                        has_dynamic_offset: false,
71                        min_binding_size: wgpu::BufferSize::new(16),
72                    },
73                    count: None,
74                },
75            ],
76        });
77
78        // Light dir: `(a, b)` where `0 ≤ a < pi/2` is the angle to the screen
79        // normal (i.e. `a = 0` is straight at the screen) and `b` is the bearing
80        // (from UP, clockwise), both in radians.
81        let dir: (f32, f32) = (0.3, 0.4);
82        assert!(0.0 <= dir.0 && dir.0 < FRAC_PI_2);
83        let a = (dir.0.sin(), dir.0.cos());
84        // We normalise intensity:
85        let f = a.0 / a.1;
86        let light_norm = [dir.1.sin() * f, -dir.1.cos() * f, 1.0, 0.0];
87
88        let light_norm_buf = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
89            label: Some("light_norm_buf"),
90            contents: bytemuck::cast_slice(&light_norm),
91            usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
92        });
93
94        let images = images::Images::new(&device, &shaders, &bgl_common);
95        let shaded_square = shaded_square::Pipeline::new(&device, &shaders, &bgl_common);
96        let shaded_round = shaded_round::Pipeline::new(&device, &shaders, &bgl_common);
97        let flat_round = flat_round::Pipeline::new(&device, &shaders, &bgl_common);
98        let round_2col = round_2col::Pipeline::new(&device, &shaders, &bgl_common);
99        let custom = custom.build(&device, &bgl_common, RENDER_TEX_FORMAT);
100
101        Ok(DrawPipe {
102            adapter,
103            device,
104            queue,
105            staging_belt,
106            bgl_common,
107            light_norm_buf,
108            bg_common: vec![],
109            images,
110            shaded_square,
111            shaded_round,
112            flat_round,
113            round_2col,
114            custom,
115        })
116    }
117
118    /// Process window resize
119    pub fn resize(&self, window: &mut DrawWindow<C::Window>, size: Size) {
120        window.clip_regions[0].rect.size = size;
121
122        let vsize = Vec2::conv(size);
123        let off = vsize * -0.5;
124        let scale = Vec2::splat(2.0) / vsize;
125        window.scale = [off.0, off.1, scale.0, -scale.1];
126
127        self.custom
128            .resize(&mut window.custom, &self.device, &self.queue, size);
129
130        self.queue.submit(std::iter::empty());
131    }
132
133    /// Render batched draw instructions via `rpass`
134    pub fn render(
135        &mut self,
136        window: &mut DrawWindow<C::Window>,
137        frame_view: &wgpu::TextureView,
138        clear_color: wgpu::Color,
139    ) {
140        // Update all bind groups. We use a separate bind group for each clip
141        // region and update on each render, although they don't always change.
142        // NOTE: we could use push constants instead.
143        let mut scale = window.scale;
144        let base_offset = (scale[0], scale[1]);
145        for (region, bg) in window.clip_regions.iter().zip(self.bg_common.iter()) {
146            let offset = Vec2::conv(region.offset);
147            scale[0] = base_offset.0 - offset.0;
148            scale[1] = base_offset.1 - offset.1;
149            self.queue
150                .write_buffer(&bg.0, 0, bytemuck::cast_slice(&scale));
151        }
152        let device = &self.device;
153        let bg_len = self.bg_common.len();
154        if window.clip_regions.len() > bg_len {
155            let (bgl_common, light_norm_buf) = (&self.bgl_common, &self.light_norm_buf);
156            self.bg_common
157                .extend(window.clip_regions[bg_len..].iter().map(|region| {
158                    let offset = Vec2::conv(region.offset);
159                    scale[0] = base_offset.0 - offset.0;
160                    scale[1] = base_offset.1 - offset.1;
161                    let scale_buf = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
162                        label: Some("scale_buf"),
163                        contents: bytemuck::cast_slice(&scale),
164                        usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
165                    });
166                    let bg_common = device.create_bind_group(&wgpu::BindGroupDescriptor {
167                        label: Some("common bind group"),
168                        layout: bgl_common,
169                        entries: &[
170                            wgpu::BindGroupEntry {
171                                binding: 0,
172                                resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding {
173                                    buffer: &scale_buf,
174                                    offset: 0,
175                                    size: None,
176                                }),
177                            },
178                            wgpu::BindGroupEntry {
179                                binding: 1,
180                                resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding {
181                                    buffer: light_norm_buf,
182                                    offset: 0,
183                                    size: None,
184                                }),
185                            },
186                        ],
187                    });
188                    (scale_buf, bg_common)
189                }));
190        }
191        self.queue.submit(std::iter::empty());
192
193        let mut encoder = self
194            .device
195            .create_command_encoder(&wgpu::CommandEncoderDescriptor {
196                label: Some("render"),
197            });
198
199        self.images.prepare(
200            &mut window.images,
201            &self.device,
202            &self.queue,
203            &mut self.staging_belt,
204            &mut encoder,
205        );
206        window
207            .shaded_square
208            .write_buffers(&self.device, &mut self.staging_belt, &mut encoder);
209        window
210            .shaded_round
211            .write_buffers(&self.device, &mut self.staging_belt, &mut encoder);
212        window
213            .flat_round
214            .write_buffers(&self.device, &mut self.staging_belt, &mut encoder);
215        window
216            .round_2col
217            .write_buffers(&self.device, &mut self.staging_belt, &mut encoder);
218        self.custom.prepare(
219            &mut window.custom,
220            &self.device,
221            &mut self.staging_belt,
222            &mut encoder,
223        );
224
225        let mut color_attachments = [Some(wgpu::RenderPassColorAttachment {
226            view: frame_view,
227            depth_slice: None,
228            resolve_target: None,
229            ops: wgpu::Operations {
230                load: wgpu::LoadOp::Clear(clear_color),
231                store: wgpu::StoreOp::Store,
232            },
233        })];
234
235        // Order passes to ensure overlays are drawn after other content
236        let mut passes: Vec<_> = window
237            .clip_regions
238            .iter()
239            .map(|pass| pass.order)
240            .enumerate()
241            .collect();
242        // Note that sorting is stable (does not re-order equal elements):
243        passes.sort_by_key(|pass| pass.1);
244
245        // We use a separate render pass for each clipped region.
246        for pass in passes.drain(..).map(|pass| pass.0) {
247            let rect = window.clip_regions[pass].rect;
248            if rect.size.0 == 0 || rect.size.1 == 0 {
249                continue;
250            }
251            let bg_common = &self.bg_common[pass].1;
252
253            {
254                let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
255                    label: Some("kas-wgpu render pass"),
256                    color_attachments: &color_attachments,
257                    depth_stencil_attachment: None,
258                    timestamp_writes: None,
259                    occlusion_query_set: None,
260                });
261                rpass.set_scissor_rect(
262                    rect.pos.0.cast(),
263                    rect.pos.1.cast(),
264                    rect.size.0.cast(),
265                    rect.size.1.cast(),
266                );
267
268                self.round_2col
269                    .render(&window.round_2col, pass, &mut rpass, bg_common);
270                self.shaded_square
271                    .render(&window.shaded_square, pass, &mut rpass, bg_common);
272                self.images
273                    .render(&window.images, pass, &mut rpass, bg_common);
274                self.shaded_round
275                    .render(&window.shaded_round, pass, &mut rpass, bg_common);
276                self.flat_round
277                    .render(&window.flat_round, pass, &mut rpass, bg_common);
278                self.custom.render_pass(
279                    &mut window.custom,
280                    &self.device,
281                    pass,
282                    &mut rpass,
283                    bg_common,
284                );
285            }
286
287            color_attachments[0].as_mut().unwrap().ops.load = wgpu::LoadOp::Load;
288        }
289
290        let size = window.clip_regions[0].rect.size;
291
292        self.custom.render_final(
293            &mut window.custom,
294            &self.device,
295            &mut encoder,
296            frame_view,
297            size,
298        );
299
300        // Keep only first clip region (which is the entire window)
301        window.clip_regions.truncate(1);
302
303        self.staging_belt.finish();
304        self.queue.submit(std::iter::once(encoder.finish()));
305
306        self.staging_belt.recall();
307    }
308}
309
310impl<C: CustomPipe> DrawSharedImpl for DrawPipe<C> {
311    type Draw = DrawWindow<C::Window>;
312
313    fn max_texture_dimension_2d(&self) -> u32 {
314        self.device.limits().max_texture_dimension_2d
315    }
316
317    fn set_raster_config(&mut self, config: &RasterConfig) {
318        self.images.text.set_raster_config(config);
319    }
320
321    #[inline]
322    fn image_alloc(&mut self, size: (u32, u32)) -> Result<ImageId, AllocError> {
323        self.images.alloc(size)
324    }
325
326    #[inline]
327    fn image_upload(&mut self, id: ImageId, data: &[u8], format: ImageFormat) {
328        self.images
329            .upload(&self.device, &self.queue, id, data, format);
330    }
331
332    #[inline]
333    fn image_free(&mut self, id: ImageId) {
334        self.images.free(id);
335    }
336
337    #[inline]
338    fn image_size(&self, id: ImageId) -> Option<(u32, u32)> {
339        self.images.image_size(id)
340    }
341
342    #[inline]
343    fn draw_image(&self, draw: &mut Self::Draw, pass: PassId, id: ImageId, rect: Quad) {
344        if let Some((atlas, tex)) = self.images.get_im_atlas_coords(id) {
345            draw.images.rect(pass, atlas, tex, rect);
346        };
347    }
348
349    #[inline]
350    fn draw_text(
351        &mut self,
352        draw: &mut Self::Draw,
353        pass: PassId,
354        pos: Vec2,
355        bb: Quad,
356        text: &TextDisplay,
357        col: Rgba,
358    ) {
359        let time = std::time::Instant::now();
360        draw.images.text(&mut self.images, pass, pos, bb, text, col);
361        draw.common.report_dur_text(time.elapsed());
362    }
363
364    fn draw_text_effects(
365        &mut self,
366        draw: &mut Self::Draw,
367        pass: PassId,
368        pos: Vec2,
369        bb: Quad,
370        text: &TextDisplay,
371        effects: &[Effect],
372        colors: &[Rgba],
373    ) {
374        let time = std::time::Instant::now();
375        draw.images.text_effects(
376            &mut self.images,
377            pass,
378            pos,
379            bb,
380            text,
381            effects,
382            colors,
383            |quad, col| {
384                draw.shaded_square.rect(pass, quad, col);
385            },
386        );
387        draw.common.report_dur_text(time.elapsed());
388    }
389}
390
391impl<CW: CustomWindow> DrawImpl for DrawWindow<CW> {
392    fn common_mut(&mut self) -> &mut WindowCommon {
393        &mut self.common
394    }
395
396    fn new_pass(
397        &mut self,
398        parent_pass: PassId,
399        rect: Rect,
400        offset: Offset,
401        class: PassType,
402    ) -> PassId {
403        let parent = match class {
404            PassType::Clip => &self.clip_regions[parent_pass.pass()],
405            PassType::Overlay => {
406                // NOTE: parent_pass.pass() is always zero in this case since
407                // this is only invoked from the Window (root).
408                &self.clip_regions[0]
409            }
410        };
411        let order = match class {
412            PassType::Clip => (parent.order << 4) + 1,
413            PassType::Overlay => (parent.order << 16) + 1,
414        };
415        let rect = rect - parent.offset;
416        let offset = offset + parent.offset;
417        let rect = rect.intersection(&parent.rect).unwrap_or(Rect::ZERO);
418        let pass = self.clip_regions.len().cast();
419        self.clip_regions.push(ClipRegion {
420            rect,
421            offset,
422            order,
423        });
424        PassId::new(pass)
425    }
426
427    #[inline]
428    fn get_clip_rect(&self, pass: PassId) -> Rect {
429        let region = &self.clip_regions[pass.pass()];
430        region.rect + region.offset
431    }
432
433    #[inline]
434    fn rect(&mut self, pass: PassId, rect: Quad, col: Rgba) {
435        self.shaded_square.rect(pass, rect, col);
436    }
437
438    #[inline]
439    fn frame(&mut self, pass: PassId, outer: Quad, inner: Quad, col: Rgba) {
440        self.shaded_square.frame(pass, outer, inner, col);
441    }
442}
443
444impl<CW: CustomWindow> DrawRoundedImpl for DrawWindow<CW> {
445    #[inline]
446    fn rounded_line(&mut self, pass: PassId, p1: Vec2, p2: Vec2, radius: f32, col: Rgba) {
447        self.flat_round.line(pass, p1, p2, radius, col);
448    }
449
450    #[inline]
451    fn circle(&mut self, pass: PassId, rect: Quad, inner_radius: f32, col: Rgba) {
452        self.flat_round.circle(pass, rect, inner_radius, col);
453    }
454
455    #[inline]
456    fn circle_2col(&mut self, pass: PassId, rect: Quad, col1: Rgba, col2: Rgba) {
457        self.round_2col.circle(pass, rect, col1, col2);
458    }
459
460    #[inline]
461    fn rounded_frame(&mut self, pass: PassId, outer: Quad, inner: Quad, r1: f32, col: Rgba) {
462        self.flat_round.rounded_frame(pass, outer, inner, r1, col);
463    }
464
465    #[inline]
466    fn rounded_frame_2col(&mut self, pass: PassId, outer: Quad, inner: Quad, c1: Rgba, c2: Rgba) {
467        self.round_2col.frame(pass, outer, inner, c1, c2);
468    }
469}
470
471impl<CW: CustomWindow> DrawShadedImpl for DrawWindow<CW> {
472    #[inline]
473    fn shaded_square(&mut self, pass: PassId, rect: Quad, norm: (f32, f32), col: Rgba) {
474        self.shaded_square
475            .shaded_rect(pass, rect, Vec2::from(norm), col);
476    }
477
478    #[inline]
479    fn shaded_circle(&mut self, pass: PassId, rect: Quad, norm: (f32, f32), col: Rgba) {
480        self.shaded_round.circle(pass, rect, Vec2::from(norm), col);
481    }
482
483    #[inline]
484    fn shaded_square_frame(
485        &mut self,
486        pass: PassId,
487        outer: Quad,
488        inner: Quad,
489        norm: (f32, f32),
490        outer_col: Rgba,
491        inner_col: Rgba,
492    ) {
493        self.shaded_square
494            .shaded_frame(pass, outer, inner, Vec2::from(norm), outer_col, inner_col);
495    }
496
497    #[inline]
498    fn shaded_round_frame(
499        &mut self,
500        pass: PassId,
501        outer: Quad,
502        inner: Quad,
503        norm: (f32, f32),
504        col: Rgba,
505    ) {
506        self.shaded_round
507            .shaded_frame(pass, outer, inner, Vec2::from(norm), col);
508    }
509}