stevenkhan commited on
Commit
7077890
·
verified ·
1 Parent(s): 4729538

Upload spectral-render/src/renderer.rs

Browse files
Files changed (1) hide show
  1. spectral-render/src/renderer.rs +385 -0
spectral-render/src/renderer.rs ADDED
@@ -0,0 +1,385 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ use std::sync::Arc;
2
+
3
+ use wgpu;
4
+ use bytemuck::{Pod, Zeroable};
5
+
6
+ use spectral_core::Terminal;
7
+ use spectral_font::{GlyphAtlas, GlyphKey};
8
+
9
+ use crate::shaders::{BG_VERTEX, BG_FRAGMENT, TEXT_VERTEX, TEXT_FRAGMENT};
10
+
11
+ #[repr(C)]
12
+ #[derive(Clone, Copy, Debug, Pod, Zeroable)]
13
+ pub struct BgInstance {
14
+ pub col: u32, pub row: u32,
15
+ pub bg_r: u32, pub bg_g: u32, pub bg_b: u32, pub bg_a: u32,
16
+ }
17
+
18
+ #[repr(C)]
19
+ #[derive(Clone, Copy, Debug, Pod, Zeroable)]
20
+ pub struct TextInstance {
21
+ pub col: f32, pub row: f32,
22
+ pub bearing_x: f32, pub bearing_y: f32,
23
+ pub glyph_w: f32, pub glyph_h: f32,
24
+ pub atlas_x: f32, pub atlas_y: f32,
25
+ pub atlas_w: f32, pub atlas_h: f32,
26
+ pub fg_r: u32, pub fg_g: u32, pub fg_b: u32,
27
+ pub flags: u32, pub thickening: f32,
28
+ }
29
+
30
+ #[repr(C)]
31
+ #[derive(Clone, Copy, Debug, Pod, Zeroable)]
32
+ pub struct Uniforms {
33
+ pub cell_width: f32, pub cell_height: f32, pub atlas_size: f32,
34
+ pub screen_cols: u32, pub screen_rows: u32,
35
+ _pad: u32, _pad2: u32,
36
+ }
37
+
38
+ pub struct Renderer {
39
+ pub device: wgpu::Device,
40
+ pub queue: wgpu::Queue,
41
+ pub surface: wgpu::Surface<'static>,
42
+ pub surface_config: wgpu::SurfaceConfiguration,
43
+ pub bg_pipeline: wgpu::RenderPipeline,
44
+ pub text_pipeline: wgpu::RenderPipeline,
45
+ pub bg_instance_buf: wgpu::Buffer,
46
+ pub text_instance_buf: wgpu::Buffer,
47
+ pub uniform_buf: wgpu::Buffer,
48
+ pub atlas_texture: wgpu::Texture,
49
+ pub atlas_view: wgpu::TextureView,
50
+ pub atlas_sampler: wgpu::Sampler,
51
+ pub bg_bind_group: wgpu::BindGroup,
52
+ pub text_uniform_bind_group: wgpu::BindGroup,
53
+ pub text_texture_bind_group: wgpu::BindGroup,
54
+ pub glyph_atlas: GlyphAtlas,
55
+ pub bg_instances: Vec<BgInstance>,
56
+ pub text_instances: Vec<TextInstance>,
57
+ pub needs_atlas_upload: bool,
58
+ }
59
+
60
+ impl Renderer {
61
+ pub async fn new(window: Arc<winit::window::Window>, term_cols: u32, term_rows: u32, atlas_size: u16) -> Self {
62
+ let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
63
+ backends: wgpu::Backends::all(), ..Default::default()
64
+ });
65
+ let surface = instance.create_surface(window).unwrap();
66
+ let adapter = instance.request_adapter(&wgpu::RequestAdapterOptions {
67
+ power_preference: wgpu::PowerPreference::HighPerformance,
68
+ compatible_surface: Some(&surface), force_fallback_adapter: false,
69
+ }).await.unwrap();
70
+
71
+ let (device, queue) = adapter.request_device(&wgpu::DeviceDescriptor {
72
+ required_features: wgpu::Features::empty(),
73
+ required_limits: wgpu::Limits::default(),
74
+ label: Some("Spectral Device"),
75
+ memory_hints: wgpu::MemoryHints::Performance,
76
+ }, None).await.unwrap();
77
+
78
+ let surface_caps = surface.get_capabilities(&adapter);
79
+ let surface_format = surface_caps.formats.iter().copied()
80
+ .find(|f| f.is_srgb()).unwrap_or(surface_caps.formats[0]);
81
+
82
+ let config = wgpu::SurfaceConfiguration {
83
+ usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
84
+ format: surface_format, width: 1280, height: 720,
85
+ present_mode: wgpu::PresentMode::AutoNoVsync,
86
+ desired_maximum_frame_latency: 1,
87
+ alpha_mode: wgpu::CompositeAlphaMode::Auto,
88
+ view_formats: vec![],
89
+ };
90
+ surface.configure(&device, &config);
91
+
92
+ let glyph_atlas = GlyphAtlas::new(atlas_size);
93
+ let max_instances = (term_cols * term_rows) as usize;
94
+ let atlas_size_px = 2048u32;
95
+
96
+ let bg_instance_buf = device.create_buffer(&wgpu::BufferDescriptor {
97
+ label: Some("bg_instances"),
98
+ size: (max_instances * std::mem::size_of::<BgInstance>()) as u64,
99
+ usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
100
+ mapped_at_creation: false,
101
+ });
102
+
103
+ let text_instance_buf = device.create_buffer(&wgpu::BufferDescriptor {
104
+ label: Some("text_instances"),
105
+ size: (max_instances * std::mem::size_of::<TextInstance>()) as u64,
106
+ usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
107
+ mapped_at_creation: false,
108
+ });
109
+
110
+ let uniform_buf = device.create_buffer(&wgpu::BufferDescriptor {
111
+ label: Some("uniforms"),
112
+ size: std::mem::size_of::<Uniforms>() as u64,
113
+ usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
114
+ mapped_at_creation: false,
115
+ });
116
+
117
+ let atlas_texture = device.create_texture(&wgpu::TextureDescriptor {
118
+ label: Some("atlas_texture"),
119
+ size: wgpu::Extent3d { width: atlas_size_px, height: atlas_size_px, depth_or_array_layers: 1 },
120
+ mip_level_count: 1, sample_count: 1, dimension: wgpu::TextureDimension::D2,
121
+ format: wgpu::TextureFormat::Rgba8UnormSrgb,
122
+ usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
123
+ view_formats: &[],
124
+ });
125
+
126
+ let atlas_view = atlas_texture.create_view(&wgpu::TextureViewDescriptor::default());
127
+ let atlas_sampler = device.create_sampler(&wgpu::SamplerDescriptor {
128
+ label: Some("atlas_sampler"),
129
+ mag_filter: wgpu::FilterMode::Linear,
130
+ min_filter: wgpu::FilterMode::Linear,
131
+ ..Default::default()
132
+ });
133
+
134
+ let uniform_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
135
+ label: Some("uniform_layout"),
136
+ entries: &[
137
+ wgpu::BindGroupLayoutEntry {
138
+ binding: 0, visibility: wgpu::ShaderStages::VERTEX,
139
+ ty: wgpu::BindingType::Buffer {
140
+ ty: wgpu::BufferBindingType::Storage { read_only: true },
141
+ has_dynamic_offset: false, min_binding_size: None,
142
+ }, count: None,
143
+ },
144
+ wgpu::BindGroupLayoutEntry {
145
+ binding: 1, visibility: wgpu::ShaderStages::VERTEX,
146
+ ty: wgpu::BindingType::Buffer {
147
+ ty: wgpu::BufferBindingType::Uniform,
148
+ has_dynamic_offset: false, min_binding_size: None,
149
+ }, count: None,
150
+ },
151
+ ],
152
+ });
153
+
154
+ let text_texture_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
155
+ label: Some("text_texture_layout"),
156
+ entries: &[
157
+ wgpu::BindGroupLayoutEntry {
158
+ binding: 2, visibility: wgpu::ShaderStages::FRAGMENT,
159
+ ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
160
+ count: None,
161
+ },
162
+ wgpu::BindGroupLayoutEntry {
163
+ binding: 3, visibility: wgpu::ShaderStages::FRAGMENT,
164
+ ty: wgpu::BindingType::Texture {
165
+ sample_type: wgpu::TextureSampleType::Float { filterable: true },
166
+ view_dimension: wgpu::TextureViewDimension::D2,
167
+ multisampled: false,
168
+ }, count: None,
169
+ },
170
+ ],
171
+ });
172
+
173
+ let bg_pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
174
+ label: Some("bg_pipeline_layout"),
175
+ bind_group_layouts: &[&uniform_layout], push_constant_ranges: &[],
176
+ });
177
+
178
+ let text_pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
179
+ label: Some("text_pipeline_layout"),
180
+ bind_group_layouts: &[&uniform_layout, &text_texture_layout], push_constant_ranges: &[],
181
+ });
182
+
183
+ let bg_shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
184
+ label: Some("bg_shader"), source: wgpu::ShaderSource::Wgsl(BG_VERTEX.into()),
185
+ });
186
+ let bg_frag = device.create_shader_module(wgpu::ShaderModuleDescriptor {
187
+ label: Some("bg_frag"), source: wgpu::ShaderSource::Wgsl(BG_FRAGMENT.into()),
188
+ });
189
+ let text_shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
190
+ label: Some("text_shader"), source: wgpu::ShaderSource::Wgsl(TEXT_VERTEX.into()),
191
+ });
192
+ let text_frag = device.create_shader_module(wgpu::ShaderModuleDescriptor {
193
+ label: Some("text_frag"), source: wgpu::ShaderSource::Wgsl(TEXT_FRAGMENT.into()),
194
+ });
195
+
196
+ let bg_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
197
+ label: Some("bg_pipeline"),
198
+ layout: Some(&bg_pipeline_layout),
199
+ vertex: wgpu::VertexState {
200
+ module: &bg_shader, entry_point: Some("main"),
201
+ buffers: &[], compilation_options: wgpu::PipelineCompilationOptions::default(),
202
+ },
203
+ fragment: Some(wgpu::FragmentState {
204
+ module: &bg_frag, entry_point: Some("main"),
205
+ targets: &[Some(wgpu::ColorTargetState {
206
+ format: surface_format, blend: Some(wgpu::BlendState::REPLACE),
207
+ write_mask: wgpu::ColorWrites::ALL,
208
+ })],
209
+ compilation_options: wgpu::PipelineCompilationOptions::default(),
210
+ }),
211
+ primitive: wgpu::PrimitiveState::default(), depth_stencil: None,
212
+ multisample: wgpu::MultisampleState::default(), multiview: None, cache: None,
213
+ });
214
+
215
+ let text_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
216
+ label: Some("text_pipeline"),
217
+ layout: Some(&text_pipeline_layout),
218
+ vertex: wgpu::VertexState {
219
+ module: &text_shader, entry_point: Some("main"),
220
+ buffers: &[], compilation_options: wgpu::PipelineCompilationOptions::default(),
221
+ },
222
+ fragment: Some(wgpu::FragmentState {
223
+ module: &text_frag, entry_point: Some("main"),
224
+ targets: &[Some(wgpu::ColorTargetState {
225
+ format: surface_format, blend: Some(wgpu::BlendState::ALPHA_BLENDING),
226
+ write_mask: wgpu::ColorWrites::ALL,
227
+ })],
228
+ compilation_options: wgpu::PipelineCompilationOptions::default(),
229
+ }),
230
+ primitive: wgpu::PrimitiveState::default(), depth_stencil: None,
231
+ multisample: wgpu::MultisampleState::default(), multiview: None, cache: None,
232
+ });
233
+
234
+ let bg_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
235
+ label: Some("bg_bind_group"), layout: &uniform_layout,
236
+ entries: &[
237
+ wgpu::BindGroupEntry { binding: 0, resource: bg_instance_buf.as_entire_binding() },
238
+ wgpu::BindGroupEntry { binding: 1, resource: uniform_buf.as_entire_binding() },
239
+ ],
240
+ });
241
+
242
+ let text_uniform_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
243
+ label: Some("text_uniform_bind_group"), layout: &uniform_layout,
244
+ entries: &[
245
+ wgpu::BindGroupEntry { binding: 0, resource: text_instance_buf.as_entire_binding() },
246
+ wgpu::BindGroupEntry { binding: 1, resource: uniform_buf.as_entire_binding() },
247
+ ],
248
+ });
249
+
250
+ let text_texture_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
251
+ label: Some("text_texture_bind_group"), layout: &text_texture_layout,
252
+ entries: &[
253
+ wgpu::BindGroupEntry { binding: 2, resource: wgpu::BindingResource::Sampler(&atlas_sampler) },
254
+ wgpu::BindGroupEntry { binding: 3, resource: wgpu::BindingResource::TextureView(&atlas_view) },
255
+ ],
256
+ });
257
+
258
+ Self {
259
+ device, queue, surface, surface_config: config,
260
+ bg_pipeline, text_pipeline,
261
+ bg_instance_buf, text_instance_buf, uniform_buf,
262
+ atlas_texture, atlas_view, atlas_sampler,
263
+ bg_bind_group, text_uniform_bind_group, text_texture_bind_group,
264
+ glyph_atlas,
265
+ bg_instances: Vec::with_capacity(max_instances),
266
+ text_instances: Vec::with_capacity(max_instances),
267
+ needs_atlas_upload: false,
268
+ }
269
+ }
270
+
271
+ pub fn resize(&mut self, width: u32, height: u32) {
272
+ if width == 0 || height == 0 { return; }
273
+ self.surface_config.width = width;
274
+ self.surface_config.height = height;
275
+ self.surface.configure(&self.device, &self.surface_config);
276
+ }
277
+
278
+ pub fn update_atlas(&mut self) {
279
+ if !self.needs_atlas_upload { return; }
280
+ for (page_idx, page_data) in self.glyph_atlas.texture_data.iter().enumerate() {
281
+ let page_size = self.glyph_atlas.page_size as u32;
282
+ self.queue.write_texture(
283
+ wgpu::ImageCopyTexture {
284
+ texture: &self.atlas_texture, mip_level: 0,
285
+ origin: wgpu::Origin3d { x: 0, y: 0, z: page_idx as u32 },
286
+ aspect: wgpu::TextureAspect::All,
287
+ },
288
+ page_data,
289
+ wgpu::ImageDataLayout { offset: 0, bytes_per_row: Some(page_size * 4), rows_per_image: Some(page_size) },
290
+ wgpu::Extent3d { width: page_size, height: page_size, depth_or_array_layers: 1 },
291
+ );
292
+ }
293
+ self.needs_atlas_upload = false;
294
+ }
295
+
296
+ pub fn update_instances(&mut self, terminal: &Terminal) {
297
+ self.bg_instances.clear();
298
+ self.text_instances.clear();
299
+ let cell_width = 8.0f32;
300
+ let cell_height = 16.0f32;
301
+ let thickening = if terminal.config.font_thicken { terminal.config.font_thicken_strength } else { 0.0 };
302
+
303
+ for (row_idx, line) in terminal.grid.lines.iter().enumerate() {
304
+ for (col_idx, cell) in line.cells.iter().enumerate() {
305
+ self.bg_instances.push(BgInstance {
306
+ col: col_idx as u32, row: row_idx as u32,
307
+ bg_r: ((cell.bg >> 16) & 0xFF) as u32,
308
+ bg_g: ((cell.bg >> 8) & 0xFF) as u32,
309
+ bg_b: (cell.bg & 0xFF) as u32,
310
+ bg_a: ((cell.bg >> 24) & 0xFF) as u32,
311
+ });
312
+ if cell.ch == ' ' || cell.ch == '\0' { continue; }
313
+ let key = GlyphKey { font_id: 0, glyph_id: cell.ch as u16, size_bucket: (cell_height * 4.0) as u16, style_flags: 0 };
314
+ if let Some((_page, slot)) = self.glyph_atlas.get(&key) {
315
+ self.text_instances.push(TextInstance {
316
+ col: col_idx as f32, row: row_idx as f32,
317
+ bearing_x: 0.0, bearing_y: 0.0,
318
+ glyph_w: slot.w as f32, glyph_h: slot.h as f32,
319
+ atlas_x: slot.x as f32, atlas_y: slot.y as f32,
320
+ atlas_w: slot.w as f32, atlas_h: slot.h as f32,
321
+ fg_r: ((cell.fg >> 16) & 0xFF) as u32,
322
+ fg_g: ((cell.fg >> 8) & 0xFF) as u32,
323
+ fg_b: (cell.fg & 0xFF) as u32,
324
+ flags: cell.flags.bits() as u32,
325
+ thickening,
326
+ });
327
+ }
328
+ }
329
+ }
330
+
331
+ if !self.bg_instances.is_empty() {
332
+ self.queue.write_buffer(&self.bg_instance_buf, 0, bytemuck::cast_slice(&self.bg_instances));
333
+ }
334
+ if !self.text_instances.is_empty() {
335
+ self.queue.write_buffer(&self.text_instance_buf, 0, bytemuck::cast_slice(&self.text_instances));
336
+ }
337
+
338
+ let uniforms = Uniforms {
339
+ cell_width, cell_height,
340
+ atlas_size: self.glyph_atlas.page_size as f32,
341
+ screen_cols: terminal.grid.cols as u32,
342
+ screen_rows: terminal.grid.rows as u32,
343
+ _pad: 0, _pad2: 0,
344
+ };
345
+ self.queue.write_buffer(&self.uniform_buf, 0, bytemuck::bytes_of(&uniforms));
346
+ }
347
+
348
+ pub fn render(&mut self) {
349
+ let output = match self.surface.get_current_texture() { Ok(t) => t, Err(_) => return };
350
+ let view = output.texture.create_view(&wgpu::TextureViewDescriptor::default());
351
+ let mut encoder = self.device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: Some("render_encoder") });
352
+
353
+ {
354
+ let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
355
+ label: Some("bg_pass"),
356
+ color_attachments: &[Some(wgpu::RenderPassColorAttachment {
357
+ view: &view, resolve_target: None,
358
+ ops: wgpu::Operations { load: wgpu::LoadOp::Clear(wgpu::Color::BLACK), store: wgpu::StoreOp::Store },
359
+ })],
360
+ depth_stencil_attachment: None, timestamp_writes: None, occlusion_query_set: None,
361
+ });
362
+ pass.set_pipeline(&self.bg_pipeline);
363
+ pass.set_bind_group(0, &self.bg_bind_group, &[]);
364
+ pass.draw(0..6, 0..self.bg_instances.len() as u32);
365
+ }
366
+
367
+ {
368
+ let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
369
+ label: Some("text_pass"),
370
+ color_attachments: &[Some(wgpu::RenderPassColorAttachment {
371
+ view: &view, resolve_target: None,
372
+ ops: wgpu::Operations { load: wgpu::LoadOp::Load, store: wgpu::StoreOp::Store },
373
+ })],
374
+ depth_stencil_attachment: None, timestamp_writes: None, occlusion_query_set: None,
375
+ });
376
+ pass.set_pipeline(&self.text_pipeline);
377
+ pass.set_bind_group(0, &self.text_uniform_bind_group, &[]);
378
+ pass.set_bind_group(1, &self.text_texture_bind_group, &[]);
379
+ pass.draw(0..6, 0..self.text_instances.len() as u32);
380
+ }
381
+
382
+ self.queue.submit(std::iter::once(encoder.finish()));
383
+ output.present();
384
+ }
385
+ }