Krolyxon
Commit e3295f9

Branches

Tags

Refs →

remagesrcrenderprims.rs

198 lines · 6.6 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 layer, then the over layer.
64 split: u32,
65 count: u32,
66}
67
68const ATTRS: [wgpu::VertexAttribute; 4] =
69 wgpu::vertex_attr_array![0 => Float32x4, 1 => Float32x4, 2 => Float32x4, 3 => Float32x4];
70
71fn instance_buffer(device: &wgpu::Device, capacity: usize) -> wgpu::Buffer {
72 device.create_buffer(&wgpu::BufferDescriptor {
73 label: Some("prims"),
74 size: (capacity * size_of::<Prim>()) as u64,
75 usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
76 mapped_at_creation: false,
77 })
78}
79
80impl PrimPipeline {
81 pub fn new(device: &wgpu::Device, target: wgpu::TextureFormat) -> Self {
82 let globals = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
83 label: Some("prim globals"),
84 contents: bytemuck::bytes_of(&[0.0f32; 4]),
85 usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
86 });
87 let layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
88 label: Some("prims"),
89 entries: &[wgpu::BindGroupLayoutEntry {
90 binding: 0,
91 visibility: wgpu::ShaderStages::VERTEX,
92 ty: wgpu::BindingType::Buffer {
93 ty: wgpu::BufferBindingType::Uniform,
94 has_dynamic_offset: false,
95 min_binding_size: None,
96 },
97 count: None,
98 }],
99 });
100 let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
101 label: Some("prims"),
102 layout: &layout,
103 entries: &[wgpu::BindGroupEntry {
104 binding: 0,
105 resource: globals.as_entire_binding(),
106 }],
107 });
108 let shader = device.create_shader_module(wgpu::include_wgsl!("shaders/prims.wgsl"));
109 let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
110 label: Some("prims"),
111 bind_group_layouts: &[Some(&layout)],
112 immediate_size: 0,
113 });
114 let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
115 label: Some("prims"),
116 layout: Some(&pipeline_layout),
117 vertex: wgpu::VertexState {
118 module: &shader,
119 entry_point: Some("vs"),
120 compilation_options: Default::default(),
121 buffers: &[Some(wgpu::VertexBufferLayout {
122 array_stride: size_of::<Prim>() as u64,
123 step_mode: wgpu::VertexStepMode::Instance,
124 attributes: &ATTRS,
125 })],
126 },
127 primitive: wgpu::PrimitiveState::default(),
128 depth_stencil: None,
129 multisample: wgpu::MultisampleState::default(),
130 fragment: Some(wgpu::FragmentState {
131 module: &shader,
132 entry_point: Some("fs"),
133 compilation_options: Default::default(),
134 targets: &[Some(wgpu::ColorTargetState {
135 format: target,
136 blend: Some(wgpu::BlendState::PREMULTIPLIED_ALPHA_BLENDING),
137 write_mask: wgpu::ColorWrites::ALL,
138 })],
139 }),
140 multiview_mask: None,
141 cache: None,
142 });
143 let capacity = 64;
144 Self {
145 pipeline,
146 globals,
147 bind_group,
148 instances: instance_buffer(device, capacity),
149 capacity,
150 split: 0,
151 count: 0,
152 }
153 }
154
155 /// Upload both layers: `under` draws before images and thumbnails, `over` after.
156 pub fn prepare(
157 &mut self,
158 device: &wgpu::Device,
159 queue: &wgpu::Queue,
160 under: &[Prim],
161 over: &[Prim],
162 viewport: [f32; 2],
163 ) {
164 let prims: Vec<Prim> = under.iter().chain(over).copied().collect();
165 queue.write_buffer(
166 &self.globals,
167 0,
168 bytemuck::bytes_of(&[viewport[0], viewport[1], 0.0, 0.0]),
169 );
170 if prims.len() > self.capacity {
171 self.capacity = prims.len().next_power_of_two();
172 self.instances = instance_buffer(device, self.capacity);
173 }
174 if !prims.is_empty() {
175 queue.write_buffer(&self.instances, 0, bytemuck::cast_slice(&prims));
176 }
177 self.split = under.len() as u32;
178 self.count = prims.len() as u32;
179 }
180
181 fn draw_range(&self, pass: &mut wgpu::RenderPass<'_>, range: std::ops::Range<u32>) {
182 if range.is_empty() {
183 return;
184 }
185 pass.set_pipeline(&self.pipeline);
186 pass.set_bind_group(0, &self.bind_group, &[]);
187 pass.set_vertex_buffer(0, self.instances.slice(..));
188 pass.draw(0..6, range);
189 }
190
191 pub fn draw_under(&self, pass: &mut wgpu::RenderPass<'_>) {
192 self.draw_range(pass, 0..self.split);
193 }
194
195 pub fn draw_over(&self, pass: &mut wgpu::RenderPass<'_>) {
196 self.draw_range(pass, self.split..self.count);
197 }
198}