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 sdf;
15pub mod test_harness;
17
18pub use gpu::GpuContext;
19pub use sprite::{SpriteCommand, SpritePipeline};
20pub use texture::{TextureId, TextureStore};
21pub use camera::Camera2D;
22pub use tilemap::{Tilemap, TilemapStore};
23pub use lighting::{LightingState, LightingUniform, PointLight, LightData, MAX_LIGHTS};
24pub use msdf::{MsdfFont, MsdfFontStore, MsdfGlyph};
25pub use shader::ShaderStore;
26pub use postprocess::PostProcessPipeline;
27pub use radiance::{RadiancePipeline, RadianceState, EmissiveSurface, Occluder, DirectionalLight, SpotLight};
28pub use geometry::GeometryBatch;
29pub use rendertarget::RenderTargetStore;
30pub use sdf::{SdfPipelineStore, SdfCommand, SdfFill};
31
32use crate::scripting::geometry_ops::GeoCommand;
33use crate::scripting::sdf_ops::SdfDrawCommand;
34use anyhow::Result;
35
36enum RenderOp {
40 Sprites { start: usize, end: usize },
42 Geometry { start: usize, end: usize },
44 Sdf { start: usize, end: usize },
46}
47
48fn build_render_schedule(
53 sprites: &[SpriteCommand],
54 geo: &[GeoCommand],
55 sdf: &[SdfCommand],
56) -> Vec<RenderOp> {
57 let mut schedule = Vec::new();
58 let mut si = 0;
59 let mut gi = 0;
60 let mut di = 0;
61
62 while si < sprites.len() || gi < geo.len() || di < sdf.len() {
63 let sprite_layer = if si < sprites.len() { sprites[si].layer } else { i32::MAX };
65 let geo_layer = if gi < geo.len() { geo[gi].layer() } else { i32::MAX };
66 let sdf_layer = if di < sdf.len() { sdf[di].layer } else { i32::MAX };
67
68 let min_layer = sprite_layer.min(geo_layer).min(sdf_layer);
70
71 if sprite_layer == min_layer {
73 let start = si;
74 let bound = geo_layer.min(sdf_layer);
76 while si < sprites.len() && sprites[si].layer <= bound {
77 si += 1;
78 }
79 schedule.push(RenderOp::Sprites { start, end: si });
80 } else if geo_layer == min_layer {
81 let start = gi;
82 let sprite_bound = if si < sprites.len() { sprites[si].layer } else { i32::MAX };
85 let sdf_bound = if di < sdf.len() { sdf[di].layer } else { i32::MAX };
86 while gi < geo.len() && geo[gi].layer() < sprite_bound && geo[gi].layer() <= sdf_bound {
87 gi += 1;
88 }
89 schedule.push(RenderOp::Geometry { start, end: gi });
90 } else {
91 let start = di;
92 let sprite_bound = if si < sprites.len() { sprites[si].layer } else { i32::MAX };
94 let geo_bound = if gi < geo.len() { geo[gi].layer() } else { i32::MAX };
95 while di < sdf.len() && sdf[di].layer < sprite_bound && sdf[di].layer < geo_bound {
96 di += 1;
97 }
98 schedule.push(RenderOp::Sdf { start, end: di });
99 }
100 }
101
102 schedule
103}
104
105pub struct Renderer {
107 pub gpu: GpuContext,
108 pub sprites: SpritePipeline,
109 pub geometry: GeometryBatch,
110 pub shaders: ShaderStore,
111 pub postprocess: PostProcessPipeline,
112 pub textures: TextureStore,
113 pub camera: Camera2D,
114 pub lighting: LightingState,
115 pub radiance: RadiancePipeline,
116 pub radiance_state: RadianceState,
117 pub render_targets: RenderTargetStore,
119 pub frame_commands: Vec<SpriteCommand>,
121 pub geo_commands: Vec<GeoCommand>,
123 pub sdf_commands: Vec<SdfCommand>,
125 pub sdf_pipeline: SdfPipelineStore,
127 pub scale_factor: f32,
129 pub clear_color: [f32; 4],
131}
132
133impl Renderer {
134 pub fn new(window: std::sync::Arc<winit::window::Window>) -> Result<Self> {
136 let scale_factor = window.scale_factor() as f32;
137 let gpu = GpuContext::new(window)?;
138 let sprites = SpritePipeline::new(&gpu);
139 let geometry = GeometryBatch::new(&gpu);
140 let shaders = ShaderStore::new(&gpu);
141 let postprocess = PostProcessPipeline::new(&gpu);
142 let sdf_pipeline = SdfPipelineStore::new(&gpu);
143 let radiance_pipeline = RadiancePipeline::new(&gpu);
144 let textures = TextureStore::new();
145 let logical_w = gpu.config.width as f32 / scale_factor;
147 let logical_h = gpu.config.height as f32 / scale_factor;
148 let camera = Camera2D {
149 viewport_size: [logical_w, logical_h],
150 ..Camera2D::default()
151 };
152 Ok(Self {
153 gpu,
154 sprites,
155 geometry,
156 shaders,
157 postprocess,
158 radiance: radiance_pipeline,
159 radiance_state: RadianceState::new(),
160 textures,
161 camera,
162 lighting: LightingState::default(),
163 render_targets: RenderTargetStore::new(),
164 frame_commands: Vec::new(),
165 geo_commands: Vec::new(),
166 sdf_commands: Vec::new(),
167 sdf_pipeline,
168 scale_factor,
169 clear_color: [0.1, 0.1, 0.15, 1.0],
170 })
171 }
172
173 pub fn set_geo_commands(&mut self, cmds: Vec<GeoCommand>) {
175 self.geo_commands = cmds;
176 }
177
178 pub fn set_sdf_commands(&mut self, cmds: Vec<SdfDrawCommand>) {
181 self.sdf_commands = cmds.into_iter().map(|c| {
182 let fill = match c.fill_type {
183 0 => SdfFill::Solid { color: c.color },
184 1 => SdfFill::Outline { color: c.color, thickness: c.fill_param },
185 2 => SdfFill::SolidWithOutline { fill: c.color, outline: c.color2, thickness: c.fill_param },
186 3 => SdfFill::Gradient { from: c.color, to: c.color2, angle: c.fill_param, scale: c.gradient_scale },
187 4 => SdfFill::Glow { color: c.color, intensity: c.fill_param },
188 5 => SdfFill::CosinePalette {
189 a: [c.palette_params[0], c.palette_params[1], c.palette_params[2]],
190 b: [c.palette_params[3], c.palette_params[4], c.palette_params[5]],
191 c: [c.palette_params[6], c.palette_params[7], c.palette_params[8]],
192 d: [c.palette_params[9], c.palette_params[10], c.palette_params[11]],
193 },
194 _ => SdfFill::Solid { color: c.color },
195 };
196 SdfCommand {
197 sdf_expr: c.sdf_expr,
198 fill,
199 x: c.x,
200 y: c.y,
201 bounds: c.bounds,
202 layer: c.layer,
203 rotation: c.rotation,
204 scale: c.scale,
205 opacity: c.opacity,
206 }
207 }).collect();
208 }
209
210 pub fn render_frame(&mut self) -> Result<()> {
212 let output = self.gpu.surface.get_current_texture()?;
213 let view = output.texture.create_view(&wgpu::TextureViewDescriptor::default());
214
215 let mut encoder = self.gpu.device.create_command_encoder(
216 &wgpu::CommandEncoderDescriptor { label: Some("frame_encoder") },
217 );
218
219 self.frame_commands.sort_by(|a, b| {
221 a.layer
222 .cmp(&b.layer)
223 .then(a.shader_id.cmp(&b.shader_id))
224 .then(a.blend_mode.cmp(&b.blend_mode))
225 .then(a.texture_id.cmp(&b.texture_id))
226 });
227
228 self.geo_commands.sort_by_key(|c| c.layer());
230
231 self.sdf_commands.sort_by_key(|c| c.layer);
233
234 let schedule = build_render_schedule(&self.frame_commands, &self.geo_commands, &self.sdf_commands);
236
237 self.shaders.flush(&self.gpu.queue);
239
240 let lighting_uniform = self.lighting.to_uniform();
241 let clear_color = wgpu::Color {
242 r: self.clear_color[0] as f64,
243 g: self.clear_color[1] as f64,
244 b: self.clear_color[2] as f64,
245 a: self.clear_color[3] as f64,
246 };
247
248 self.sprites.prepare(&self.gpu.device, &self.gpu.queue, &self.camera, &lighting_uniform);
250 self.sdf_pipeline.prepare(&self.gpu.queue, &self.camera, 0.0);
251
252 let gi_active = self.radiance.compute(
254 &self.gpu,
255 &mut encoder,
256 &self.radiance_state,
257 &self.lighting,
258 self.camera.x,
259 self.camera.y,
260 self.camera.viewport_size[0],
261 self.camera.viewport_size[1],
262 );
263
264 if self.postprocess.has_effects() {
265 {
267 let sprite_target = self.postprocess.sprite_target(&self.gpu);
268 let camera_bg = self.sprites.camera_bind_group();
269
270 if schedule.is_empty() {
271 self.sprites.render(
273 &self.gpu.device, &self.gpu.queue, &self.textures, &self.shaders,
274 &[], sprite_target, &mut encoder, Some(clear_color),
275 );
276 } else {
277 let mut first = true;
278 for op in &schedule {
279 let cc = if first { Some(clear_color) } else { None };
280 first = false;
281 match op {
282 RenderOp::Sprites { start, end } => {
283 self.sprites.render(
284 &self.gpu.device, &self.gpu.queue, &self.textures, &self.shaders,
285 &self.frame_commands[*start..*end],
286 sprite_target, &mut encoder, cc,
287 );
288 }
289 RenderOp::Geometry { start, end } => {
290 self.geometry.flush_commands(
291 &self.gpu.device, &mut encoder, sprite_target,
292 camera_bg, &self.geo_commands[*start..*end], cc,
293 );
294 }
295 RenderOp::Sdf { start, end } => {
296 self.sdf_pipeline.render(
297 &self.gpu.device, &mut encoder, sprite_target,
298 &self.sdf_commands[*start..*end], cc,
299 );
300 }
301 }
302 }
303 }
304 }
305 if gi_active {
307 let sprite_target = self.postprocess.sprite_target(&self.gpu);
308 self.radiance.compose(&mut encoder, sprite_target);
309 }
310 self.postprocess.apply(&self.gpu, &mut encoder, &view);
311 } else {
312 let camera_bg = self.sprites.camera_bind_group();
314
315 if schedule.is_empty() {
316 self.sprites.render(
318 &self.gpu.device, &self.gpu.queue, &self.textures, &self.shaders,
319 &[], &view, &mut encoder, Some(clear_color),
320 );
321 } else {
322 let mut first = true;
323 for op in &schedule {
324 let cc = if first { Some(clear_color) } else { None };
325 first = false;
326 match op {
327 RenderOp::Sprites { start, end } => {
328 self.sprites.render(
329 &self.gpu.device, &self.gpu.queue, &self.textures, &self.shaders,
330 &self.frame_commands[*start..*end],
331 &view, &mut encoder, cc,
332 );
333 }
334 RenderOp::Geometry { start, end } => {
335 self.geometry.flush_commands(
336 &self.gpu.device, &mut encoder, &view,
337 camera_bg, &self.geo_commands[*start..*end], cc,
338 );
339 }
340 RenderOp::Sdf { start, end } => {
341 self.sdf_pipeline.render(
342 &self.gpu.device, &mut encoder, &view,
343 &self.sdf_commands[*start..*end], cc,
344 );
345 }
346 }
347 }
348 }
349 if gi_active {
351 self.radiance.compose(&mut encoder, &view);
352 }
353 }
354
355 self.gpu.queue.submit(std::iter::once(encoder.finish()));
356 output.present();
357
358 self.frame_commands.clear();
359 self.geo_commands.clear();
360 self.sdf_commands.clear();
361 Ok(())
362 }
363
364 pub fn resize(&mut self, physical_width: u32, physical_height: u32, scale_factor: f32) {
367 if physical_width > 0 && physical_height > 0 {
368 self.scale_factor = scale_factor;
369 self.gpu.config.width = physical_width;
370 self.gpu.config.height = physical_height;
371 self.gpu.surface.configure(&self.gpu.device, &self.gpu.config);
372 self.camera.viewport_size = [
374 physical_width as f32 / scale_factor,
375 physical_height as f32 / scale_factor,
376 ];
377 }
378 }
379
380 pub fn create_render_target(&mut self, id: u32, width: u32, height: u32) {
384 let surface_format = self.gpu.config.format;
385 self.render_targets.create(&self.gpu.device, id, width, height, surface_format);
386 if let Some(view) = self.render_targets.get_view(id) {
387 self.textures.register_render_target(
388 &self.gpu.device,
389 &self.sprites.texture_bind_group_layout,
390 id,
391 view,
392 width,
393 height,
394 );
395 }
396 }
397
398 pub fn destroy_render_target(&mut self, id: u32) {
400 self.render_targets.destroy(id);
401 self.textures.unregister_render_target(id);
402 }
403
404 pub fn render_targets_prepass(
409 &mut self,
410 target_queues: std::collections::HashMap<u32, Vec<SpriteCommand>>,
411 ) {
412 if target_queues.is_empty() {
413 return;
414 }
415
416 let mut encoder = self.gpu.device.create_command_encoder(
417 &wgpu::CommandEncoderDescriptor { label: Some("rt_encoder") },
418 );
419 let lighting_uniform = self.lighting.to_uniform();
420
421 for (target_id, mut cmds) in target_queues {
422 let view = self.render_targets.get_view(target_id);
423 let dims = self.render_targets.get_dims(target_id);
424 if let (Some(view), Some((tw, th))) = (view, dims) {
425 cmds.sort_by(|a, b| {
427 a.layer
428 .cmp(&b.layer)
429 .then(a.shader_id.cmp(&b.shader_id))
430 .then(a.blend_mode.cmp(&b.blend_mode))
431 .then(a.texture_id.cmp(&b.texture_id))
432 });
433 let target_camera = Camera2D {
435 x: tw as f32 / 2.0,
436 y: th as f32 / 2.0,
437 zoom: 1.0,
438 viewport_size: [tw as f32, th as f32],
439 ..Camera2D::default()
440 };
441 self.sprites.prepare(&self.gpu.device, &self.gpu.queue, &target_camera, &lighting_uniform);
442 self.sprites.render(
443 &self.gpu.device,
444 &self.gpu.queue,
445 &self.textures,
446 &self.shaders,
447 &cmds,
448 view,
449 &mut encoder,
450 Some(wgpu::Color { r: 0.0, g: 0.0, b: 0.0, a: 0.0 }),
451 );
452 }
453 }
454
455 self.gpu.queue.submit(std::iter::once(encoder.finish()));
456 }
457}