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}