use std::sync::Arc;
use agg_gui::TransAffine;
use agg_rust::conv_curve::ConvCurve;
use agg_rust::conv_transform::ConvTransform;
use crate::{DrawCommand, WgpuGfxCtx, WgpuLayerEntry};
pub(crate) struct ClipMaskMesh {
pub(crate) verts: Vec<[f32; 3]>,
pub(crate) indices: Vec<u32>,
}
impl ClipMaskMesh {
pub(crate) fn composite_verts(&self, origin_x: f32, origin_y: f32, w: u32, h: u32) -> Vec<f32> {
let fw = (w as f32).max(1.0);
let fh = (h as f32).max(1.0);
let mut out = Vec::with_capacity(self.verts.len() * 5);
for v in &self.verts {
let u = (v[0] - origin_x) / fw;
let vv = 1.0 - (v[1] - origin_y) / fh;
out.extend_from_slice(&[v[0], v[1], u, vv, v[2]]);
}
out
}
}
impl WgpuGfxCtx {
pub(crate) fn clip_path_impl(&mut self) {
let ctm = *self.ctm();
let fill_rule = self.fill_rule;
let tess = {
let mut curves = ConvCurve::new(&mut self.path);
let mut transformed = ConvTransform::new(&mut curves, ctm);
agg_gui::gl_renderer::tessellate_path_aa(&mut transformed, 1.0, fill_rule)
};
let (verts, indices) = match tess {
Some((v, i)) if !v.is_empty() && !i.is_empty() => (v, i),
_ => (Vec::new(), Vec::new()),
};
let mut min_x = f32::INFINITY;
let mut min_y = f32::INFINITY;
let mut max_x = f32::NEG_INFINITY;
let mut max_y = f32::NEG_INFINITY;
for v in &verts {
min_x = min_x.min(v[0]);
min_y = min_y.min(v[1]);
max_x = max_x.max(v[0]);
max_y = max_y.max(v[1]);
}
let (mut x1, mut y1, mut x2, mut y2) =
(min_x as f64, min_y as f64, max_x as f64, max_y as f64);
if let Some([cx, cy, cw, ch]) = self.current_clip() {
x1 = x1.max(cx as f64);
y1 = y1.max(cy as f64);
x2 = x2.min((cx + cw) as f64);
y2 = y2.min((cy + ch) as f64);
}
x1 = x1.max(0.0);
y1 = y1.max(0.0);
x2 = x2.min(self.viewport.0 as f64);
y2 = y2.min(self.viewport.1 as f64);
let empty = verts.is_empty() || !(x2 > x1 && y2 > y1);
let (origin_x, origin_y) = if empty {
(0.0, 0.0)
} else {
(x1.floor(), y1.floor())
};
let w = if empty {
1
} else {
(x2.ceil() - origin_x).max(1.0) as u32
};
let h = if empty {
1
} else {
(y2.ceil() - origin_y).max(1.0) as u32
};
let mask = if empty {
ClipMaskMesh {
verts: Vec::new(),
indices: Vec::new(),
}
} else {
ClipMaskMesh { verts, indices }
};
let saved = self.capture_draw_state();
let parent_clip = self.current_clip();
let (texture, view) = self.alloc_layer_texture(w, h);
let mut local: TransAffine = ctm;
local.tx -= origin_x;
local.ty -= origin_y;
self.layer_stack.push(WgpuLayerEntry {
texture: Arc::clone(&texture),
view: view.clone(),
width: w,
height: h,
origin_x,
origin_y,
alpha: 1.0,
saved,
retained_key: None,
rounded_clip: None,
parent_clip,
opaque_backdrop: false,
clip_mask: Some(mask),
clip_saved_path: Some(self.path.clone()),
});
self.viewport = (w as f32, h as f32);
self.state_stack = vec![(local, None)];
self.commands.push(DrawCommand::PushLayer {
texture,
view,
width: w,
height: h,
});
}
pub(crate) fn push_masked_pop_command(&mut self, layer: WgpuLayerEntry, mask: ClipMaskMesh) {
let verts = mask.composite_verts(
layer.origin_x as f32,
layer.origin_y as f32,
layer.width,
layer.height,
);
self.commands.push(DrawCommand::PopLayerMasked {
texture: layer.texture,
view: layer.view,
layer_w: layer.width,
layer_h: layer.height,
alpha: layer.alpha as f32,
verts,
indices: mask.indices,
parent_clip: layer.parent_clip,
});
}
}
pub(crate) const LAYER_MESH_WGSL: &str = "
struct LayerUniforms {
resolution: vec2<f32>,
alpha: f32,
mask_enabled: u32,
layer_size: vec2<f32>,
mask_radius: f32,
pad0: f32,
mask_rect: vec4<f32>,
}
@group(0) @binding(0) var<uniform> u: LayerUniforms;
@group(1) @binding(0) var u_tex: texture_2d<f32>;
@group(1) @binding(1) var u_sampler: sampler;
struct VIn {
@location(0) pos: vec2<f32>,
@location(1) uv: vec2<f32>,
@location(2) cov: f32,
}
struct VOut {
@builtin(position) clip_pos: vec4<f32>,
@location(0) v_uv: vec2<f32>,
@location(1) v_cov: f32,
}
@vertex fn vs_main(in: VIn) -> VOut {
let ndc = (in.pos / u.resolution) * 2.0 - 1.0;
return VOut(vec4<f32>(ndc, 0.0, 1.0), in.uv, in.cov);
}
@fragment fn fs_main(in: VOut) -> @location(0) vec4<f32> {
let c = textureSample(u_tex, u_sampler, in.v_uv);
let a = u.alpha * clamp(in.v_cov, 0.0, 1.0);
return vec4<f32>(c.rgb * a, c.a * a);
}
";
pub(crate) fn build_layer_mesh_pipeline(
device: &wgpu::Device,
bgl0: &wgpu::BindGroupLayout,
bgl1: &wgpu::BindGroupLayout,
surface_format: wgpu::TextureFormat,
blend: wgpu::BlendState,
sample_count: u32,
) -> wgpu::RenderPipeline {
let module = device.create_shader_module(wgpu::ShaderModuleDescriptor {
label: Some("layer_mesh"),
source: wgpu::ShaderSource::Wgsl(LAYER_MESH_WGSL.into()),
});
let layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: Some("layer_mesh"),
bind_group_layouts: &[Some(bgl0), Some(bgl1)],
immediate_size: 0,
});
let attrs = [
wgpu::VertexAttribute {
format: wgpu::VertexFormat::Float32x2,
offset: 0,
shader_location: 0,
},
wgpu::VertexAttribute {
format: wgpu::VertexFormat::Float32x2,
offset: 8,
shader_location: 1,
},
wgpu::VertexAttribute {
format: wgpu::VertexFormat::Float32,
offset: 16,
shader_location: 2,
},
];
device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
label: Some("layer_mesh"),
layout: Some(&layout),
vertex: wgpu::VertexState {
module: &module,
entry_point: Some("vs_main"),
compilation_options: Default::default(),
buffers: &[wgpu::VertexBufferLayout {
array_stride: 20,
step_mode: wgpu::VertexStepMode::Vertex,
attributes: &attrs,
}],
},
fragment: Some(wgpu::FragmentState {
module: &module,
entry_point: Some("fs_main"),
compilation_options: Default::default(),
targets: &[Some(wgpu::ColorTargetState {
format: surface_format,
blend: Some(blend),
write_mask: wgpu::ColorWrites::ALL,
})],
}),
primitive: wgpu::PrimitiveState {
topology: wgpu::PrimitiveTopology::TriangleList,
cull_mode: None,
..Default::default()
},
depth_stencil: None,
multisample: wgpu::MultisampleState {
count: sample_count,
mask: !0,
alpha_to_coverage_enabled: false,
},
multiview_mask: None,
cache: None,
})
}
#[cfg(test)]
mod tests {
use super::ClipMaskMesh;
#[test]
fn composite_verts_map_uv_with_flipped_v() {
let mesh = ClipMaskMesh {
verts: vec![[10.0, 20.0, 1.0], [30.0, 40.0, 0.5]],
indices: vec![0, 1, 0],
};
let out = mesh.composite_verts(10.0, 20.0, 20, 20);
assert_eq!(&out[0..5], &[10.0, 20.0, 0.0, 1.0, 1.0]);
assert_eq!(&out[5..10], &[30.0, 40.0, 1.0, 0.0, 0.5]);
}
}