Seregon/StratoSDK

StratoSDK is a framework with a declarative approach similar to Flutter/React, written and designed entirely for Rust.

Rust/27.3 KB/No license
crates/strato-renderer/src/backend/wgpu.rs
1use crate::backend::{commands::RenderCommand, Backend};
2use crate::gpu::{
3 BufferManager, DeviceManager, PipelineManager, ShaderManager, SimpleVertex, SurfaceManager,
4 TextureManager,
5};
6use anyhow::Result;
7use async_trait::async_trait;
8use raw_window_handle::{HasDisplayHandle, HasWindowHandle};
9use wgpu::{Backends, CommandEncoderDescriptor, Surface};
10 
11pub struct WgpuBackend {
12 device_mgr: Option<DeviceManager>,
13 surface_mgr: Option<SurfaceManager>,
14 shader_mgr: Option<ShaderManager>,
15 buffer_mgr: Option<BufferManager>,
16 texture_mgr: Option<TextureManager>,
17 pipeline_mgr: Option<PipelineManager>,
18 
19 // State
20 scale_factor: f64,
21 
22 // Cache for reuse
23 vertices: Vec<SimpleVertex>,
24 indices: Vec<u32>,
25}
26 
27impl WgpuBackend {
28 pub fn new() -> Self {
29 Self {
30 device_mgr: None,
31 surface_mgr: None,
32 shader_mgr: None,
33 buffer_mgr: None,
34 texture_mgr: None,
35 pipeline_mgr: None,
36 scale_factor: 1.0,
37 vertices: Vec::with_capacity(1024),
38 indices: Vec::with_capacity(1536),
39 }
40 }
41 
42 pub async fn init<W>(&mut self, window: &W) -> Result<()>
43 where
44 W: HasWindowHandle + HasDisplayHandle + Send + Sync,
45 {
46 println!("=== INITIALIZING WGPU BACKEND ===");
47 
48 // 1. Initialize DeviceManager
49 let device_mgr = DeviceManager::new(Backends::all()).await?;
50 println!("✅ DeviceManager initialized");
51 
52 // 2. Create Surface
53 // Safety: The surface must live as long as the window.
54 // We assume the window outlives the backend.
55 let surface = device_mgr.instance().create_surface(window)?;
56 let surface: Surface<'static> = unsafe { std::mem::transmute(surface) };
57 
58 // 3. Initialize SurfaceManager
59 // We use a default size, it will be resized later
60 let surface_mgr =
61 SurfaceManager::new(surface, device_mgr.device(), device_mgr.adapter(), 800, 600)?;
62 println!("✅ SurfaceManager initialized");
63 
64 // 4. Initialize ShaderManager
65 let shader_mgr = ShaderManager::from_wgsl(
66 device_mgr.device(),
67 include_str!("../shaders/simple.wgsl"),
68 Some("Simple Shader"),
69 )?;
70 println!("✅ ShaderManager initialized");
71 
72 // 5. Initialize BufferManager
73 let mut buffer_mgr = BufferManager::new(device_mgr.device());
74 println!("✅ BufferManager initialized");
75 
76 // 6. Initialize TextureManager
77 let texture_mgr = TextureManager::new_with_font(device_mgr.device(), device_mgr.queue());
78 println!("✅ TextureManager initialized");
79 
80 // 7. Initialize PipelineManager
81 let pipeline_mgr = PipelineManager::new(
82 device_mgr.device(),
83 &shader_mgr,
84 &buffer_mgr,
85 &texture_mgr,
86 surface_mgr.format(),
87 )?;
88 println!("✅ PipelineManager initialized");
89 
90 // Initialize projection matrix
91 let width = surface_mgr.width();
92 let height = surface_mgr.height();
93 let projection =
94 glam::Mat4::orthographic_rh(0.0, width as f32, height as f32, 0.0, -1.0, 1.0);
95 buffer_mgr.upload_projection(device_mgr.queue(), &projection.to_cols_array_2d());
96 
97 self.device_mgr = Some(device_mgr);
98 self.surface_mgr = Some(surface_mgr);
99 self.shader_mgr = Some(shader_mgr);
100 self.buffer_mgr = Some(buffer_mgr);
101 self.texture_mgr = Some(texture_mgr);
102 self.pipeline_mgr = Some(pipeline_mgr);
103 
104 Ok(())
105 }
106}
107 
108#[async_trait]
109impl Backend for WgpuBackend {
110 fn resize(&mut self, width: u32, height: u32) {
111 if let (Some(surface_mgr), Some(device_mgr), Some(buffer_mgr)) = (
112 &mut self.surface_mgr,
113 &self.device_mgr,
114 &mut self.buffer_mgr,
115 ) {
116 if let Err(e) = surface_mgr.resize(width, height, device_mgr.device()) {
117 eprintln!("Failed to resize surface: {}", e);
118 }
119 
120 // Update projection matrix using logical coordinates
121 // This ensures that the UI coordinates (which are logical) map correctly to the physical viewport
122 let logical_width = width as f64 / self.scale_factor;
123 let logical_height = height as f64 / self.scale_factor;
124 
125 let projection = glam::Mat4::orthographic_rh(
126 0.0,
127 logical_width as f32,
128 logical_height as f32,
129 0.0,
130 -1.0,
131 1.0,
132 );
133 buffer_mgr.upload_projection(device_mgr.queue(), &projection.to_cols_array_2d());
134 }
135 }
136 
137 fn set_scale_factor(&mut self, scale_factor: f64) {
138 self.scale_factor = scale_factor;
139 
140 // Update projection matrix if initialized
141 if let (Some(surface_mgr), Some(device_mgr), Some(buffer_mgr)) =
142 (&self.surface_mgr, &self.device_mgr, &mut self.buffer_mgr)
143 {
144 let width = surface_mgr.width();
145 let height = surface_mgr.height();
146 
147 let logical_width = width as f64 / self.scale_factor;
148 let logical_height = height as f64 / self.scale_factor;
149 
150 let projection = glam::Mat4::orthographic_rh(
151 0.0,
152 logical_width as f32,
153 logical_height as f32,
154 0.0,
155 -1.0,
156 1.0,
157 );
158 buffer_mgr.upload_projection(device_mgr.queue(), &projection.to_cols_array_2d());
159 }
160 }
161 
162 fn begin_frame(&mut self) -> Result<()> {
163 if self.surface_mgr.is_none() {
164 anyhow::bail!("Backend not initialized");
165 }
166 Ok(())
167 }
168 
169 fn end_frame(&mut self) -> Result<()> {
170 Ok(())
171 }
172 
173 fn submit(&mut self, commands: &[RenderCommand]) -> Result<()> {
174 let device_mgr = self
175 .device_mgr
176 .as_ref()
177 .ok_or_else(|| anyhow::anyhow!("DeviceManager not initialized"))?;
178 let texture_mgr = self
179 .texture_mgr
180 .as_mut()
181 .ok_or_else(|| anyhow::anyhow!("TextureManager not initialized"))?;
182 let pipeline_mgr = self
183 .pipeline_mgr
184 .as_ref()
185 .ok_or_else(|| anyhow::anyhow!("PipelineManager not initialized"))?;
186 let buffer_mgr = self
187 .buffer_mgr
188 .as_mut()
189 .ok_or_else(|| anyhow::anyhow!("BufferManager not initialized"))?;
190 let surface_mgr = self
191 .surface_mgr
192 .as_mut()
193 .ok_or_else(|| anyhow::anyhow!("SurfaceManager not initialized"))?;
194 
195 // 1. Clear buffers
196 self.vertices.clear();
197 self.indices.clear();
198 
199 let mut vertex_count = 0;
200 let mut current_index_start = 0;
201 let mut current_index_count = 0;
202 let mut batches: Vec<DrawBatch> = Vec::new();
203 let mut scissor_stack: Vec<[u32; 4]> = Vec::new();
204 
205 let get_current_scissor =
206 |stack: &[[u32; 4]]| -> Option<[u32; 4]> { stack.last().cloned() };
207 
208 // 2. Process commands
209 for cmd in commands {
210 match cmd {
211 RenderCommand::PushClip(rect) => {
212 if current_index_count > 0 {
213 batches.push(DrawBatch {
214 index_start: current_index_start,
215 index_count: current_index_count,
216 scissor: get_current_scissor(&scissor_stack),
217 });
218 current_index_start += current_index_count;
219 current_index_count = 0;
220 }
221 // Calculate scissor (simplified reuse from existing code)
222 let scale = self.scale_factor;
223 let x = (rect.x as f64 * scale).round() as i32;
224 let y = (rect.y as f64 * scale).round() as i32;
225 let w = (rect.width as f64 * scale).round() as i32;
226 let h = (rect.height as f64 * scale).round() as i32;
227 let surface_w = surface_mgr.width() as i32;
228 let surface_h = surface_mgr.height() as i32;
229 let min_x = x.max(0);
230 let min_y = y.max(0);
231 let max_x = (x + w).min(surface_w).max(min_x);
232 let max_y = (y + h).min(surface_h).max(min_y);
233 let mut new_rect = [
234 min_x as u32,
235 min_y as u32,
236 (max_x - min_x) as u32,
237 (max_y - min_y) as u32,
238 ];
239 if let Some(parent) = scissor_stack.last() {
240 let px = parent[0];
241 let py = parent[1];
242 let pw = parent[2];
243 let ph = parent[3];
244 let ix = new_rect[0].max(px);
245 let iy = new_rect[1].max(py);
246 let iw = (new_rect[0] + new_rect[2]).min(px + pw).saturating_sub(ix);
247 let ih = (new_rect[1] + new_rect[3]).min(py + ph).saturating_sub(iy);
248 new_rect = [ix, iy, iw, ih];
249 }
250 scissor_stack.push(new_rect);
251 }
252 RenderCommand::PopClip => {
253 if current_index_count > 0 {
254 batches.push(DrawBatch {
255 index_start: current_index_start,
256 index_count: current_index_count,
257 scissor: get_current_scissor(&scissor_stack),
258 });
259 current_index_start += current_index_count;
260 current_index_count = 0;
261 }
262 scissor_stack.pop();
263 }
264 RenderCommand::DrawRect {
265 rect,
266 color,
267 transform,
268 } => {
269 // Implementation as before...
270 let (x, y, w, h) = (rect.x, rect.y, rect.width, rect.height);
271 let transform = transform.unwrap_or(strato_core::types::Transform::identity());
272 let apply_transform = |p: [f32; 2]| -> [f32; 2] {
273 let point = strato_core::types::Point::new(p[0], p[1]);
274 let transformed = transform.transform_point(point);
275 [transformed.x, transformed.y]
276 };
277 let p0 = apply_transform([x, y]);
278 let p1 = apply_transform([x + w, y]);
279 let p2 = apply_transform([x + w, y + h]);
280 let p3 = apply_transform([x, y + h]);
281 let color_arr = [color.r, color.g, color.b, color.a];
282 
283 self.vertices
284 .push(SimpleVertex::from(&crate::vertex::Vertex::solid(
285 p0, color_arr,
286 )));
287 self.vertices
288 .push(SimpleVertex::from(&crate::vertex::Vertex::solid(
289 p1, color_arr,
290 )));
291 self.vertices
292 .push(SimpleVertex::from(&crate::vertex::Vertex::solid(
293 p2, color_arr,
294 )));
295 self.vertices
296 .push(SimpleVertex::from(&crate::vertex::Vertex::solid(
297 p3, color_arr,
298 )));
299 
300 self.indices.push(vertex_count);
301 self.indices.push(vertex_count + 1);
302 self.indices.push(vertex_count + 2);
303 self.indices.push(vertex_count);
304 self.indices.push(vertex_count + 2);
305 self.indices.push(vertex_count + 3);
306 vertex_count += 4;
307 current_index_count += 6;
308 }
309 RenderCommand::DrawText {
310 text,
311 position,
312 color,
313 font_size,
314 align,
315 } => {
316 // Copied implementation from before...
317 let (x_orig, y) = *position;
318 let color_arr = [color.r, color.g, color.b, color.a];
319 let font_size = *font_size;
320 let align = *align;
321 let text_width = if align != strato_core::text::TextAlign::Left {
322 let mut width = 0.0;
323 for ch in text.chars() {
324 if let Some(glyph) = texture_mgr.get_or_cache_glyph(
325 device_mgr.queue(),
326 ch,
327 font_size as u32,
328 ) {
329 width += glyph.metrics.advance;
330 } else if ch == ' ' {
331 width += font_size * 0.3;
332 }
333 }
334 width
335 } else {
336 0.0
337 };
338 let mut x = x_orig;
339 if align == strato_core::text::TextAlign::Center {
340 x -= text_width / 2.0;
341 } else if align == strato_core::text::TextAlign::Right {
342 x -= text_width;
343 }
344 
345 for ch in text.chars() {
346 if let Some(glyph) =
347 texture_mgr.get_or_cache_glyph(device_mgr.queue(), ch, font_size as u32)
348 {
349 let (gx, gy, w, h) = (
350 x + glyph.metrics.bearing_x as f32,
351 y + font_size - glyph.metrics.bearing_y as f32,
352 glyph.metrics.width as f32,
353 glyph.metrics.height as f32,
354 );
355 let (u0, v0, u1, v1) = glyph.uv_rect;
356 let p0 = [gx, gy];
357 let p1 = [gx + w, gy];
358 let p2 = [gx + w, gy + h];
359 let p3 = [gx, gy + h];
360 self.vertices.push(SimpleVertex {
361 position: p0,
362 color: color_arr,
363 uv: [u0, v0],
364 params: [0.0; 4],
365 flags: 1,
366 });
367 self.vertices.push(SimpleVertex {
368 position: p1,
369 color: color_arr,
370 uv: [u1, v0],
371 params: [0.0; 4],
372 flags: 1,
373 });
374 self.vertices.push(SimpleVertex {
375 position: p2,
376 color: color_arr,
377 uv: [u1, v1],
378 params: [0.0; 4],
379 flags: 1,
380 });
381 self.vertices.push(SimpleVertex {
382 position: p3,
383 color: color_arr,
384 uv: [u0, v1],
385 params: [0.0; 4],
386 flags: 1,
387 });
388 self.indices.push(vertex_count);
389 self.indices.push(vertex_count + 1);
390 self.indices.push(vertex_count + 2);
391 self.indices.push(vertex_count);
392 self.indices.push(vertex_count + 2);
393 self.indices.push(vertex_count + 3);
394 vertex_count += 4;
395 current_index_count += 6;
396 x += glyph.metrics.advance;
397 } else if ch == ' ' {
398 x += font_size * 0.3;
399 }
400 }
401 }
402 _ => {}
403 }
404 }
405 
406 // Push final batch
407 if current_index_count > 0 {
408 batches.push(DrawBatch {
409 index_start: current_index_start,
410 index_count: current_index_count,
411 scissor: get_current_scissor(&scissor_stack),
412 });
413 }
414 
415 Self::flush_and_render(
416 batches,
417 device_mgr,
418 surface_mgr,
419 buffer_mgr,
420 pipeline_mgr,
421 &self.vertices,
422 &self.indices,
423 )
424 }
425 
426 fn submit_batch(&mut self, batch: &crate::batch::RenderBatch) -> Result<()> {
427 let device_mgr = self
428 .device_mgr
429 .as_ref()
430 .ok_or_else(|| anyhow::anyhow!("DeviceManager not initialized"))?;
431 let texture_mgr = self
432 .texture_mgr
433 .as_mut()
434 .ok_or_else(|| anyhow::anyhow!("TextureManager not initialized"))?;
435 let pipeline_mgr = self
436 .pipeline_mgr
437 .as_ref()
438 .ok_or_else(|| anyhow::anyhow!("PipelineManager not initialized"))?;
439 let buffer_mgr = self
440 .buffer_mgr
441 .as_mut()
442 .ok_or_else(|| anyhow::anyhow!("BufferManager not initialized"))?;
443 let surface_mgr = self
444 .surface_mgr
445 .as_mut()
446 .ok_or_else(|| anyhow::anyhow!("SurfaceManager not initialized"))?;
447 
448 // 1. Clear buffers
449 self.vertices.clear();
450 self.indices.clear();
451 
452 // 2. Pre-populate vertices from batch
453 // We convert them to SimpleVertex
454 self.vertices.reserve(batch.vertices.len());
455 for v in &batch.vertices {
456 self.vertices.push(SimpleVertex::from(v));
457 }
458 
459 // 3. Process commands
460 let mut batches: Vec<DrawBatch> = Vec::new();
461 let mut current_index_start = 0;
462 let mut current_index_count = 0;
463 let mut scissor_stack: Vec<[u32; 4]> = Vec::new();
464 let mut vertex_count = self.vertices.len() as u32; // Offset for new vertices (text)
465 
466 let get_current_scissor =
467 |stack: &[[u32; 4]]| -> Option<[u32; 4]> { stack.last().cloned() };
468 
469 // Combine commands and overlay_commands for processing
470 let all_commands = batch.commands.iter().chain(batch.overlay_commands.iter());
471 
472 for cmd in all_commands {
473 use crate::batch::DrawCommand;
474 match cmd {
475 DrawCommand::PushClip(rect) => {
476 if current_index_count > 0 {
477 batches.push(DrawBatch {
478 index_start: current_index_start,
479 index_count: current_index_count,
480 scissor: get_current_scissor(&scissor_stack),
481 });
482 current_index_start += current_index_count;
483 current_index_count = 0;
484 }
485 // Calculate scissor
486 let scale = self.scale_factor;
487 let x = (rect.x as f64 * scale).round() as i32;
488 let y = (rect.y as f64 * scale).round() as i32;
489 let w = (rect.width as f64 * scale).round() as i32;
490 let h = (rect.height as f64 * scale).round() as i32;
491 let surface_w = surface_mgr.width() as i32;
492 let surface_h = surface_mgr.height() as i32;
493 let min_x = x.max(0);
494 let min_y = y.max(0);
495 let max_x = (x + w).min(surface_w).max(min_x);
496 let max_y = (y + h).min(surface_h).max(min_y);
497 let mut new_rect = [
498 min_x as u32,
499 min_y as u32,
500 (max_x - min_x) as u32,
501 (max_y - min_y) as u32,
502 ];
503 if let Some(parent) = scissor_stack.last() {
504 let px = parent[0];
505 let py = parent[1];
506 let pw = parent[2];
507 let ph = parent[3];
508 let ix = new_rect[0].max(px);
509 let iy = new_rect[1].max(py);
510 let iw = (new_rect[0] + new_rect[2]).min(px + pw).saturating_sub(ix);
511 let ih = (new_rect[1] + new_rect[3]).min(py + ph).saturating_sub(iy);
512 new_rect = [ix, iy, iw, ih];
513 }
514 scissor_stack.push(new_rect);
515 }
516 DrawCommand::PopClip => {
517 if current_index_count > 0 {
518 batches.push(DrawBatch {
519 index_start: current_index_start,
520 index_count: current_index_count,
521 scissor: get_current_scissor(&scissor_stack),
522 });
523 current_index_start += current_index_count;
524 current_index_count = 0;
525 }
526 scissor_stack.pop();
527 }
528 DrawCommand::Rect { index_range, .. }
529 | DrawCommand::TexturedQuad { index_range, .. }
530 | DrawCommand::Circle { index_range, .. }
531 | DrawCommand::Line { index_range, .. } => {
532 // Use pre-batched indices
533 // We need to copy indices from batch.indices[index_range] to self.indices
534 // self.vertices already contains batch vertices at offset 0
535 // batch.indices are 0-based relative to batch vertices
536 // So we can use them directly (just cast to u32)
537 for i in index_range.clone() {
538 if (i as usize) < batch.indices.len() {
539 self.indices.push(batch.indices[i as usize] as u32);
540 current_index_count += 1;
541 }
542 }
543 }
544 DrawCommand::Text {
545 text,
546 position,
547 color,
548 font_size,
549 align,
550 ..
551 } => {
552 // Generate text vertices/indices immediate mode style
553 // Appending to self.vertices, so indices start at 'vertex_count'
554 let (x_orig, y) = *position;
555 let color_arr = [color.r, color.g, color.b, color.a];
556 let font_size = *font_size;
557 let align = *align;
558 let text_width = if align != strato_core::text::TextAlign::Left {
559 let mut width = 0.0;
560 for ch in text.chars() {
561 if let Some(glyph) = texture_mgr.get_or_cache_glyph(
562 device_mgr.queue(),
563 ch,
564 font_size as u32,
565 ) {
566 width += glyph.metrics.advance;
567 } else if ch == ' ' {
568 width += font_size * 0.3;
569 }
570 }
571 width
572 } else {
573 0.0
574 };
575 let mut x = x_orig;
576 if align == strato_core::text::TextAlign::Center {
577 x -= text_width / 2.0;
578 } else if align == strato_core::text::TextAlign::Right {
579 x -= text_width;
580 }
581 
582 for ch in text.chars() {
583 if let Some(glyph) =
584 texture_mgr.get_or_cache_glyph(device_mgr.queue(), ch, font_size as u32)
585 {
586 let (gx, gy, w, h) = (
587 x + glyph.metrics.bearing_x as f32,
588 y + font_size - glyph.metrics.bearing_y as f32,
589 glyph.metrics.width as f32,
590 glyph.metrics.height as f32,
591 );
592 let (u0, v0, u1, v1) = glyph.uv_rect;
593 let p0 = [gx, gy];
594 let p1 = [gx + w, gy];
595 let p2 = [gx + w, gy + h];
596 let p3 = [gx, gy + h];
597 self.vertices.push(SimpleVertex {
598 position: p0,
599 color: color_arr,
600 uv: [u0, v0],
601 params: [0.0; 4],
602 flags: 1,
603 });
604 self.vertices.push(SimpleVertex {
605 position: p1,
606 color: color_arr,
607 uv: [u1, v0],
608 params: [0.0; 4],
609 flags: 1,
610 });
611 self.vertices.push(SimpleVertex {
612 position: p2,
613 color: color_arr,
614 uv: [u1, v1],
615 params: [0.0; 4],
616 flags: 1,
617 });
618 self.vertices.push(SimpleVertex {
619 position: p3,
620 color: color_arr,
621 uv: [u0, v1],
622 params: [0.0; 4],
623 flags: 1,
624 });
625 self.indices.push(vertex_count);
626 self.indices.push(vertex_count + 1);
627 self.indices.push(vertex_count + 2);
628 self.indices.push(vertex_count);
629 self.indices.push(vertex_count + 2);
630 self.indices.push(vertex_count + 3);
631 vertex_count += 4;
632 current_index_count += 6;
633 x += glyph.metrics.advance;
634 } else if ch == ' ' {
635 x += font_size * 0.3;
636 }
637 }
638 }
639 _ => {}
640 }
641 }
642 
643 if current_index_count > 0 {
644 batches.push(DrawBatch {
645 index_start: current_index_start,
646 index_count: current_index_count,
647 scissor: get_current_scissor(&scissor_stack),
648 });
649 }
650 
651 Self::flush_and_render(
652 batches,
653 device_mgr,
654 surface_mgr,
655 buffer_mgr,
656 pipeline_mgr,
657 &self.vertices,
658 &self.indices,
659 )
660 }
661}
662 
663impl WgpuBackend {
664 fn flush_and_render(
665 batches: Vec<DrawBatch>,
666 device_mgr: &DeviceManager,
667 surface_mgr: &mut SurfaceManager,
668 buffer_mgr: &mut BufferManager,
669 pipeline_mgr: &PipelineManager,
670 vertices: &[SimpleVertex],
671 indices: &[u32],
672 ) -> Result<()> {
673 // 3. Update buffers
674 buffer_mgr.upload_vertices(device_mgr.device(), device_mgr.queue(), vertices);
675 buffer_mgr.upload_indices(device_mgr.device(), device_mgr.queue(), indices);
676 
677 // 4. Render Pass
678 let output = surface_mgr.get_current_texture()?;
679 let view = output
680 .texture
681 .create_view(&wgpu::TextureViewDescriptor::default());
682 
683 let mut encoder = device_mgr
684 .device()
685 .create_command_encoder(&CommandEncoderDescriptor {
686 label: Some("Render Encoder"),
687 });
688 
689 {
690 let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
691 label: Some("Render Pass"),
692 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
693 view: &view,
694 resolve_target: None,
695 ops: wgpu::Operations {
696 load: wgpu::LoadOp::Clear(wgpu::Color {
697 r: 0.1,
698 g: 0.1,
699 b: 0.1,
700 a: 1.0,
701 }),
702 store: wgpu::StoreOp::Store,
703 },
704 })],
705 depth_stencil_attachment: None,
706 occlusion_query_set: None,
707 timestamp_writes: None,
708 });
709 
710 if !indices.is_empty() {
711 render_pass.set_pipeline(pipeline_mgr.pipeline());
712 render_pass.set_bind_group(0, pipeline_mgr.bind_group(), &[]);
713 render_pass.set_vertex_buffer(0, buffer_mgr.vertex_buffer().slice(..));
714 render_pass.set_index_buffer(
715 buffer_mgr.index_buffer().slice(..),
716 wgpu::IndexFormat::Uint32,
717 );
718 
719 for batch in batches {
720 if batch.index_count == 0 {
721 continue;
722 }
723 if let Some(scissor) = batch.scissor {
724 if scissor[2] == 0 || scissor[3] == 0 {
725 continue;
726 }
727 render_pass
728 .set_scissor_rect(scissor[0], scissor[1], scissor[2], scissor[3]);
729 } else {
730 render_pass.set_scissor_rect(
731 0,
732 0,
733 surface_mgr.width(),
734 surface_mgr.height(),
735 );
736 }
737 render_pass.draw_indexed(
738 batch.index_start..batch.index_start + batch.index_count,
739 0,
740 0..1,
741 );
742 }
743 }
744 }
745 device_mgr.queue().submit(std::iter::once(encoder.finish()));
746 output.present();
747 Ok(())
748 }
749}
750 
751struct DrawBatch {
752 index_start: u32,
753 index_count: u32,
754 scissor: Option<[u32; 4]>,
755}
756