1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License in the LICENSE-APACHE file or at:
//     https://www.apache.org/licenses/LICENSE-2.0

//! Custom draw pipes

use super::DrawWindow;
use kas::draw::PassId;
use kas::geom::{Rect, Size};

/// Allows use of the low-level graphics API
///
/// To use this, write an implementation of [`CustomPipe`], then pass the
/// corresponding [`CustomPipeBuilder`] to [`crate::Toolkit::new_custom`].
pub trait DrawCustom<CW: CustomWindow> {
    /// Call a custom draw pipe
    fn custom(&mut self, pass: PassId, rect: Rect, param: CW::Param);
}

/// Builder for a [`CustomPipe`]
pub trait CustomPipeBuilder {
    type Pipe: CustomPipe;

    /// Request a device supporting these features and limits
    ///
    /// See [`wgpu::Adapter::request_device`] and [`wgpu::DeviceDescriptor`] doc.
    fn device_descriptor() -> wgpu::DeviceDescriptor<'static> {
        Default::default()
    }

    /// Build a pipe
    ///
    /// A "common" bind group with layout `bgl_common` is available, supplying
    /// window sizing and theme lighting information (refer to existing pipes
    /// and shaders for usage). Usage is optional.
    ///
    /// The given texture format should be used to construct a
    /// compatible [`wgpu::RenderPipeline`].
    fn build(
        &mut self,
        device: &wgpu::Device,
        bgl_common: &wgpu::BindGroupLayout,
        tex_format: wgpu::TextureFormat,
    ) -> Self::Pipe;
}

/// A custom draw pipe
///
/// A "draw pipe" consists of draw primitives (usually triangles), resources
/// (textures), shaders, and pipe configuration (e.g. blending mode).
/// A custom pipe allows direct use of the WebGPU graphics stack.
///
/// To use this, pass the corresponding [`CustomPipeBuilder`] to
/// [`crate::Toolkit::new_custom`].
///
/// Note that `kas-wgpu` accepts only a single custom pipe. To use more than
/// one custom graphics pipeline, you must implement your own multiplexer.
pub trait CustomPipe: 'static {
    /// Associated per-window state for the custom pipe
    type Window: CustomWindow;

    /// Construct a window associated with this pipeline
    ///
    /// Note: [`Self::resize`] will be called before usage.
    fn new_window(&self, device: &wgpu::Device) -> Self::Window;

    /// Called whenever the window is resized
    fn resize(
        &self,
        window: &mut Self::Window,
        device: &wgpu::Device,
        queue: &wgpu::Queue,
        size: Size,
    ) {
        let _ = (window, device, queue, size);
    }

    /// Per-frame updates
    ///
    /// This is called once per frame before rendering operations, and may for
    /// example be used to prepare uniform and buffers.
    ///
    /// This method is optional; by default it does nothing.
    fn prepare(
        &self,
        window: &mut Self::Window,
        device: &wgpu::Device,
        staging_belt: &mut wgpu::util::StagingBelt,
        encoder: &mut wgpu::CommandEncoder,
    ) {
        let _ = (window, device, staging_belt, encoder);
    }

    /// Render (pass)
    ///
    /// Each item drawn is associated with a clip region, and each of these
    /// with a pass. This method will be called once for each clip region in use
    /// (possibly also for other clip regions). Drawing uses an existing texture
    /// and occurs after most other draw operations, but before text.
    ///
    /// The "common" bind group supplies window scaling and theme lighting
    /// information may optionally be set (see [`CustomPipeBuilder::build`]).
    ///
    /// This method is optional; by default it does nothing.
    #[allow(unused)]
    fn render_pass<'a>(
        &'a self,
        window: &'a mut Self::Window,
        device: &wgpu::Device,
        pass: usize,
        rpass: &mut wgpu::RenderPass<'a>,
        bg_common: &'a wgpu::BindGroup,
    ) {
    }

    /// Render (final)
    ///
    /// This method is the last step in drawing a frame except for text
    /// rendering. Depending on the application, it may make more sense to draw
    /// in [`CustomPipe::render_pass`] or in this method.
    ///
    /// This method is optional; by default it does nothing.
    #[allow(unused)]
    fn render_final<'a>(
        &'a self,
        window: &'a mut Self::Window,
        device: &wgpu::Device,
        encoder: &mut wgpu::CommandEncoder,
        frame_view: &wgpu::TextureView,
        size: Size,
    ) {
    }
}

/// Per-window state for a custom draw pipe
///
/// One instance is constructed per window. Since the [`CustomPipe`] is not
/// accessible during a widget's [`kas::Layout::draw`] calls, this struct must
/// batch per-frame draw data.
pub trait CustomWindow: 'static {
    /// User parameter type
    type Param;

    /// Invoke user-defined custom routine
    ///
    /// Custom add-primitives / update function called from user code by
    /// [`DrawCustom::custom`].
    fn invoke(&mut self, pass: PassId, rect: Rect, param: Self::Param);
}

/// A dummy implementation (does nothing)
impl CustomPipeBuilder for () {
    type Pipe = ();
    fn build(
        &mut self,
        _: &wgpu::Device,
        _: &wgpu::BindGroupLayout,
        _: wgpu::TextureFormat,
    ) -> Self::Pipe {
    }
}

pub enum Void {}

/// A dummy implementation (does nothing)
impl CustomPipe for () {
    type Window = ();
    fn new_window(&self, _: &wgpu::Device) -> Self::Window {}
    fn resize(&self, _: &mut Self::Window, _: &wgpu::Device, _: &wgpu::Queue, _: Size) {}
}

/// A dummy implementation (does nothing)
impl CustomWindow for () {
    type Param = Void;
    fn invoke(&mut self, _: PassId, _: Rect, _: Self::Param) {}
}

impl<CW: CustomWindow> DrawCustom<CW> for DrawWindow<CW> {
    fn custom(&mut self, pass: PassId, rect: Rect, param: CW::Param) {
        self.custom.invoke(pass, rect, param);
    }
}