Skip to main content

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