1mod gpu;
2mod sprite;
3mod texture;
4pub mod camera;
5mod tilemap;
6mod lighting;
7pub mod font;
8pub mod msdf;
9pub mod shader;
10pub mod postprocess;
11pub mod radiance;
12pub mod geometry;
13pub mod rendertarget;
14pub mod test_harness;
16
17pub use gpu::GpuContext;
18pub use sprite::{SpriteCommand, SpritePipeline};
19pub use texture::{TextureId, TextureStore};
20pub use camera::Camera2D;
21pub use tilemap::{Tilemap, TilemapStore};
22pub use lighting::{LightingState, LightingUniform, PointLight, LightData, MAX_LIGHTS};
23pub use msdf::{MsdfFont, MsdfFontStore, MsdfGlyph};
24pub use shader::ShaderStore;
25pub use postprocess::PostProcessPipeline;
26pub use radiance::{RadiancePipeline, RadianceState, EmissiveSurface, Occluder, DirectionalLight, SpotLight};
27pub use geometry::GeometryBatch;
28pub use rendertarget::RenderTargetStore;
29
30use crate::scripting::geometry_ops::GeoCommand;
31use anyhow::Result;
32
33enum RenderOp {
37 Sprites { start: usize, end: usize },
39 Geometry { start: usize, end: usize },
41}
42
43fn build_render_schedule(
48 sprites: &[SpriteCommand],
49 geo: &[GeoCommand],
50) -> Vec<RenderOp> {
51 let mut schedule = Vec::new();
52 let mut si = 0;
53 let mut gi = 0;
54
55 while si < sprites.len() || gi < geo.len() {
56 let do_sprites = if si >= sprites.len() {
59 false
60 } else if gi >= geo.len() {
61 true
62 } else {
63 sprites[si].layer <= geo[gi].layer()
64 };
65
66 if do_sprites {
67 let start = si;
68 let bound = if gi < geo.len() { geo[gi].layer() } else { i32::MAX };
71 while si < sprites.len() && sprites[si].layer <= bound {
72 si += 1;
73 }
74 schedule.push(RenderOp::Sprites { start, end: si });
75 } else {
76 let start = gi;
77 let bound = if si < sprites.len() { sprites[si].layer } else { i32::MAX };
80 while gi < geo.len() && geo[gi].layer() < bound {
81 gi += 1;
82 }
83 schedule.push(RenderOp::Geometry { start, end: gi });
84 }
85 }
86
87 schedule
88}
89
90pub struct Renderer {
92 pub gpu: GpuContext,
93 pub sprites: SpritePipeline,
94 pub geometry: GeometryBatch,
95 pub shaders: ShaderStore,
96 pub postprocess: PostProcessPipeline,
97 pub textures: TextureStore,
98 pub camera: Camera2D,
99 pub lighting: LightingState,
100 pub radiance: RadiancePipeline,
101 pub radiance_state: RadianceState,
102 pub render_targets: RenderTargetStore,
104 pub frame_commands: Vec<SpriteCommand>,
106 pub geo_commands: Vec<GeoCommand>,
108 pub scale_factor: f32,
110 pub clear_color: [f32; 4],
112}
113
114impl Renderer {
115 pub fn new(window: std::sync::Arc<winit::window::Window>) -> Result<Self> {
117 let scale_factor = window.scale_factor() as f32;
118 let gpu = GpuContext::new(window)?;
119 let sprites = SpritePipeline::new(&gpu);
120 let geometry = GeometryBatch::new(&gpu);
121 let shaders = ShaderStore::new(&gpu);
122 let postprocess = PostProcessPipeline::new(&gpu);
123 let radiance_pipeline = RadiancePipeline::new(&gpu);
124 let textures = TextureStore::new();
125 let logical_w = gpu.config.width as f32 / scale_factor;
127 let logical_h = gpu.config.height as f32 / scale_factor;
128 let camera = Camera2D {
129 viewport_size: [logical_w, logical_h],
130 ..Camera2D::default()
131 };
132 Ok(Self {
133 gpu,
134 sprites,
135 geometry,
136 shaders,
137 postprocess,
138 radiance: radiance_pipeline,
139 radiance_state: RadianceState::new(),
140 textures,
141 camera,
142 lighting: LightingState::default(),
143 render_targets: RenderTargetStore::new(),
144 frame_commands: Vec::new(),
145 geo_commands: Vec::new(),
146 scale_factor,
147 clear_color: [0.1, 0.1, 0.15, 1.0],
148 })
149 }
150
151 pub fn set_geo_commands(&mut self, cmds: Vec<GeoCommand>) {
153 self.geo_commands = cmds;
154 }
155
156 pub fn render_frame(&mut self) -> Result<()> {
158 let output = self.gpu.surface.get_current_texture()?;
159 let view = output.texture.create_view(&wgpu::TextureViewDescriptor::default());
160
161 let mut encoder = self.gpu.device.create_command_encoder(
162 &wgpu::CommandEncoderDescriptor { label: Some("frame_encoder") },
163 );
164
165 self.frame_commands.sort_by(|a, b| {
167 a.layer
168 .cmp(&b.layer)
169 .then(a.shader_id.cmp(&b.shader_id))
170 .then(a.blend_mode.cmp(&b.blend_mode))
171 .then(a.texture_id.cmp(&b.texture_id))
172 });
173
174 self.geo_commands.sort_by_key(|c| c.layer());
176
177 let schedule = build_render_schedule(&self.frame_commands, &self.geo_commands);
179
180 self.shaders.flush(&self.gpu.queue);
182
183 let lighting_uniform = self.lighting.to_uniform();
184 let clear_color = wgpu::Color {
185 r: self.clear_color[0] as f64,
186 g: self.clear_color[1] as f64,
187 b: self.clear_color[2] as f64,
188 a: self.clear_color[3] as f64,
189 };
190
191 self.sprites.prepare(&self.gpu.device, &self.gpu.queue, &self.camera, &lighting_uniform);
193
194 let gi_active = self.radiance.compute(
196 &self.gpu,
197 &mut encoder,
198 &self.radiance_state,
199 &self.lighting,
200 self.camera.x,
201 self.camera.y,
202 self.camera.viewport_size[0],
203 self.camera.viewport_size[1],
204 );
205
206 if self.postprocess.has_effects() {
207 {
209 let sprite_target = self.postprocess.sprite_target(&self.gpu);
210 let camera_bg = self.sprites.camera_bind_group();
211
212 if schedule.is_empty() {
213 self.sprites.render(
215 &self.gpu.device, &self.gpu.queue, &self.textures, &self.shaders,
216 &[], sprite_target, &mut encoder, Some(clear_color),
217 );
218 } else {
219 let mut first = true;
220 for op in &schedule {
221 let cc = if first { Some(clear_color) } else { None };
222 first = false;
223 match op {
224 RenderOp::Sprites { start, end } => {
225 self.sprites.render(
226 &self.gpu.device, &self.gpu.queue, &self.textures, &self.shaders,
227 &self.frame_commands[*start..*end],
228 sprite_target, &mut encoder, cc,
229 );
230 }
231 RenderOp::Geometry { start, end } => {
232 self.geometry.flush_commands(
233 &self.gpu.device, &mut encoder, sprite_target,
234 camera_bg, &self.geo_commands[*start..*end], cc,
235 );
236 }
237 }
238 }
239 }
240 }
241 if gi_active {
243 let sprite_target = self.postprocess.sprite_target(&self.gpu);
244 self.radiance.compose(&mut encoder, sprite_target);
245 }
246 self.postprocess.apply(&self.gpu, &mut encoder, &view);
247 } else {
248 let camera_bg = self.sprites.camera_bind_group();
250
251 if schedule.is_empty() {
252 self.sprites.render(
254 &self.gpu.device, &self.gpu.queue, &self.textures, &self.shaders,
255 &[], &view, &mut encoder, Some(clear_color),
256 );
257 } else {
258 let mut first = true;
259 for op in &schedule {
260 let cc = if first { Some(clear_color) } else { None };
261 first = false;
262 match op {
263 RenderOp::Sprites { start, end } => {
264 self.sprites.render(
265 &self.gpu.device, &self.gpu.queue, &self.textures, &self.shaders,
266 &self.frame_commands[*start..*end],
267 &view, &mut encoder, cc,
268 );
269 }
270 RenderOp::Geometry { start, end } => {
271 self.geometry.flush_commands(
272 &self.gpu.device, &mut encoder, &view,
273 camera_bg, &self.geo_commands[*start..*end], cc,
274 );
275 }
276 }
277 }
278 }
279 if gi_active {
281 self.radiance.compose(&mut encoder, &view);
282 }
283 }
284
285 self.gpu.queue.submit(std::iter::once(encoder.finish()));
286 output.present();
287
288 self.frame_commands.clear();
289 self.geo_commands.clear();
290 Ok(())
291 }
292
293 pub fn resize(&mut self, physical_width: u32, physical_height: u32, scale_factor: f32) {
296 if physical_width > 0 && physical_height > 0 {
297 self.scale_factor = scale_factor;
298 self.gpu.config.width = physical_width;
299 self.gpu.config.height = physical_height;
300 self.gpu.surface.configure(&self.gpu.device, &self.gpu.config);
301 self.camera.viewport_size = [
303 physical_width as f32 / scale_factor,
304 physical_height as f32 / scale_factor,
305 ];
306 }
307 }
308
309 pub fn create_render_target(&mut self, id: u32, width: u32, height: u32) {
313 let surface_format = self.gpu.config.format;
314 self.render_targets.create(&self.gpu.device, id, width, height, surface_format);
315 if let Some(view) = self.render_targets.get_view(id) {
316 self.textures.register_render_target(
317 &self.gpu.device,
318 &self.sprites.texture_bind_group_layout,
319 id,
320 view,
321 width,
322 height,
323 );
324 }
325 }
326
327 pub fn destroy_render_target(&mut self, id: u32) {
329 self.render_targets.destroy(id);
330 self.textures.unregister_render_target(id);
331 }
332
333 pub fn render_targets_prepass(
338 &mut self,
339 target_queues: std::collections::HashMap<u32, Vec<SpriteCommand>>,
340 ) {
341 if target_queues.is_empty() {
342 return;
343 }
344
345 let mut encoder = self.gpu.device.create_command_encoder(
346 &wgpu::CommandEncoderDescriptor { label: Some("rt_encoder") },
347 );
348 let lighting_uniform = self.lighting.to_uniform();
349
350 for (target_id, mut cmds) in target_queues {
351 let view = self.render_targets.get_view(target_id);
352 let dims = self.render_targets.get_dims(target_id);
353 if let (Some(view), Some((tw, th))) = (view, dims) {
354 cmds.sort_by(|a, b| {
356 a.layer
357 .cmp(&b.layer)
358 .then(a.shader_id.cmp(&b.shader_id))
359 .then(a.blend_mode.cmp(&b.blend_mode))
360 .then(a.texture_id.cmp(&b.texture_id))
361 });
362 let target_camera = Camera2D {
364 x: tw as f32 / 2.0,
365 y: th as f32 / 2.0,
366 zoom: 1.0,
367 viewport_size: [tw as f32, th as f32],
368 ..Camera2D::default()
369 };
370 self.sprites.prepare(&self.gpu.device, &self.gpu.queue, &target_camera, &lighting_uniform);
371 self.sprites.render(
372 &self.gpu.device,
373 &self.gpu.queue,
374 &self.textures,
375 &self.shaders,
376 &cmds,
377 view,
378 &mut encoder,
379 Some(wgpu::Color { r: 0.0, g: 0.0, b: 0.0, a: 0.0 }),
380 );
381 }
382 }
383
384 self.gpu.queue.submit(std::iter::once(encoder.finish()));
385 }
386}