narui_widgets/
subpass.rs

1use narui_core::{
2    layout::Transparent,
3    re_export::smallvec::smallvec,
4    CallbackContext,
5    ContextEffect,
6    ContextMeasure,
7    Fragment,
8    Rect,
9    SubPassRenderFunction,
10    SubPassSetup,
11    Vec2,
12    WidgetContext,
13};
14use narui_macros::{rsx, widget};
15use std::sync::Arc;
16use vulkano::{
17    command_buffer::{AutoCommandBufferBuilder, CommandBufferUsage},
18    descriptor_set::PersistentDescriptorSet,
19    device::{DeviceOwned, Queue},
20    pipeline::{
21        graphics::{
22            color_blend::{
23                AttachmentBlend,
24                BlendFactor,
25                BlendOp,
26                ColorBlendAttachmentState,
27                ColorBlendState,
28                ColorComponents,
29            },
30            depth_stencil::{CompareOp, DepthState, DepthStencilState},
31            input_assembly::{InputAssemblyState, PrimitiveTopology},
32            viewport::ViewportState,
33        },
34        GraphicsPipeline,
35        Pipeline,
36        PipelineBindPoint,
37        StateMode,
38    },
39    render_pass::Subpass,
40    sampler::{Filter, MipmapMode, Sampler, SamplerAddressMode},
41};
42
43mod vertex_shader {
44    vulkano_shaders::shader! {
45        ty: "vertex",
46        src: "
47            #version 450
48            #extension GL_EXT_debug_printf: enable
49            layout(push_constant) uniform PushConstantData {
50                uint width;
51                uint height;
52                float z_index;
53                vec2 origin;
54                vec2 size;
55                ivec2 direction;
56                vec3 coeffs;
57                vec2 window_min;
58                vec2 window_max;
59            } params;
60
61            layout(location = 0) out vec2 origin;
62            layout(location = 1) out ivec2 direction;
63            layout(location = 2) out vec3 coeffs;
64            layout(location = 3) out vec2 window_min;
65            layout(location = 4) out vec2 window_max;
66
67            void main() {
68                int idx = gl_VertexIndex;
69                int top = idx & 1;
70                int left = (idx & 2) / 2;
71
72                origin = params.origin;
73                coeffs = params.coeffs;
74                direction = params.direction;
75                window_min = params.window_min;
76                window_max = params.window_max;
77
78                vec2 pos = params.origin + vec2(top, left) * params.size;
79                pos = (pos / (vec2(params.width, params.height) / 2.) - vec2(1.));
80                gl_Position = vec4(pos, params.z_index, 1.0);
81            }
82        "
83    }
84}
85mod fragment_shader {
86    vulkano_shaders::shader! {
87        ty: "fragment",
88        src: "
89            #version 450
90            #extension GL_EXT_debug_printf: enable
91            layout(location = 0) out vec4 f_color;
92            layout(set = 0, binding = 0) uniform sampler2DMS depth;
93            layout(set = 0, binding = 1) uniform sampler2D color;
94
95            layout(location = 0) flat in vec2 origin;
96            layout(location = 1) flat in ivec2 direction;
97            layout(location = 2) flat in vec3 coeffs;
98            layout(location = 3) flat in vec2 window_min;
99            layout(location = 4) flat in vec2 window_max;
100
101            void main() {
102                ivec2 pos = ivec2(gl_FragCoord.xy - origin);
103
104                if (pos.x < window_min.x || pos.y < window_min.y
105                 || pos.x > window_max.x || pos.y > window_max.y) {
106                    f_color = texelFetch(color, pos, 0);
107                } else {
108                    float sigma = (1. / coeffs.x) / sqrt(2. * 3.14159265358979361);
109                    float sampleCount = ceil(1.5 * sigma);
110
111                    vec3 g = coeffs;
112                    ivec2 size = textureSize(color, 0) - 1;
113                    vec4 color_sum = texelFetch(color, pos, 0) * g.x;
114                    float coeff_norm = g.x;
115                    for (int i = 1; i < sampleCount; i++) {
116                        g.xy *= g.yz;
117                        color_sum += texelFetch(color, min(pos + i * direction, size), 0) * g.x;
118                        color_sum += texelFetch(color, max(pos - i * direction, ivec2(0)), 0) * g.x;
119                        coeff_norm += 2.0 * g.x;
120                    }
121                    vec4 color = color_sum / coeff_norm;
122                    f_color = vec4(color.rgb * color.w, color.w);
123                }
124            }
125        "
126    }
127}
128
129#[widget]
130pub fn raw_blur(
131    children: Fragment,
132    window: Option<Fragment>,
133    sigma: f32,
134    in_x: bool,
135    backdrop_after: Option<usize>,
136    context: &mut WidgetContext,
137) -> FragmentInner {
138    let pipeline_and_sampler = context.effect(
139        |context| {
140            let render_pass = context.vulkan_context.render_pass.clone();
141            let vs = vertex_shader::load(render_pass.device().clone()).unwrap();
142            let fs = fragment_shader::load(render_pass.device().clone()).unwrap();
143            let pipeline = GraphicsPipeline::start()
144                .vertex_shader(vs.entry_point("main").unwrap(), ())
145                .input_assembly_state(
146                    InputAssemblyState::new().topology(PrimitiveTopology::TriangleList),
147                )
148                .viewport_state(ViewportState::viewport_dynamic_scissor_irrelevant())
149                .fragment_shader(fs.entry_point("main").unwrap(), ())
150                .color_blend_state(ColorBlendState {
151                    logic_op: None,
152                    attachments: vec![ColorBlendAttachmentState {
153                        blend: Some(AttachmentBlend {
154                            color_op: BlendOp::Add,
155                            color_source: BlendFactor::SrcAlpha,
156                            color_destination: BlendFactor::OneMinusSrcAlpha,
157                            alpha_op: BlendOp::Max,
158                            alpha_source: BlendFactor::One,
159                            alpha_destination: BlendFactor::One,
160                        }),
161                        color_write_mask: ColorComponents::all(),
162                        color_write_enable: StateMode::Fixed(true),
163                    }],
164                    blend_constants: StateMode::Fixed([1.0, 1.0, 1.0, 1.0]),
165                })
166                .depth_stencil_state(DepthStencilState {
167                    depth: Some(DepthState {
168                        compare_op: StateMode::Fixed(CompareOp::LessOrEqual),
169                        ..Default::default()
170                    }),
171                    ..DepthStencilState::simple_depth_test()
172                })
173                .render_pass(Subpass::from(render_pass.clone(), 0).unwrap())
174                .build(render_pass.device().clone())
175                .unwrap();
176
177            let sampler = Sampler::new(
178                render_pass.device().clone(),
179                Filter::Nearest,
180                Filter::Nearest,
181                MipmapMode::Nearest,
182                SamplerAddressMode::ClampToEdge,
183                SamplerAddressMode::ClampToEdge,
184                SamplerAddressMode::ClampToEdge,
185                0.0,
186                1.0,
187                0.0,
188                0.0,
189            )
190            .unwrap();
191
192            (pipeline, sampler)
193        },
194        (),
195    );
196    let pipeline_and_sampler = pipeline_and_sampler.read();
197    let pipeline = pipeline_and_sampler.0.clone();
198    let sampler = pipeline_and_sampler.1.clone();
199    let queue = context
200        .vulkan_context
201        .queues
202        .iter()
203        .find(|&q| q.family().supports_graphics())
204        .unwrap()
205        .clone();
206
207    fn generate_resolve(
208        in_x: bool,
209        sigma: f32,
210        pipeline: Arc<GraphicsPipeline>,
211        sampler: Arc<Sampler>,
212        queue: Arc<Queue>,
213        window_function: impl Fn(&CallbackContext, Rect, Rect) -> Rect + 'static,
214    ) -> SubPassRenderFunction {
215        std::rc::Rc::new(
216            move |context,
217                  color,
218                  depth,
219                  render_pass,
220                  viewport,
221                  dimensions,
222                  abs_pos,
223                  rect,
224                  z_index| {
225                let window = window_function(context, abs_pos, rect);
226
227                let mut set_builder = PersistentDescriptorSet::start(
228                    pipeline.layout().descriptor_set_layouts()[0].clone(),
229                );
230                set_builder
231                    .add_sampled_image(depth, sampler.clone())
232                    .unwrap()
233                    .add_sampled_image(color, sampler.clone())
234                    .unwrap();
235                let descriptor_set = set_builder.build().unwrap();
236
237                let coeff_a = 1.0 / ((2.0f32 * 3.151_592_3).sqrt() * sigma);
238                let coeff_b = (-0.5 / (sigma * sigma)).exp();
239                let coeff_c = coeff_b * coeff_b;
240
241                let push_constants = vertex_shader::ty::PushConstantData {
242                    width: dimensions[0],
243                    height: dimensions[1],
244                    z_index,
245                    origin: rect.pos.into(),
246                    size: rect.size.into(),
247                    coeffs: [coeff_a, coeff_b, coeff_c],
248                    direction: if in_x { [1, 0] } else { [0, 1] },
249                    window_min: window.top_left_corner().into(),
250                    window_max: window.bottom_right_corner().into(),
251                    _dummy0: Default::default(),
252                    _dummy1: Default::default(),
253                    _dummy2: Default::default(),
254                };
255
256                let mut builder = AutoCommandBufferBuilder::secondary_graphics(
257                    render_pass.device().clone(),
258                    queue.family(),
259                    CommandBufferUsage::MultipleSubmit,
260                    pipeline.subpass().clone(),
261                )
262                .unwrap();
263                builder
264                    .bind_descriptor_sets(
265                        PipelineBindPoint::Graphics,
266                        pipeline.layout().clone(),
267                        0,
268                        descriptor_set,
269                    )
270                    .bind_pipeline_graphics(pipeline.clone())
271                    .push_constants(pipeline.layout().clone(), 0, push_constants)
272                    .set_viewport(0, std::iter::once(viewport))
273                    .draw(4, 1, 0, 0)
274                    .unwrap();
275
276                builder.build().unwrap()
277            },
278        )
279    }
280
281    let resolve = generate_resolve(
282        in_x,
283        sigma,
284        pipeline.clone(),
285        sampler.clone(),
286        queue.clone(),
287        move |context, abs_pos, rect| match window {
288            Some(frag) => {
289                let window_rect = context.measure(frag).unwrap();
290                window_rect.minus_position(abs_pos.pos)
291            }
292            None => Rect::from_corners(Vec2::zero(), rect.size),
293        },
294    );
295
296    let finish =
297        generate_resolve(in_x, sigma, pipeline, sampler, queue, move |_, _, _| Rect::zero());
298
299    FragmentInner::Node {
300        children: smallvec![children],
301        layout: Box::new(Transparent),
302        is_clipper: false,
303        subpass: Some(SubPassSetup {
304            resolve,
305            finish: backdrop_after.map(|after| (finish, Some(after))),
306        }),
307    }
308}
309
310#[widget]
311pub fn blur(
312    children: Fragment,
313    #[default] window: Option<Fragment>,
314    sigma: f32,
315    context: &mut WidgetContext,
316) -> Fragment {
317    rsx! {
318        <raw_blur sigma=sigma in_x=true window=window backdrop_after=None>
319            <raw_blur sigma=sigma in_x=false window=window backdrop_after=None>
320                {children}
321            </raw_blur>
322        </raw_blur>
323    }
324}
325
326#[widget]
327pub fn backdrop_blur(children: Fragment, sigma: f32, context: &mut WidgetContext) -> Fragment {
328    rsx! {
329        <raw_blur sigma=sigma in_x=true window=None backdrop_after=None>
330            <raw_blur sigma=sigma in_x=false window=None backdrop_after=Some(1)>
331                {children}
332            </raw_blur>
333        </raw_blur>
334    }
335}