1use cvkg_core::Rect;
35use std::sync::Arc;
36
37pub use accesskit::{
40 ActionHandler, ActionRequest, ActivationHandler, DeactivationHandler,
41 Node, NodeId, Role, Tree, TreeId, TreeUpdate,
42};
43pub use accesskit_winit::Adapter as ShieldWallAdapter;
44
45
46#[repr(C)]
47#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)]
48pub struct Vertex {
49 pub position: [f32; 2],
50 pub uv: [f32; 2],
51 pub color: [f32; 4],
52 pub mode: u32,
53}
54
55impl Vertex {
56 const ATTRIBUTES: [wgpu::VertexAttribute; 4] = wgpu::vertex_attr_array![
57 0 => Float32x2,
58 1 => Float32x2,
59 2 => Float32x4,
60 3 => Uint32
61 ];
62
63 fn desc() -> wgpu::VertexBufferLayout<'static> {
64 wgpu::VertexBufferLayout {
65 array_stride: std::mem::size_of::<Vertex>() as wgpu::BufferAddress,
66 step_mode: wgpu::VertexStepMode::Vertex,
67 attributes: &Self::ATTRIBUTES,
68 }
69 }
70}
71
72pub struct SurtrRenderer {
74 device: Arc<wgpu::Device>,
75 queue: Arc<wgpu::Queue>,
76 surface: wgpu::Surface<'static>,
77 config: wgpu::SurfaceConfiguration,
78 pipeline: wgpu::RenderPipeline,
79
80 #[allow(dead_code)]
82 bloom_extract_pipeline: wgpu::RenderPipeline,
83 blur_h_pipeline: wgpu::RenderPipeline,
84 blur_v_pipeline: wgpu::RenderPipeline,
85 composite_pipeline: wgpu::RenderPipeline,
86 blur_texture_a: wgpu::TextureView,
87 blur_texture_b: wgpu::TextureView,
88 blur_bind_group_a: wgpu::BindGroup,
89 blur_bind_group_b: wgpu::BindGroup,
90
91 #[allow(dead_code)]
93 font_system: cosmic_text::FontSystem,
94 #[allow(dead_code)]
95 swash_cache: cosmic_text::SwashCache,
96
97 dummy_bind_group: wgpu::BindGroup,
99
100 vertex_buffer: wgpu::Buffer,
102 index_buffer: wgpu::Buffer,
103 vertices: Vec<Vertex>,
104 indices: Vec<u16>,
105
106 opacity_stack: Vec<f32>,
108 clip_stack: Vec<Rect>,
110}
111
112const MAX_VERTICES: usize = 10000;
113const MAX_INDICES: usize = 15000;
114
115impl SurtrRenderer {
116 pub async fn forge(window: Arc<winit::window::Window>) -> Self {
118 let instance = wgpu::Instance::default();
119 let surface = instance.create_surface(window.clone()).unwrap();
120 let adapter = instance.request_adapter(&wgpu::RequestAdapterOptions {
121 power_preference: wgpu::PowerPreference::HighPerformance,
122 compatible_surface: Some(&surface),
123 force_fallback_adapter: false,
124 }).await.expect("Failed to find a suitable GPU for Surtr");
125
126 let (device, queue) = adapter.request_device(
127 &wgpu::DeviceDescriptor {
128 label: Some("Surtr Forge"),
129 required_features: wgpu::Features::empty(),
130 required_limits: wgpu::Limits::default(),
131 },
132 None,
133 ).await.expect("Failed to create Surtr device");
134
135 let device = Arc::new(device);
136 let queue = Arc::new(queue);
137
138 let size = window.inner_size();
139 let config = surface.get_default_config(&adapter, size.width, size.height).unwrap();
140 surface.configure(&device, &config);
141
142 let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
144 label: Some("Muspelheim Main Shader"),
145 source: wgpu::ShaderSource::Wgsl(include_str!("shaders.wgsl").into()),
146 });
147
148 let texture_bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
150 entries: &[
151 wgpu::BindGroupLayoutEntry {
152 binding: 0,
153 visibility: wgpu::ShaderStages::FRAGMENT,
154 ty: wgpu::BindingType::Texture {
155 multisampled: false,
156 view_dimension: wgpu::TextureViewDimension::D2,
157 sample_type: wgpu::TextureSampleType::Float { filterable: true },
158 },
159 count: None,
160 },
161 wgpu::BindGroupLayoutEntry {
162 binding: 1,
163 visibility: wgpu::ShaderStages::FRAGMENT,
164 ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
165 count: None,
166 },
167 ],
168 label: Some("Niflheim Texture Bind Group Layout"),
169 });
170
171 let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
173 label: Some("Surtr Pipeline Layout"),
174 bind_group_layouts: &[&texture_bind_group_layout],
175 push_constant_ranges: &[],
176 });
177
178 let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
179 label: Some("Surtr Main Pipeline"),
180 layout: Some(&pipeline_layout),
181 vertex: wgpu::VertexState {
182 module: &shader,
183 entry_point: "vs_main",
184 buffers: &[Vertex::desc()],
185 },
186 fragment: Some(wgpu::FragmentState {
187 module: &shader,
188 entry_point: "fs_main",
189 targets: &[Some(wgpu::ColorTargetState {
190 format: config.format,
191 blend: Some(wgpu::BlendState::ALPHA_BLENDING),
192 write_mask: wgpu::ColorWrites::ALL,
193 })],
194 }),
195 primitive: wgpu::PrimitiveState::default(),
196 depth_stencil: None,
197 multisample: wgpu::MultisampleState::default(),
198 multiview: None,
199 });
200
201 let bloom_extract_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
203 label: Some("Muspelheim Bloom Extract"),
204 layout: Some(&pipeline_layout),
205 vertex: wgpu::VertexState {
206 module: &shader,
207 entry_point: "vs_fullscreen",
208 buffers: &[],
209 },
210 fragment: Some(wgpu::FragmentState {
211 module: &shader,
212 entry_point: "fs_bloom_extract",
213 targets: &[Some(wgpu::ColorTargetState {
214 format: config.format,
215 blend: None,
216 write_mask: wgpu::ColorWrites::ALL,
217 })],
218 }),
219 primitive: wgpu::PrimitiveState::default(),
220 depth_stencil: None,
221 multisample: wgpu::MultisampleState::default(),
222 multiview: None,
223 });
224
225 let blur_h_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
227 label: Some("Muspelheim Horizontal Blur"),
228 layout: Some(&pipeline_layout),
229 vertex: wgpu::VertexState {
230 module: &shader,
231 entry_point: "vs_fullscreen",
232 buffers: &[],
233 },
234 fragment: Some(wgpu::FragmentState {
235 module: &shader,
236 entry_point: "fs_blur_h",
237 targets: &[Some(wgpu::ColorTargetState {
238 format: config.format,
239 blend: Some(wgpu::BlendState::ALPHA_BLENDING),
240 write_mask: wgpu::ColorWrites::ALL,
241 })],
242 }),
243 primitive: wgpu::PrimitiveState::default(),
244 depth_stencil: None,
245 multisample: wgpu::MultisampleState::default(),
246 multiview: None,
247 });
248
249 let blur_v_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
250 label: Some("Muspelheim Vertical Blur"),
251 layout: Some(&pipeline_layout),
252 vertex: wgpu::VertexState {
253 module: &shader,
254 entry_point: "vs_fullscreen",
255 buffers: &[],
256 },
257 fragment: Some(wgpu::FragmentState {
258 module: &shader,
259 entry_point: "fs_blur_v",
260 targets: &[Some(wgpu::ColorTargetState {
261 format: config.format,
262 blend: Some(wgpu::BlendState::ALPHA_BLENDING),
263 write_mask: wgpu::ColorWrites::ALL,
264 })],
265 }),
266 primitive: wgpu::PrimitiveState::default(),
267 depth_stencil: None,
268 multisample: wgpu::MultisampleState::default(),
269 multiview: None,
270 });
271
272 let composite_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
274 label: Some("Muspelheim Composite"),
275 layout: Some(&pipeline_layout),
276 vertex: wgpu::VertexState {
277 module: &shader,
278 entry_point: "vs_fullscreen",
279 buffers: &[],
280 },
281 fragment: Some(wgpu::FragmentState {
282 module: &shader,
283 entry_point: "fs_composite",
284 targets: &[Some(wgpu::ColorTargetState {
285 format: config.format,
286 blend: Some(wgpu::BlendState {
288 color: wgpu::BlendComponent {
289 src_factor: wgpu::BlendFactor::One,
290 dst_factor: wgpu::BlendFactor::One,
291 operation: wgpu::BlendOperation::Add,
292 },
293 alpha: wgpu::BlendComponent {
294 src_factor: wgpu::BlendFactor::One,
295 dst_factor: wgpu::BlendFactor::One,
296 operation: wgpu::BlendOperation::Add,
297 },
298 }),
299 write_mask: wgpu::ColorWrites::ALL,
300 })],
301 }),
302 primitive: wgpu::PrimitiveState::default(),
303 depth_stencil: None,
304 multisample: wgpu::MultisampleState::default(),
305 multiview: None,
306 });
307
308 let blur_tex_desc = wgpu::TextureDescriptor {
310 label: Some("Muspelheim Intermediate"),
311 size: wgpu::Extent3d {
312 width: config.width,
313 height: config.height,
314 depth_or_array_layers: 1,
315 },
316 mip_level_count: 1,
317 sample_count: 1,
318 dimension: wgpu::TextureDimension::D2,
319 format: config.format,
320 usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING,
321 view_formats: &[],
322 };
323 let blur_texture_a_obj = device.create_texture(&blur_tex_desc);
324 let blur_texture_b_obj = device.create_texture(&blur_tex_desc);
325 let blur_texture_a = blur_texture_a_obj.create_view(&wgpu::TextureViewDescriptor::default());
326 let blur_texture_b = blur_texture_b_obj.create_view(&wgpu::TextureViewDescriptor::default());
327
328 let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
329 address_mode_u: wgpu::AddressMode::ClampToEdge,
330 address_mode_v: wgpu::AddressMode::ClampToEdge,
331 mag_filter: wgpu::FilterMode::Linear,
332 ..Default::default()
333 });
334
335 let blur_bind_group_a = device.create_bind_group(&wgpu::BindGroupDescriptor {
336 layout: &texture_bind_group_layout,
337 entries: &[
338 wgpu::BindGroupEntry { binding: 0, resource: wgpu::BindingResource::TextureView(&blur_texture_a) },
339 wgpu::BindGroupEntry { binding: 1, resource: wgpu::BindingResource::Sampler(&sampler) },
340 ],
341 label: Some("Blur Bind Group A"),
342 });
343
344 let blur_bind_group_b = device.create_bind_group(&wgpu::BindGroupDescriptor {
345 layout: &texture_bind_group_layout,
346 entries: &[
347 wgpu::BindGroupEntry { binding: 0, resource: wgpu::BindingResource::TextureView(&blur_texture_b) },
348 wgpu::BindGroupEntry { binding: 1, resource: wgpu::BindingResource::Sampler(&sampler) },
349 ],
350 label: Some("Blur Bind Group B"),
351 });
352
353 let dummy_size = wgpu::Extent3d {
355 width: 1,
356 height: 1,
357 depth_or_array_layers: 1,
358 };
359 let dummy_texture = device.create_texture(&wgpu::TextureDescriptor {
360 label: Some("Niflheim Dummy Texture"),
361 size: dummy_size,
362 mip_level_count: 1,
363 sample_count: 1,
364 dimension: wgpu::TextureDimension::D2,
365 format: wgpu::TextureFormat::Rgba8UnormSrgb,
366 usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
367 view_formats: &[],
368 });
369 queue.write_texture(
370 wgpu::ImageCopyTexture {
371 texture: &dummy_texture,
372 mip_level: 0,
373 origin: wgpu::Origin3d::ZERO,
374 aspect: wgpu::TextureAspect::All,
375 },
376 &[255, 255, 255, 255],
377 wgpu::ImageDataLayout {
378 offset: 0,
379 bytes_per_row: Some(4),
380 rows_per_image: Some(1),
381 },
382 dummy_size,
383 );
384
385 let dummy_view = dummy_texture.create_view(&wgpu::TextureViewDescriptor::default());
386 let dummy_sampler = device.create_sampler(&wgpu::SamplerDescriptor {
387 address_mode_u: wgpu::AddressMode::ClampToEdge,
388 address_mode_v: wgpu::AddressMode::ClampToEdge,
389 address_mode_w: wgpu::AddressMode::ClampToEdge,
390 mag_filter: wgpu::FilterMode::Linear,
391 min_filter: wgpu::FilterMode::Nearest,
392 mipmap_filter: wgpu::FilterMode::Nearest,
393 ..Default::default()
394 });
395
396 let dummy_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
397 layout: &texture_bind_group_layout,
398 entries: &[
399 wgpu::BindGroupEntry {
400 binding: 0,
401 resource: wgpu::BindingResource::TextureView(&dummy_view),
402 },
403 wgpu::BindGroupEntry {
404 binding: 1,
405 resource: wgpu::BindingResource::Sampler(&dummy_sampler),
406 },
407 ],
408 label: Some("Niflheim Dummy Bind Group"),
409 });
410
411 let vertex_buffer = device.create_buffer(&wgpu::BufferDescriptor {
413 label: Some("Surtr Vertex Anvil"),
414 size: (MAX_VERTICES * std::mem::size_of::<Vertex>()) as u64,
415 usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
416 mapped_at_creation: false,
417 });
418
419 let index_buffer = device.create_buffer(&wgpu::BufferDescriptor {
420 label: Some("Surtr Index Anvil"),
421 size: (MAX_INDICES * std::mem::size_of::<u16>()) as u64,
422 usage: wgpu::BufferUsages::INDEX | wgpu::BufferUsages::COPY_DST,
423 mapped_at_creation: false,
424 });
425
426
427 Self {
428 device,
429 queue,
430 surface,
431 config,
432 pipeline,
433 bloom_extract_pipeline,
434 blur_h_pipeline,
435 blur_v_pipeline,
436 composite_pipeline,
437 blur_texture_a,
438 blur_texture_b,
439 blur_bind_group_a,
440 blur_bind_group_b,
441 font_system: cosmic_text::FontSystem::new(),
442 swash_cache: cosmic_text::SwashCache::new(),
443 dummy_bind_group,
444 vertex_buffer,
445 index_buffer,
446 vertices: Vec::with_capacity(MAX_VERTICES),
447 indices: Vec::with_capacity(MAX_INDICES),
448 opacity_stack: Vec::new(),
449 clip_stack: Vec::new(),
450 }
451 }
452
453 pub fn begin_frame(&mut self) -> wgpu::CommandEncoder {
455 self.vertices.clear();
456 self.indices.clear();
457 self.device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
458 label: Some("Surtr's Flaming Sword"),
459 })
460 }
461
462 pub fn end_frame(&mut self, mut encoder: wgpu::CommandEncoder) {
464 self.queue.write_buffer(&self.vertex_buffer, 0, bytemuck::cast_slice(&self.vertices));
465 self.queue.write_buffer(&self.index_buffer, 0, bytemuck::cast_slice(&self.indices));
466
467 let frame = self.surface.get_current_texture()
468 .expect("Surtr: failed to acquire surface texture");
469 let screen = frame.texture.create_view(&wgpu::TextureViewDescriptor::default());
470
471 {
473 let mut p = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
474 label: Some("Surtr P1 Base"),
475 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
476 view: &screen,
477 resolve_target: None,
478 ops: wgpu::Operations {
479 load: wgpu::LoadOp::Clear(wgpu::Color { r: 0.0, g: 0.0, b: 0.0, a: 1.0 }), store: wgpu::StoreOp::Store,
481 },
482 })],
483 depth_stencil_attachment: None,
484 occlusion_query_set: None,
485 timestamp_writes: None,
486 });
487 if !self.indices.is_empty() {
488 p.set_pipeline(&self.pipeline);
489 p.set_bind_group(0, &self.dummy_bind_group, &[]);
490 p.set_vertex_buffer(0, self.vertex_buffer.slice(..));
491 p.set_index_buffer(self.index_buffer.slice(..), wgpu::IndexFormat::Uint16);
492 p.draw_indexed(0..self.indices.len() as u32, 0, 0..1);
493 }
494 }
495
496 {
498 let mut p = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
499 label: Some("Surtr P2 Bloom Src"),
500 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
501 view: &self.blur_texture_a,
502 resolve_target: None,
503 ops: wgpu::Operations {
504 load: wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT),
505 store: wgpu::StoreOp::Store,
506 },
507 })],
508 depth_stencil_attachment: None,
509 occlusion_query_set: None,
510 timestamp_writes: None,
511 });
512 if !self.indices.is_empty() {
513 p.set_pipeline(&self.pipeline);
514 p.set_bind_group(0, &self.dummy_bind_group, &[]);
515 p.set_vertex_buffer(0, self.vertex_buffer.slice(..));
516 p.set_index_buffer(self.index_buffer.slice(..), wgpu::IndexFormat::Uint16);
517 p.draw_indexed(0..self.indices.len() as u32, 0, 0..1);
518 }
519 }
520
521 let blur_iters: u32 = 6;
523 for i in 0..blur_iters {
524 {
525 let label = format!("Surtr Blur H iter {}", i);
526 let mut p = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
527 label: Some(&label),
528 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
529 view: &self.blur_texture_b,
530 resolve_target: None,
531 ops: wgpu::Operations {
532 load: wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT),
533 store: wgpu::StoreOp::Store,
534 },
535 })],
536 depth_stencil_attachment: None,
537 occlusion_query_set: None,
538 timestamp_writes: None,
539 });
540 p.set_pipeline(&self.blur_h_pipeline);
541 p.set_bind_group(0, &self.blur_bind_group_a, &[]);
542 p.draw(0..3, 0..1);
543 }
544 {
545 let label = format!("Surtr Blur V iter {}", i);
546 let mut p = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
547 label: Some(&label),
548 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
549 view: &self.blur_texture_a,
550 resolve_target: None,
551 ops: wgpu::Operations {
552 load: wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT),
553 store: wgpu::StoreOp::Store,
554 },
555 })],
556 depth_stencil_attachment: None,
557 occlusion_query_set: None,
558 timestamp_writes: None,
559 });
560 p.set_pipeline(&self.blur_v_pipeline);
561 p.set_bind_group(0, &self.blur_bind_group_b, &[]);
562 p.draw(0..3, 0..1);
563 }
564 }
565
566 {
568 let mut p = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
569 label: Some("Surtr P7 Composite"),
570 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
571 view: &screen,
572 resolve_target: None,
573 ops: wgpu::Operations {
574 load: wgpu::LoadOp::Load,
575 store: wgpu::StoreOp::Store,
576 },
577 })],
578 depth_stencil_attachment: None,
579 occlusion_query_set: None,
580 timestamp_writes: None,
581 });
582 p.set_pipeline(&self.composite_pipeline);
583 p.set_bind_group(0, &self.blur_bind_group_a, &[]);
584 p.draw(0..3, 0..1);
585 }
586
587 self.queue.submit(Some(encoder.finish()));
588 frame.present();
589 }
590}
591
592impl cvkg_core::Renderer for SurtrRenderer {
593 fn fill_rect(&mut self, rect: Rect, color: [f32; 4]) {
594 self.fill_rect_with_mode(rect, self.apply_opacity(color), 0);
595 }
596
597 fn fill_rounded_rect(&mut self, rect: Rect, _radius: f32, color: [f32; 4]) {
598 self.fill_rect_with_mode(rect, self.apply_opacity(color), 0);
601 }
602
603 fn fill_ellipse(&mut self, rect: Rect, color: [f32; 4]) {
604 self.fill_rect_with_mode(rect, self.apply_opacity(color), 0);
606 }
607
608 fn stroke_rect(&mut self, rect: Rect, color: [f32; 4], stroke_width: f32) {
609 let c = self.apply_opacity(color);
610 let hw = stroke_width;
611 self.fill_rect_with_mode(Rect { x: rect.x, y: rect.y, width: rect.width, height: hw }, c, 1);
613 self.fill_rect_with_mode(Rect { x: rect.x, y: rect.y + rect.height - hw, width: rect.width, height: hw }, c, 1);
614 self.fill_rect_with_mode(Rect { x: rect.x, y: rect.y, width: hw, height: rect.height }, c, 1);
615 self.fill_rect_with_mode(Rect { x: rect.x + rect.width - hw, y: rect.y, width: hw, height: rect.height }, c, 1);
616 }
617
618 fn stroke_rounded_rect(&mut self, rect: Rect, _radius: f32, color: [f32; 4], stroke_width: f32) {
619 self.stroke_rect(rect, color, stroke_width);
621 }
622
623 fn stroke_ellipse(&mut self, rect: Rect, color: [f32; 4], stroke_width: f32) {
624 self.stroke_rect(rect, color, stroke_width);
625 }
626
627 fn draw_line(&mut self, x1: f32, y1: f32, x2: f32, y2: f32, color: [f32; 4], stroke_width: f32) {
628 let dx = x2 - x1;
630 let dy = y2 - y1;
631 let len = (dx * dx + dy * dy).sqrt();
632 if len < 0.001 { return; }
633 let c = self.apply_opacity(color);
634 let min_x = x1.min(x2);
636 let min_y = y1.min(y2);
637 let w = (dx.abs()).max(stroke_width);
638 let h = (dy.abs()).max(stroke_width);
639 self.fill_rect_with_mode(Rect { x: min_x, y: min_y, width: w, height: h }, c, 1);
640 }
641
642 fn draw_text(&mut self, _text: &str, x: f32, y: f32, size: f32, color: [f32; 4]) {
643 let c = self.apply_opacity(color);
646 self.fill_rect_with_mode(Rect { x, y, width: size * 5.0, height: size }, c, 0);
647 }
648
649 fn draw_texture(&mut self, _texture_id: u32, rect: Rect) {
650 self.fill_rect_with_mode(rect, [1.0, 1.0, 1.0, 1.0], 0);
651 }
652
653 fn draw_image(&mut self, _image_name: &str, rect: Rect) {
654 self.fill_rect_with_mode(rect, [1.0, 1.0, 1.0, 1.0], 0);
655 }
656
657 fn push_clip_rect(&mut self, rect: Rect) {
658 self.clip_stack.push(rect);
660 }
661
662 fn pop_clip_rect(&mut self) {
663 self.clip_stack.pop();
664 }
665
666 fn push_opacity(&mut self, opacity: f32) {
667 let current = self.opacity_stack.last().copied().unwrap_or(1.0);
668 self.opacity_stack.push(current * opacity);
669 }
670
671 fn pop_opacity(&mut self) {
672 self.opacity_stack.pop();
673 }
674}
675
676impl cvkg_core::FrameRenderer<wgpu::CommandEncoder> for SurtrRenderer {
677 fn begin_frame(&mut self) -> wgpu::CommandEncoder {
678 self.begin_frame()
679 }
680
681 fn end_frame(&mut self, encoder: wgpu::CommandEncoder) {
682 self.end_frame(encoder)
683 }
684}
685
686impl SurtrRenderer {
687 fn apply_opacity(&self, mut color: [f32; 4]) -> [f32; 4] {
689 if let Some(&alpha) = self.opacity_stack.last() {
690 color[3] *= alpha;
691 }
692 color
693 }
694
695 pub fn fill_rect_with_mode(&mut self, rect: Rect, color: [f32; 4], mode: u32) {
698 let base_idx = self.vertices.len() as u16;
699
700 let half_w = self.config.width as f32 / 2.0;
702 let half_h = self.config.height as f32 / 2.0;
703
704 let x1 = (rect.x / half_w) - 1.0;
705 let y1 = 1.0 - (rect.y / half_h);
706 let x2 = ((rect.x + rect.width) / half_w) - 1.0;
707 let y2 = 1.0 - ((rect.y + rect.height) / half_h);
708
709 self.vertices.push(Vertex { position: [x1, y1], uv: [0.0, 0.0], color, mode });
710 self.vertices.push(Vertex { position: [x2, y1], uv: [1.0, 0.0], color, mode });
711 self.vertices.push(Vertex { position: [x2, y2], uv: [1.0, 1.0], color, mode });
712 self.vertices.push(Vertex { position: [x1, y2], uv: [0.0, 1.0], color, mode });
713
714 self.indices.extend_from_slice(&[
715 base_idx, base_idx + 1, base_idx + 2,
716 base_idx, base_idx + 2, base_idx + 3,
717 ]);
718 }
719}