Krolyxon
Commit a44bdf7

Branches

Tags

Refs →

remagesrcrenderprims.rs

203 lines · 6.8 KB · Rust

1//! One instanced SDF shader for rects, rounded rects, borders and dots.
2
3use bytemuck::{Pod, Zeroable};
4use wgpu::util::DeviceExt;
5
6/// A primitive in physical pixels; colours are linear with straight alpha.
7#[repr(C)]
8#[derive(Clone, Copy, Debug, Pod, Zeroable)]
9pub struct Prim {
10 pub rect: [f32; 4],
11 pub color: [f32; 4],
12 pub border_color: [f32; 4],
13 /// x: corner radius, y: border width.
14 pub params: [f32; 4],
15}
16
17impl Prim {
18 pub fn rect(rect: [f32; 4], color: [f32; 4]) -> Self {
19 Self {
20 rect,
21 color,
22 border_color: [0.0; 4],
23 params: [0.0; 4],
24 }
25 }
26
27 pub fn rounded(rect: [f32; 4], color: [f32; 4], radius: f32) -> Self {
28 Self {
29 params: [radius, 0.0, 0.0, 0.0],
30 ..Self::rect(rect, color)
31 }
32 }
33
34 /// An outline only.
35 pub fn outline(rect: [f32; 4], color: [f32; 4], radius: f32, width: f32) -> Self {
36 Self {
37 rect,
38 color: [0.0; 4],
39 border_color: color,
40 params: [radius, width, 0.0, 0.0],
41 }
42 }
43
44 /// A dot centred on `c`, optionally ringed.
45 pub fn dot(c: [f32; 2], r: f32, color: [f32; 4], ring: Option<([f32; 4], f32)>) -> Self {
46 let (border_color, w) = ring.unwrap_or(([0.0; 4], 0.0));
47 let r = r + w;
48 Self {
49 rect: [c[0] - r, c[1] - r, 2.0 * r, 2.0 * r],
50 color,
51 border_color,
52 params: [r, w, 0.0, 0.0],
53 }
54 }
55}
56
57pub struct PrimPipeline {
58 pipeline: wgpu::RenderPipeline,
59 globals: wgpu::Buffer,
60 bind_group: wgpu::BindGroup,
61 instances: wgpu::Buffer,
62 capacity: usize,
63 /// Instances of the under, over and overlay layers, in that order: their end offsets.
64 ends: [u32; 3],
65}
66
67const ATTRS: [wgpu::VertexAttribute; 4] =
68 wgpu::vertex_attr_array![0 => Float32x4, 1 => Float32x4, 2 => Float32x4, 3 => Float32x4];
69
70fn instance_buffer(device: &wgpu::Device, capacity: usize) -> wgpu::Buffer {
71 device.create_buffer(&wgpu::BufferDescriptor {
72 label: Some("prims"),
73 size: (capacity * size_of::<Prim>()) as u64,
74 usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
75 mapped_at_creation: false,
76 })
77}
78
79impl PrimPipeline {
80 pub fn new(device: &wgpu::Device, target: wgpu::TextureFormat) -> Self {
81 let globals = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
82 label: Some("prim globals"),
83 contents: bytemuck::bytes_of(&[0.0f32; 4]),
84 usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
85 });
86 let layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
87 label: Some("prims"),
88 entries: &[wgpu::BindGroupLayoutEntry {
89 binding: 0,
90 visibility: wgpu::ShaderStages::VERTEX,
91 ty: wgpu::BindingType::Buffer {
92 ty: wgpu::BufferBindingType::Uniform,
93 has_dynamic_offset: false,
94 min_binding_size: None,
95 },
96 count: None,
97 }],
98 });
99 let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
100 label: Some("prims"),
101 layout: &layout,
102 entries: &[wgpu::BindGroupEntry {
103 binding: 0,
104 resource: globals.as_entire_binding(),
105 }],
106 });
107 let shader = device.create_shader_module(wgpu::include_wgsl!("shaders/prims.wgsl"));
108 let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
109 label: Some("prims"),
110 bind_group_layouts: &[Some(&layout)],
111 immediate_size: 0,
112 });
113 let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
114 label: Some("prims"),
115 layout: Some(&pipeline_layout),
116 vertex: wgpu::VertexState {
117 module: &shader,
118 entry_point: Some("vs"),
119 compilation_options: Default::default(),
120 buffers: &[Some(wgpu::VertexBufferLayout {
121 array_stride: size_of::<Prim>() as u64,
122 step_mode: wgpu::VertexStepMode::Instance,
123 attributes: &ATTRS,
124 })],
125 },
126 primitive: wgpu::PrimitiveState::default(),
127 depth_stencil: None,
128 multisample: wgpu::MultisampleState::default(),
129 fragment: Some(wgpu::FragmentState {
130 module: &shader,
131 entry_point: Some("fs"),
132 compilation_options: Default::default(),
133 targets: &[Some(wgpu::ColorTargetState {
134 format: target,
135 blend: Some(wgpu::BlendState::PREMULTIPLIED_ALPHA_BLENDING),
136 write_mask: wgpu::ColorWrites::ALL,
137 })],
138 }),
139 multiview_mask: None,
140 cache: None,
141 });
142 let capacity = 64;
143 Self {
144 pipeline,
145 globals,
146 bind_group,
147 instances: instance_buffer(device, capacity),
148 capacity,
149 ends: [0; 3],
150 }
151 }
152
153 /// Upload the layers: `under` draws before images and thumbnails, `over` after them,
154 /// `overlay` after the base layer's text.
155 pub fn prepare(
156 &mut self,
157 device: &wgpu::Device,
158 queue: &wgpu::Queue,
159 layers: [&[Prim]; 3],
160 viewport: [f32; 2],
161 ) {
162 let prims: Vec<Prim> = layers.iter().flat_map(|l| l.iter()).copied().collect();
163 queue.write_buffer(
164 &self.globals,
165 0,
166 bytemuck::bytes_of(&[viewport[0], viewport[1], 0.0, 0.0]),
167 );
168 if prims.len() > self.capacity {
169 self.capacity = prims.len().next_power_of_two();
170 self.instances = instance_buffer(device, self.capacity);
171 }
172 if !prims.is_empty() {
173 queue.write_buffer(&self.instances, 0, bytemuck::cast_slice(&prims));
174 }
175 let mut end = 0;
176 for (e, l) in self.ends.iter_mut().zip(layers) {
177 end += l.len() as u32;
178 *e = end;
179 }
180 }
181
182 fn draw_range(&self, pass: &mut wgpu::RenderPass<'_>, range: std::ops::Range<u32>) {
183 if range.is_empty() {
184 return;
185 }
186 pass.set_pipeline(&self.pipeline);
187 pass.set_bind_group(0, &self.bind_group, &[]);
188 pass.set_vertex_buffer(0, self.instances.slice(..));
189 pass.draw(0..6, range);
190 }
191
192 pub fn draw_under(&self, pass: &mut wgpu::RenderPass<'_>) {
193 self.draw_range(pass, 0..self.ends[0]);
194 }
195
196 pub fn draw_over(&self, pass: &mut wgpu::RenderPass<'_>) {
197 self.draw_range(pass, self.ends[0]..self.ends[1]);
198 }
199
200 pub fn draw_overlay(&self, pass: &mut wgpu::RenderPass<'_>) {
201 self.draw_range(pass, self.ends[1]..self.ends[2]);
202 }
203}