1// Doing `if let` gives you nice scoping with passes/encoders
2#![allow(irrefutable_let_patterns)]
3
4use super::{BladeBelt, BladeBeltDescriptor};
5use crate::{PrimitiveBatch, Quad, Scene, Shadow};
6use bytemuck::{Pod, Zeroable};
7
8use blade_graphics as gpu;
9use std::sync::Arc;
10
11const SURFACE_FRAME_COUNT: u32 = 3;
12const MAX_FRAME_TIME_MS: u32 = 1000;
13
14#[repr(C)]
15#[derive(Clone, Copy, Pod, Zeroable)]
16struct GlobalParams {
17 viewport_size: [f32; 2],
18 pad: [u32; 2],
19}
20
21#[derive(blade_macros::ShaderData)]
22struct ShaderQuadsData {
23 globals: GlobalParams,
24 b_quads: gpu::BufferPiece,
25}
26
27#[derive(blade_macros::ShaderData)]
28struct ShaderShadowsData {
29 globals: GlobalParams,
30 b_shadows: gpu::BufferPiece,
31}
32
33struct BladePipelines {
34 quads: gpu::RenderPipeline,
35 shadows: gpu::RenderPipeline,
36}
37
38impl BladePipelines {
39 fn new(gpu: &gpu::Context, surface_format: gpu::TextureFormat) -> Self {
40 let shader = gpu.create_shader(gpu::ShaderDesc {
41 source: include_str!("shaders.wgsl"),
42 });
43 shader.check_struct_size::<Quad>();
44 shader.check_struct_size::<Shadow>();
45 let quads_layout = <ShaderQuadsData as gpu::ShaderData>::layout();
46 let shadows_layout = <ShaderShadowsData as gpu::ShaderData>::layout();
47 Self {
48 quads: gpu.create_render_pipeline(gpu::RenderPipelineDesc {
49 name: "quads",
50 data_layouts: &[&quads_layout],
51 vertex: shader.at("vs_quad"),
52 primitive: gpu::PrimitiveState {
53 topology: gpu::PrimitiveTopology::TriangleStrip,
54 ..Default::default()
55 },
56 depth_stencil: None,
57 fragment: shader.at("fs_quad"),
58 color_targets: &[gpu::ColorTargetState {
59 format: surface_format,
60 blend: Some(gpu::BlendState::ALPHA_BLENDING),
61 write_mask: gpu::ColorWrites::default(),
62 }],
63 }),
64 shadows: gpu.create_render_pipeline(gpu::RenderPipelineDesc {
65 name: "shadows",
66 data_layouts: &[&shadows_layout],
67 vertex: shader.at("vs_shadow"),
68 primitive: gpu::PrimitiveState {
69 topology: gpu::PrimitiveTopology::TriangleStrip,
70 ..Default::default()
71 },
72 depth_stencil: None,
73 fragment: shader.at("fs_shadow"),
74 color_targets: &[gpu::ColorTargetState {
75 format: surface_format,
76 blend: Some(gpu::BlendState::ALPHA_BLENDING),
77 write_mask: gpu::ColorWrites::default(),
78 }],
79 }),
80 }
81 }
82}
83
84pub struct BladeRenderer {
85 gpu: Arc<gpu::Context>,
86 command_encoder: gpu::CommandEncoder,
87 last_sync_point: Option<gpu::SyncPoint>,
88 pipelines: BladePipelines,
89 instance_belt: BladeBelt,
90 viewport_size: gpu::Extent,
91}
92
93impl BladeRenderer {
94 pub fn new(gpu: Arc<gpu::Context>, size: gpu::Extent) -> Self {
95 let surface_format = gpu.resize(gpu::SurfaceConfig {
96 size,
97 usage: gpu::TextureUsage::TARGET,
98 frame_count: SURFACE_FRAME_COUNT,
99 });
100 let command_encoder = gpu.create_command_encoder(gpu::CommandEncoderDesc {
101 name: "main",
102 buffer_count: 2,
103 });
104 let pipelines = BladePipelines::new(&gpu, surface_format);
105 let instance_belt = BladeBelt::new(BladeBeltDescriptor {
106 memory: gpu::Memory::Shared,
107 min_chunk_size: 0x1000,
108 });
109 Self {
110 gpu,
111 command_encoder,
112 last_sync_point: None,
113 pipelines,
114 instance_belt,
115 viewport_size: size,
116 }
117 }
118
119 fn wait_for_gpu(&mut self) {
120 if let Some(last_sp) = self.last_sync_point.take() {
121 if !self.gpu.wait_for(&last_sp, MAX_FRAME_TIME_MS) {
122 panic!("GPU hung");
123 }
124 }
125 }
126
127 pub fn destroy(&mut self) {
128 self.wait_for_gpu();
129 self.instance_belt.destroy(&self.gpu);
130 self.gpu.destroy_command_encoder(&mut self.command_encoder);
131 }
132
133 pub fn resize(&mut self, size: gpu::Extent) {
134 self.wait_for_gpu();
135 self.gpu.resize(gpu::SurfaceConfig {
136 size,
137 usage: gpu::TextureUsage::TARGET,
138 frame_count: SURFACE_FRAME_COUNT,
139 });
140 self.viewport_size = size;
141 }
142
143 pub fn draw(&mut self, scene: &Scene) {
144 let frame = self.gpu.acquire_frame();
145 self.command_encoder.start();
146 self.command_encoder.init_texture(frame.texture());
147
148 let globals = GlobalParams {
149 viewport_size: [
150 self.viewport_size.width as f32,
151 self.viewport_size.height as f32,
152 ],
153 pad: [0; 2],
154 };
155
156 if let mut pass = self.command_encoder.render(gpu::RenderTargetSet {
157 colors: &[gpu::RenderTarget {
158 view: frame.texture_view(),
159 init_op: gpu::InitOp::Clear(gpu::TextureColor::TransparentBlack),
160 finish_op: gpu::FinishOp::Store,
161 }],
162 depth_stencil: None,
163 }) {
164 for batch in scene.batches() {
165 match batch {
166 PrimitiveBatch::Quads(quads) => {
167 let instances = self.instance_belt.alloc_data(quads, &self.gpu);
168 let mut encoder = pass.with(&self.pipelines.quads);
169 encoder.bind(
170 0,
171 &ShaderQuadsData {
172 globals,
173 b_quads: instances,
174 },
175 );
176 encoder.draw(0, 4, 0, quads.len() as u32);
177 }
178 PrimitiveBatch::Shadows(shadows) => {
179 let instances = self.instance_belt.alloc_data(shadows, &self.gpu);
180 let mut encoder = pass.with(&self.pipelines.shadows);
181 encoder.bind(
182 0,
183 &ShaderShadowsData {
184 globals,
185 b_shadows: instances,
186 },
187 );
188 encoder.draw(0, 4, 0, shadows.len() as u32);
189 }
190 _ => continue,
191 }
192 }
193 }
194
195 self.command_encoder.present(frame);
196 let sync_point = self.gpu.submit(&mut self.command_encoder);
197 self.instance_belt.flush(&sync_point);
198 self.wait_for_gpu();
199 self.last_sync_point = Some(sync_point);
200 }
201}