1use super::backend::{
5 BackendCapabilities, BackendContext, BufferHandle, BufferUsage, ComputePipelineHandle,
6 GpuBackend, GpuCommand, PipelineHandle, PipelineLayout, ShaderHandle, ShaderStage,
7 SoftwareContext, TextureFormat, TextureHandle,
8};
9use glam::{Vec3, Vec4};
10
11#[derive(Debug, Clone)]
17pub struct VertexAttribute {
18 pub location: u32,
19 pub format: AttributeFormat,
20 pub offset: u32,
21}
22
23#[derive(Debug, Clone, Copy, PartialEq, Eq)]
25pub enum AttributeFormat {
26 Float,
27 Float2,
28 Float3,
29 Float4,
30 UInt,
31 Int,
32}
33
34impl AttributeFormat {
35 pub fn byte_size(&self) -> u32 {
37 match self {
38 Self::Float => 4,
39 Self::Float2 => 8,
40 Self::Float3 => 12,
41 Self::Float4 => 16,
42 Self::UInt => 4,
43 Self::Int => 4,
44 }
45 }
46}
47
48#[derive(Debug, Clone)]
50pub struct VertexLayout {
51 pub attributes: Vec<VertexAttribute>,
52}
53
54impl VertexLayout {
55 pub fn new() -> Self {
56 Self { attributes: Vec::new() }
57 }
58
59 pub fn push(mut self, location: u32, format: AttributeFormat, offset: u32) -> Self {
60 self.attributes.push(VertexAttribute { location, format, offset });
61 self
62 }
63
64 pub fn stride(&self) -> u32 {
66 self.attributes.iter().map(|a| a.offset + a.format.byte_size()).max().unwrap_or(0)
67 }
68}
69
70impl Default for VertexLayout {
71 fn default() -> Self { Self::new() }
72}
73
74#[derive(Debug, Clone)]
80pub struct UniformBlock {
81 pub data: Vec<u8>,
82 pub binding: u32,
83}
84
85impl UniformBlock {
86 pub fn new(binding: u32, data: Vec<u8>) -> Self {
87 Self { data, binding }
88 }
89
90 pub fn from_f32_slice(binding: u32, values: &[f32]) -> Self {
91 let data: Vec<u8> = values.iter().flat_map(|f| f.to_le_bytes()).collect();
92 Self { data, binding }
93 }
94}
95
96#[derive(Debug, Clone)]
102pub struct RenderPass {
103 pub color_attachments: Vec<TextureHandle>,
104 pub depth_attachment: Option<TextureHandle>,
105 pub clear_color: [f32; 4],
106}
107
108impl RenderPass {
109 pub fn new() -> Self {
110 Self {
111 color_attachments: Vec::new(),
112 depth_attachment: None,
113 clear_color: [0.0, 0.0, 0.0, 1.0],
114 }
115 }
116
117 pub fn with_color(mut self, tex: TextureHandle) -> Self {
118 self.color_attachments.push(tex);
119 self
120 }
121
122 pub fn with_depth(mut self, tex: TextureHandle) -> Self {
123 self.depth_attachment = Some(tex);
124 self
125 }
126
127 pub fn with_clear(mut self, r: f32, g: f32, b: f32, a: f32) -> Self {
128 self.clear_color = [r, g, b, a];
129 self
130 }
131}
132
133impl Default for RenderPass {
134 fn default() -> Self { Self::new() }
135}
136
137#[derive(Debug, Clone)]
143pub struct DrawCall {
144 pub pipeline: PipelineHandle,
145 pub vertex_buffer: BufferHandle,
146 pub index_buffer: Option<BufferHandle>,
147 pub vertex_count: u32,
148 pub index_count: u32,
149 pub instance_buffer: Option<BufferHandle>,
150 pub instance_count: u32,
151 pub uniforms: Vec<UniformBlock>,
152}
153
154impl DrawCall {
155 pub fn new(pipeline: PipelineHandle, vertex_buffer: BufferHandle, vertex_count: u32) -> Self {
156 Self {
157 pipeline,
158 vertex_buffer,
159 index_buffer: None,
160 vertex_count,
161 index_count: 0,
162 instance_buffer: None,
163 instance_count: 1,
164 uniforms: Vec::new(),
165 }
166 }
167
168 pub fn with_instances(mut self, buffer: BufferHandle, count: u32) -> Self {
169 self.instance_buffer = Some(buffer);
170 self.instance_count = count;
171 self
172 }
173
174 pub fn with_index_buffer(mut self, buffer: BufferHandle, count: u32) -> Self {
175 self.index_buffer = Some(buffer);
176 self.index_count = count;
177 self
178 }
179
180 pub fn with_uniform(mut self, block: UniformBlock) -> Self {
181 self.uniforms.push(block);
182 self
183 }
184}
185
186#[derive(Debug, Clone, Copy, PartialEq, Eq)]
191enum FrameState {
192 Idle,
193 Recording,
194}
195
196pub struct MultiBackendRenderer {
203 pub backend: Box<dyn BackendContext>,
204 pub capabilities: BackendCapabilities,
205 frame_state: FrameState,
206 recorded_commands: Vec<GpuCommand>,
207 frame_count: u64,
208}
209
210impl MultiBackendRenderer {
211 pub fn new(backend: Box<dyn BackendContext>, capabilities: BackendCapabilities) -> Self {
212 Self {
213 backend,
214 capabilities,
215 frame_state: FrameState::Idle,
216 recorded_commands: Vec::new(),
217 frame_count: 0,
218 }
219 }
220
221 pub fn software() -> Self {
223 let caps = BackendCapabilities::for_backend(GpuBackend::Software);
224 Self::new(Box::new(SoftwareContext::new()), caps)
225 }
226
227 pub fn from_existing_pipeline(_pipeline: &()) -> Self {
230 Self::software()
231 }
232
233 pub fn begin_frame(&mut self) {
237 self.frame_state = FrameState::Recording;
238 self.recorded_commands.clear();
239 }
240
241 pub fn end_frame(&mut self) {
243 let cmds: Vec<GpuCommand> = std::mem::take(&mut self.recorded_commands);
244 self.backend.submit(&cmds);
245 self.frame_state = FrameState::Idle;
246 self.frame_count += 1;
247 }
248
249 pub fn present(&mut self) {
251 self.backend.present();
252 }
253
254 pub fn frame_count(&self) -> u64 {
256 self.frame_count
257 }
258
259 pub fn draw(&mut self, _pass: &RenderPass, calls: &[DrawCall]) {
263 for call in calls {
264 for uniform in &call.uniforms {
266 let ubuf = self.backend.create_buffer(uniform.data.len(), BufferUsage::UNIFORM);
267 self.backend.write_buffer(ubuf, &uniform.data);
268 self.recorded_commands.push(GpuCommand::SetBindGroup {
269 index: uniform.binding,
270 buffers: vec![ubuf],
271 });
272 }
273
274 if let Some(idx_buf) = call.index_buffer {
275 self.recorded_commands.push(GpuCommand::DrawIndexed {
276 pipeline: call.pipeline,
277 vertex_buffer: call.vertex_buffer,
278 index_buffer: idx_buf,
279 index_count: call.index_count,
280 instance_count: call.instance_count,
281 });
282 } else {
283 self.recorded_commands.push(GpuCommand::Draw {
284 pipeline: call.pipeline,
285 vertex_buffer: call.vertex_buffer,
286 vertex_count: call.vertex_count,
287 instance_count: call.instance_count,
288 });
289 }
290 }
291 }
292
293 pub fn dispatch_compute(
297 &mut self,
298 pipeline: ComputePipelineHandle,
299 workgroups: [u32; 3],
300 buffers: &[BufferHandle],
301 ) {
302 if !buffers.is_empty() {
303 self.recorded_commands.push(GpuCommand::SetBindGroup {
304 index: 0,
305 buffers: buffers.to_vec(),
306 });
307 }
308 self.recorded_commands.push(GpuCommand::Dispatch {
309 pipeline,
310 x: workgroups[0],
311 y: workgroups[1],
312 z: workgroups[2],
313 });
314 }
315
316 pub fn read_buffer(&self, buffer: BufferHandle) -> Vec<u8> {
320 self.backend.read_buffer(buffer)
321 }
322
323 pub fn upload_buffer(&mut self, buffer: BufferHandle, data: &[u8]) {
325 self.backend.write_buffer(buffer, data);
326 }
327
328 pub fn upload_texture(&mut self, texture: TextureHandle, data: &[u8]) {
330 self.backend.write_texture(texture, data);
331 }
332
333 pub fn create_vertex_buffer(&mut self, data: &[u8]) -> BufferHandle {
337 let buf = self.backend.create_buffer(data.len(), BufferUsage::VERTEX);
338 self.backend.write_buffer(buf, data);
339 buf
340 }
341
342 pub fn create_index_buffer(&mut self, data: &[u8]) -> BufferHandle {
344 let buf = self.backend.create_buffer(data.len(), BufferUsage::INDEX);
345 self.backend.write_buffer(buf, data);
346 buf
347 }
348
349 pub fn create_uniform_buffer(&mut self, data: &[u8]) -> BufferHandle {
351 let buf = self.backend.create_buffer(data.len(), BufferUsage::UNIFORM);
352 self.backend.write_buffer(buf, data);
353 buf
354 }
355
356 pub fn create_storage_buffer(&mut self, size: usize) -> BufferHandle {
358 self.backend.create_buffer(size, BufferUsage::STORAGE)
359 }
360
361 pub fn create_color_texture(&mut self, w: u32, h: u32) -> TextureHandle {
363 self.backend.create_texture(w, h, TextureFormat::RGBA8)
364 }
365
366 pub fn create_depth_texture(&mut self, w: u32, h: u32) -> TextureHandle {
368 self.backend.create_texture(w, h, TextureFormat::Depth32F)
369 }
370
371 pub fn destroy_buffer(&mut self, buf: BufferHandle) {
373 self.backend.destroy_buffer(buf);
374 }
375
376 pub fn destroy_texture(&mut self, tex: TextureHandle) {
378 self.backend.destroy_texture(tex);
379 }
380
381 pub fn backend_name(&self) -> &str {
383 self.backend.name()
384 }
385}
386
387#[derive(Debug, Clone, Default)]
393pub struct RenderStats {
394 pub draw_calls: u32,
395 pub triangles: u32,
396 pub compute_dispatches: u32,
397 pub buffer_uploads: u32,
398 pub texture_uploads: u32,
399}
400
401impl RenderStats {
402 pub fn new() -> Self { Self::default() }
403
404 pub fn record_draw(&mut self, tris: u32) {
405 self.draw_calls += 1;
406 self.triangles += tris;
407 }
408
409 pub fn record_compute(&mut self) {
410 self.compute_dispatches += 1;
411 }
412
413 pub fn record_buffer_upload(&mut self) {
414 self.buffer_uploads += 1;
415 }
416
417 pub fn record_texture_upload(&mut self) {
418 self.texture_uploads += 1;
419 }
420}
421
422pub struct StatsCollector {
428 history: Vec<RenderStats>,
429 max_history: usize,
430}
431
432impl StatsCollector {
433 pub fn new(max_history: usize) -> Self {
434 Self {
435 history: Vec::with_capacity(max_history),
436 max_history,
437 }
438 }
439
440 pub fn push(&mut self, stats: RenderStats) {
441 if self.history.len() >= self.max_history {
442 self.history.remove(0);
443 }
444 self.history.push(stats);
445 }
446
447 pub fn average_draw_calls(&self) -> f32 {
448 if self.history.is_empty() { return 0.0; }
449 let sum: u32 = self.history.iter().map(|s| s.draw_calls).sum();
450 sum as f32 / self.history.len() as f32
451 }
452
453 pub fn average_triangles(&self) -> f32 {
454 if self.history.is_empty() { return 0.0; }
455 let sum: u32 = self.history.iter().map(|s| s.triangles).sum();
456 sum as f32 / self.history.len() as f32
457 }
458
459 pub fn total_frames(&self) -> usize {
460 self.history.len()
461 }
462
463 pub fn clear(&mut self) {
464 self.history.clear();
465 }
466}
467
468#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
474pub enum RenderOrder {
475 Background = 0,
476 Opaque = 1,
477 Transparent = 2,
478 Overlay = 3,
479 UI = 4,
480}
481
482#[derive(Debug, Clone)]
484pub struct QueuedDraw {
485 pub order: RenderOrder,
486 pub depth: u32, pub call: DrawCall,
488}
489
490pub struct RenderQueue {
492 items: Vec<QueuedDraw>,
493}
494
495impl RenderQueue {
496 pub fn new() -> Self {
497 Self { items: Vec::new() }
498 }
499
500 pub fn push(&mut self, item: QueuedDraw) {
501 self.items.push(item);
502 }
503
504 pub fn sort(&mut self) {
505 self.items.sort_by(|a, b| {
506 a.order.cmp(&b.order).then_with(|| {
507 if a.order == RenderOrder::Transparent {
508 b.depth.cmp(&a.depth)
510 } else {
511 a.depth.cmp(&b.depth)
513 }
514 })
515 });
516 }
517
518 pub fn drain(&mut self) -> Vec<DrawCall> {
519 self.sort();
520 self.items.drain(..).map(|q| q.call).collect()
521 }
522
523 pub fn len(&self) -> usize { self.items.len() }
524 pub fn is_empty(&self) -> bool { self.items.is_empty() }
525 pub fn clear(&mut self) { self.items.clear(); }
526}
527
528impl Default for RenderQueue {
529 fn default() -> Self { Self::new() }
530}
531
532#[cfg(test)]
537mod tests {
538 use super::*;
539 use crate::wgpu_backend::backend::*;
540
541 fn make_renderer() -> MultiBackendRenderer {
542 MultiBackendRenderer::software()
543 }
544
545 #[test]
546 fn attribute_format_sizes() {
547 assert_eq!(AttributeFormat::Float.byte_size(), 4);
548 assert_eq!(AttributeFormat::Float3.byte_size(), 12);
549 assert_eq!(AttributeFormat::Float4.byte_size(), 16);
550 }
551
552 #[test]
553 fn vertex_layout_stride() {
554 let layout = VertexLayout::new()
555 .push(0, AttributeFormat::Float3, 0) .push(1, AttributeFormat::Float2, 12); assert_eq!(layout.stride(), 20);
558 }
559
560 #[test]
561 fn uniform_block_from_f32() {
562 let block = UniformBlock::from_f32_slice(0, &[1.0, 2.0]);
563 assert_eq!(block.data.len(), 8);
564 assert_eq!(block.binding, 0);
565 }
566
567 #[test]
568 fn render_pass_builder() {
569 let mut r = make_renderer();
570 let color = r.create_color_texture(800, 600);
571 let depth = r.create_depth_texture(800, 600);
572 let pass = RenderPass::new()
573 .with_color(color)
574 .with_depth(depth)
575 .with_clear(0.1, 0.1, 0.1, 1.0);
576 assert_eq!(pass.color_attachments.len(), 1);
577 assert!(pass.depth_attachment.is_some());
578 assert_eq!(pass.clear_color[0], 0.1);
579 }
580
581 #[test]
582 fn draw_call_builder() {
583 let mut r = make_renderer();
584 let vbuf = r.create_vertex_buffer(&[0u8; 64]);
585 let pipe = r.backend.create_pipeline(
586 r.backend.create_shader("v", ShaderStage::Vertex),
587 r.backend.create_shader("f", ShaderStage::Fragment),
588 &PipelineLayout::default(),
589 );
590 let call = DrawCall::new(pipe, vbuf, 3)
591 .with_instances(vbuf, 10)
592 .with_uniform(UniformBlock::new(0, vec![0u8; 64]));
593 assert_eq!(call.instance_count, 10);
594 assert_eq!(call.uniforms.len(), 1);
595 }
596
597 #[test]
598 fn begin_end_frame() {
599 let mut r = make_renderer();
600 assert_eq!(r.frame_count(), 0);
601 r.begin_frame();
602 r.end_frame();
603 assert_eq!(r.frame_count(), 1);
604 r.present();
605 }
606
607 #[test]
608 fn draw_within_pass() {
609 let mut r = make_renderer();
610 let vbuf = r.create_vertex_buffer(&[0u8; 48]);
611 let pipe = r.backend.create_pipeline(
612 r.backend.create_shader("v", ShaderStage::Vertex),
613 r.backend.create_shader("f", ShaderStage::Fragment),
614 &PipelineLayout::default(),
615 );
616 let pass = RenderPass::new();
617 let call = DrawCall::new(pipe, vbuf, 3);
618
619 r.begin_frame();
620 r.draw(&pass, &[call]);
621 r.end_frame();
622 assert_eq!(r.frame_count(), 1);
623 }
624
625 #[test]
626 fn dispatch_compute() {
627 let mut r = make_renderer();
628 let cs = r.backend.create_shader("compute", ShaderStage::Compute);
629 let cp = r.backend.create_compute_pipeline(cs, &PipelineLayout::default());
630 let buf = r.create_storage_buffer(256);
631
632 r.begin_frame();
633 r.dispatch_compute(cp, [4, 1, 1], &[buf]);
634 r.end_frame();
635 }
636
637 #[test]
638 fn upload_and_read_buffer() {
639 let mut r = make_renderer();
640 let buf = r.backend.create_buffer(8, BufferUsage::STORAGE);
641 r.upload_buffer(buf, &[1, 2, 3, 4, 5, 6, 7, 8]);
642 assert_eq!(r.read_buffer(buf), vec![1, 2, 3, 4, 5, 6, 7, 8]);
643 }
644
645 #[test]
646 fn upload_texture() {
647 let mut r = make_renderer();
648 let tex = r.create_color_texture(2, 2);
649 let pixels = vec![128u8; 16];
650 r.upload_texture(tex, &pixels);
651 }
652
653 #[test]
654 fn backend_name() {
655 let r = make_renderer();
656 assert_eq!(r.backend_name(), "Software");
657 }
658
659 #[test]
660 fn render_stats() {
661 let mut stats = RenderStats::new();
662 stats.record_draw(100);
663 stats.record_draw(200);
664 stats.record_compute();
665 assert_eq!(stats.draw_calls, 2);
666 assert_eq!(stats.triangles, 300);
667 assert_eq!(stats.compute_dispatches, 1);
668 }
669
670 #[test]
671 fn stats_collector_rolling() {
672 let mut collector = StatsCollector::new(3);
673 for i in 0..5 {
674 let mut s = RenderStats::new();
675 s.record_draw(i * 10);
676 collector.push(s);
677 }
678 assert_eq!(collector.total_frames(), 3);
679 assert!((collector.average_draw_calls() - 1.0).abs() < 0.01);
681 }
682
683 #[test]
684 fn render_queue_sorting() {
685 let mut r = make_renderer();
686 let vbuf = r.create_vertex_buffer(&[0u8; 12]);
687 let pipe = r.backend.create_pipeline(
688 r.backend.create_shader("v", ShaderStage::Vertex),
689 r.backend.create_shader("f", ShaderStage::Fragment),
690 &PipelineLayout::default(),
691 );
692
693 let mut queue = RenderQueue::new();
694 queue.push(QueuedDraw {
695 order: RenderOrder::Transparent,
696 depth: 10,
697 call: DrawCall::new(pipe, vbuf, 3),
698 });
699 queue.push(QueuedDraw {
700 order: RenderOrder::Opaque,
701 depth: 5,
702 call: DrawCall::new(pipe, vbuf, 3),
703 });
704 queue.push(QueuedDraw {
705 order: RenderOrder::Background,
706 depth: 0,
707 call: DrawCall::new(pipe, vbuf, 3),
708 });
709 queue.push(QueuedDraw {
710 order: RenderOrder::Transparent,
711 depth: 20,
712 call: DrawCall::new(pipe, vbuf, 3),
713 });
714
715 let calls = queue.drain();
716 assert_eq!(calls.len(), 4);
717 }
718
719 #[test]
720 fn render_queue_empty() {
721 let queue = RenderQueue::new();
722 assert!(queue.is_empty());
723 assert_eq!(queue.len(), 0);
724 }
725
726 #[test]
727 fn draw_indexed() {
728 let mut r = make_renderer();
729 let vbuf = r.create_vertex_buffer(&[0u8; 48]);
730 let ibuf = r.create_index_buffer(&[0u8; 12]);
731 let pipe = r.backend.create_pipeline(
732 r.backend.create_shader("v", ShaderStage::Vertex),
733 r.backend.create_shader("f", ShaderStage::Fragment),
734 &PipelineLayout::default(),
735 );
736 let call = DrawCall::new(pipe, vbuf, 3)
737 .with_index_buffer(ibuf, 6);
738 assert_eq!(call.index_count, 6);
739
740 r.begin_frame();
741 r.draw(&RenderPass::new(), &[call]);
742 r.end_frame();
743 }
744
745 #[test]
746 fn destroy_resources() {
747 let mut r = make_renderer();
748 let buf = r.create_vertex_buffer(&[0u8; 16]);
749 let tex = r.create_color_texture(4, 4);
750 r.destroy_buffer(buf);
751 r.destroy_texture(tex);
752 assert!(r.read_buffer(buf).is_empty());
753 }
754
755 #[test]
756 fn from_existing_pipeline() {
757 let unit = ();
758 let r = MultiBackendRenderer::from_existing_pipeline(&unit);
759 assert_eq!(r.backend_name(), "Software");
760 }
761}