1use std::cell::Cell;
22use glow::HasContext;
23use crate::render::shaders::{FULLSCREEN_VERT, BLOOM_FRAG, COMPOSITE_FRAG, UPSAMPLE_FRAG, FXAA_FRAG, LIGHT_FRAG, PERSIST_FRAG};
24use crate::render::screen_fx::{ScreenFx, MAX_SHOCKWAVES};
25use crate::config::RenderConfig;
26
27pub struct PostFxPipeline {
28 pub scene_fbo: glow::Framebuffer,
30 pub scene_color_tex: glow::Texture, pub scene_emission_tex: glow::Texture, pub scene_occluder_tex: glow::Texture, scene_w: u32,
36 scene_h: u32,
37
38 bloom_fbo: Vec<glow::Framebuffer>,
44 bloom_tex: Vec<glow::Texture>,
45
46 mip_fbo: Vec<glow::Framebuffer>,
52 mip_tex: Vec<glow::Texture>,
53 mip_size: Vec<(i32, i32)>,
54 up_fbo: Vec<glow::Framebuffer>,
56 up_tex: Vec<glow::Texture>,
57 upsample_prog: glow::Program,
58
59 light_fbo: glow::Framebuffer,
61 light_tex: glow::Texture,
62 light_size: (i32, i32),
63 light_prog: glow::Program,
64
65 history_fbo: [glow::Framebuffer; 2],
68 history_tex: [glow::Texture; 2],
69 history_idx: usize,
70 history_valid: bool,
71 persist_prog: glow::Program,
72
73 ldr_fbo: glow::Framebuffer,
75 ldr_tex: glow::Texture,
76 fxaa_prog: glow::Program,
77
78 bloom_prog: glow::Program,
79 composite_prog: glow::Program,
80
81 auto_shaft: Cell<([f32; 4], [f32; 3])>,
85 readback: std::cell::RefCell<Vec<f32>>,
87
88 fullscreen_vao: glow::VertexArray,
90}
91
92fn scaled(width: u32, height: u32, scale: f32) -> (u32, u32) {
95 let s = if scale.is_finite() && scale > 0.0 { scale } else { 1.0 };
96 (
97 ((width as f32 * s).round() as u32).max(1),
98 ((height as f32 * s).round() as u32).max(1),
99 )
100}
101
102impl PostFxPipeline {
103 pub unsafe fn new(gl: &glow::Context, width: u32, height: u32, render_scale: f32) -> Self {
104 let bloom_prog = compile_postfx_program(gl, FULLSCREEN_VERT, BLOOM_FRAG);
105 let composite_prog = compile_postfx_program(gl, FULLSCREEN_VERT, COMPOSITE_FRAG);
106 let upsample_prog = compile_postfx_program(gl, FULLSCREEN_VERT, UPSAMPLE_FRAG);
107 let fxaa_prog = compile_postfx_program(gl, FULLSCREEN_VERT, FXAA_FRAG);
108 let light_prog = compile_postfx_program(gl, FULLSCREEN_VERT, LIGHT_FRAG);
109 let persist_prog = compile_postfx_program(gl, FULLSCREEN_VERT, PERSIST_FRAG);
110 let fullscreen_vao = gl.create_vertex_array().expect("postfx fullscreen_vao");
111
112 gl.use_program(Some(bloom_prog));
114 set_u_i32(gl, bloom_prog, "u_texture", 0);
115
116 gl.use_program(Some(composite_prog));
117 set_u_i32(gl, composite_prog, "u_scene", 0);
118 set_u_i32(gl, composite_prog, "u_bloom", 1);
119 set_u_i32(gl, composite_prog, "u_light", 2);
120 set_u_i32(gl, composite_prog, "u_emission", 3);
121
122 gl.use_program(Some(light_prog));
123 set_u_i32(gl, light_prog, "u_occluder", 0);
124
125 gl.use_program(Some(persist_prog));
126 set_u_i32(gl, persist_prog, "u_scene", 0);
127 set_u_i32(gl, persist_prog, "u_history", 1);
128
129 gl.use_program(Some(upsample_prog));
130 set_u_i32(gl, upsample_prog, "u_lower", 0);
131 set_u_i32(gl, upsample_prog, "u_higher", 1);
132
133 gl.use_program(Some(fxaa_prog));
134 set_u_i32(gl, fxaa_prog, "u_image", 0);
135
136 let (scene_w, scene_h) = scaled(width, height, render_scale);
137 let (scene_fbo, scene_color_tex, scene_emission_tex, scene_occluder_tex) =
138 create_scene_fbo(gl, scene_w, scene_h);
139 let (bloom_fbo, bloom_tex, _) = create_pyramid(gl, scene_w, scene_h);
140 let (mip_fbo, mip_tex, mip_size) = create_pyramid(gl, scene_w, scene_h);
141 let (up_fbo, up_tex, _) = create_pyramid(gl, scene_w, scene_h);
142 let (ldr_fbo, ldr_tex) = create_ldr_fbo(gl, width, height);
143 let (lw, lh) = ((scene_w / 2).max(1), (scene_h / 2).max(1));
144 let (light_fbo, light_tex) = create_hdr_fbo(gl, lw, lh, "light");
145 let (h0, t0) = create_hdr_fbo(gl, scene_w, scene_h, "history 0");
146 let (h1, t1) = create_hdr_fbo(gl, scene_w, scene_h, "history 1");
147
148 Self {
149 scene_fbo,
150 scene_color_tex,
151 scene_emission_tex,
152 scene_occluder_tex,
153 scene_w,
154 scene_h,
155 light_fbo,
156 light_tex,
157 light_size: (lw as i32, lh as i32),
158 light_prog,
159 history_fbo: [h0, h1],
160 history_tex: [t0, t1],
161 history_idx: 0,
162 history_valid: false,
163 persist_prog,
164 bloom_fbo,
165 bloom_tex,
166 mip_fbo,
167 mip_tex,
168 mip_size,
169 up_fbo,
170 up_tex,
171 upsample_prog,
172 ldr_fbo,
173 ldr_tex,
174 fxaa_prog,
175 bloom_prog,
176 composite_prog,
177 auto_shaft: Cell::new(([0.5, 0.5, 0.0, 0.0], [1.0, 1.0, 1.0])),
178 readback: std::cell::RefCell::new(Vec::new()),
179 fullscreen_vao,
180 }
181 }
182
183 unsafe fn read_auto_shaft(&self, gl: &glow::Context) {
186 let Some(top) = self.mip_fbo.len().checked_sub(1) else { return };
187 let (w, h) = self.mip_size[top];
188 let n = (w * h * 4) as usize;
189 let mut buf = self.readback.borrow_mut();
190 if buf.len() != n {
191 buf.resize(n, 0.0);
192 }
193 gl.bind_framebuffer(glow::FRAMEBUFFER, Some(self.mip_fbo[top]));
194 gl.read_buffer(glow::COLOR_ATTACHMENT0);
195 gl.pixel_store_i32(glow::PACK_ALIGNMENT, 1);
196 gl.read_pixels(
197 0, 0, w, h,
198 glow::RGBA, glow::FLOAT,
199 glow::PixelPackData::Slice(Some(bytemuck::cast_slice_mut(&mut buf[..]))),
200 );
201 let mut best = 0.0f32;
202 let mut best_i = 0usize;
203 for i in 0..(w * h) as usize {
204 let r = buf[i * 4];
205 let g = buf[i * 4 + 1];
206 let b = buf[i * 4 + 2];
207 let lum = r * 0.2126 + g * 0.7152 + b * 0.0722;
208 if lum > best {
209 best = lum;
210 best_i = i;
211 }
212 }
213 if best <= 1e-4 {
214 self.auto_shaft.set(([0.5, 0.5, 0.0, 0.0], [1.0, 1.0, 1.0]));
215 return;
216 }
217 let x = (best_i as i32 % w) as f32 + 0.5;
218 let y = (best_i as i32 / w) as f32 + 0.5;
219 let r = buf[best_i * 4];
220 let g = buf[best_i * 4 + 1];
221 let b = buf[best_i * 4 + 2];
222 let m = r.max(g).max(b).max(1e-4);
223 let strength = (best * 6.0).clamp(0.0, 1.0);
226 self.auto_shaft.set(([x / w as f32, y / h as f32, strength, 0.0], [r / m, g / m, b / m]));
227 }
228
229 pub fn scene_size(&self) -> (u32, u32) {
232 (self.scene_w, self.scene_h)
233 }
234
235 pub unsafe fn resize(&mut self, gl: &glow::Context, width: u32, height: u32, render_scale: f32) {
237 gl.delete_framebuffer(self.scene_fbo);
238 gl.delete_texture(self.scene_color_tex);
239 gl.delete_texture(self.scene_emission_tex);
240 gl.delete_texture(self.scene_occluder_tex);
241 gl.delete_framebuffer(self.light_fbo);
242 gl.delete_texture(self.light_tex);
243 for i in 0..2 {
244 gl.delete_framebuffer(self.history_fbo[i]);
245 gl.delete_texture(self.history_tex[i]);
246 }
247 for f in self.bloom_fbo.drain(..) {
248 gl.delete_framebuffer(f);
249 }
250 for t in self.bloom_tex.drain(..) {
251 gl.delete_texture(t);
252 }
253 for f in self.mip_fbo.drain(..) {
254 gl.delete_framebuffer(f);
255 }
256 for t in self.mip_tex.drain(..) {
257 gl.delete_texture(t);
258 }
259 for f in self.up_fbo.drain(..) {
260 gl.delete_framebuffer(f);
261 }
262 for t in self.up_tex.drain(..) {
263 gl.delete_texture(t);
264 }
265 gl.delete_framebuffer(self.ldr_fbo);
266 gl.delete_texture(self.ldr_tex);
267
268 let (scene_w, scene_h) = scaled(width, height, render_scale);
269 self.scene_w = scene_w;
270 self.scene_h = scene_h;
271
272 let (mip_fbo, mip_tex, mip_size) = create_pyramid(gl, scene_w, scene_h);
273 let (up_fbo, up_tex, _) = create_pyramid(gl, scene_w, scene_h);
274 self.mip_fbo = mip_fbo;
275 self.mip_tex = mip_tex;
276 self.mip_size = mip_size;
277 self.up_fbo = up_fbo;
278 self.up_tex = up_tex;
279
280 let (scene_fbo, scene_color_tex, scene_emission_tex, scene_occluder_tex) =
281 create_scene_fbo(gl, scene_w, scene_h);
282 let (bloom_fbo, bloom_tex, _) = create_pyramid(gl, scene_w, scene_h);
283 let (ldr_fbo, ldr_tex) = create_ldr_fbo(gl, width, height);
284 let (lw, lh) = ((scene_w / 2).max(1), (scene_h / 2).max(1));
285 let (light_fbo, light_tex) = create_hdr_fbo(gl, lw, lh, "light");
286 let (h0, t0) = create_hdr_fbo(gl, scene_w, scene_h, "history 0");
287 let (h1, t1) = create_hdr_fbo(gl, scene_w, scene_h, "history 1");
288 self.light_fbo = light_fbo;
289 self.light_tex = light_tex;
290 self.light_size = (lw as i32, lh as i32);
291 self.history_fbo = [h0, h1];
292 self.history_tex = [t0, t1];
293 self.history_valid = false;
294
295 self.scene_fbo = scene_fbo;
296 self.scene_color_tex = scene_color_tex;
297 self.scene_emission_tex = scene_emission_tex;
298 self.scene_occluder_tex = scene_occluder_tex;
299 self.bloom_fbo = bloom_fbo;
300 self.bloom_tex = bloom_tex;
301 self.ldr_fbo = ldr_fbo;
302 self.ldr_tex = ldr_tex;
303 }
304
305 pub unsafe fn run(
308 &mut self,
309 gl: &glow::Context,
310 config: &RenderConfig,
311 fx: &ScreenFx,
312 full_w: u32,
313 full_h: u32,
314 time: f32,
315 ) -> u32 {
316 let mut draws = 0u32;
317 gl.bind_vertex_array(Some(self.fullscreen_vao));
318 gl.disable(glow::BLEND);
319
320 let mut scene_tex = self.scene_color_tex;
325 if config.persistence > 0.0 {
326 let next = self.history_idx ^ 1;
327 gl.bind_framebuffer(glow::FRAMEBUFFER, Some(self.history_fbo[next]));
328 gl.viewport(0, 0, self.scene_w as i32, self.scene_h as i32);
329 gl.use_program(Some(self.persist_prog));
330 gl.active_texture(glow::TEXTURE0);
331 gl.bind_texture(glow::TEXTURE_2D, Some(self.scene_color_tex));
332 gl.active_texture(glow::TEXTURE1);
333 gl.bind_texture(glow::TEXTURE_2D, Some(self.history_tex[self.history_idx]));
334 let keep = if self.history_valid { config.persistence } else { 0.0 };
335 set_u_f32(gl, self.persist_prog, "u_persistence", keep);
336 gl.draw_arrays(glow::TRIANGLES, 0, 3);
337 gl.active_texture(glow::TEXTURE0);
338 self.history_idx = next;
339 self.history_valid = true;
340 scene_tex = self.history_tex[next];
341 draws += 1;
342 } else {
343 self.history_valid = false;
344 }
345
346 let lighting = fx.lighting_active();
348 if lighting {
349 let (lw, lh) = self.light_size;
350 gl.bind_framebuffer(glow::FRAMEBUFFER, Some(self.light_fbo));
351 gl.viewport(0, 0, lw, lh);
352 gl.use_program(Some(self.light_prog));
353 gl.active_texture(glow::TEXTURE0);
354 gl.bind_texture(glow::TEXTURE_2D, Some(self.scene_occluder_tex));
355 let p = self.light_prog;
356 set_u_vec2(gl, p, "u_screen", [full_w as f32, full_h as f32]);
357 set_u_vec3(gl, p, "u_ambient", fx.ambient.to_array());
358 set_u_f32(gl, p, "u_shadow_density", fx.shadow_density.max(0.0));
359 let (pos, col, n) = fx.pack_lights(full_w as f32, full_h as f32);
360 set_u_i32(gl, p, "u_count", n as i32);
361 if n > 0 {
362 if let Some(loc) = gl.get_uniform_location(p, "u_light_pos[0]") {
363 gl.uniform_4_f32_slice(Some(&loc), &pos);
364 }
365 if let Some(loc) = gl.get_uniform_location(p, "u_light_color[0]") {
366 gl.uniform_4_f32_slice(Some(&loc), &col);
367 }
368 }
369 gl.draw_arrays(glow::TRIANGLES, 0, 3);
370 draws += 1;
371 }
372
373 if config.bloom_enabled && !self.mip_fbo.is_empty() {
379 let radius = config.bloom_radius.max(0.5);
380 gl.clear_color(0.0, 0.0, 0.0, 0.0);
382 gl.use_program(Some(self.bloom_prog));
383 gl.active_texture(glow::TEXTURE0);
384 set_u_f32(gl, self.bloom_prog, "u_threshold", config.bloom_threshold);
385 set_u_f32(gl, self.bloom_prog, "u_knee", config.bloom_knee.max(1e-3));
386 set_u_f32(gl, self.bloom_prog, "u_stretch", 1.0);
387
388 for level in 0..self.mip_fbo.len() {
390 let (w, h) = self.mip_size[level];
391 let source = if level == 0 {
392 self.scene_emission_tex
393 } else {
394 self.mip_tex[level - 1]
395 };
396
397 gl.bind_framebuffer(glow::FRAMEBUFFER, Some(self.bloom_fbo[level]));
398 gl.viewport(0, 0, w, h);
399 gl.clear(glow::COLOR_BUFFER_BIT);
400 gl.bind_texture(glow::TEXTURE_2D, Some(source));
401 set_u_bool(gl, self.bloom_prog, "u_prefilter", level == 0);
402 set_u_bool(gl, self.bloom_prog, "u_horizontal", true);
403 set_u_f32(gl, self.bloom_prog, "u_radius", radius);
404 gl.draw_arrays(glow::TRIANGLES, 0, 3);
405
406 gl.bind_framebuffer(glow::FRAMEBUFFER, Some(self.mip_fbo[level]));
407 gl.viewport(0, 0, w, h);
408 gl.clear(glow::COLOR_BUFFER_BIT);
409 gl.bind_texture(glow::TEXTURE_2D, Some(self.bloom_tex[level]));
410 set_u_bool(gl, self.bloom_prog, "u_prefilter", false);
411 set_u_bool(gl, self.bloom_prog, "u_horizontal", false);
412 gl.draw_arrays(glow::TRIANGLES, 0, 3);
413 draws += 2;
414 }
415
416 if config.anamorphic > 0.0 && self.mip_fbo.len() > 2 {
418 let level = self.mip_fbo.len() / 2;
419 let (w, h) = self.mip_size[level];
420 gl.bind_framebuffer(glow::FRAMEBUFFER, Some(self.bloom_fbo[level]));
421 gl.viewport(0, 0, w, h);
422 gl.clear(glow::COLOR_BUFFER_BIT);
423 gl.bind_texture(glow::TEXTURE_2D, Some(self.mip_tex[level]));
424 set_u_bool(gl, self.bloom_prog, "u_horizontal", true);
425 set_u_f32(gl, self.bloom_prog, "u_stretch", 9.0 * config.anamorphic);
426 gl.draw_arrays(glow::TRIANGLES, 0, 3);
427
428 gl.bind_framebuffer(glow::FRAMEBUFFER, Some(self.mip_fbo[level]));
429 gl.viewport(0, 0, w, h);
430 gl.clear(glow::COLOR_BUFFER_BIT);
431 gl.bind_texture(glow::TEXTURE_2D, Some(self.bloom_tex[level]));
432 set_u_bool(gl, self.bloom_prog, "u_horizontal", true);
433 set_u_f32(gl, self.bloom_prog, "u_stretch", 16.0 * config.anamorphic);
434 gl.draw_arrays(glow::TRIANGLES, 0, 3);
435 set_u_f32(gl, self.bloom_prog, "u_stretch", 1.0);
436 draws += 2;
437 }
438
439 gl.use_program(Some(self.upsample_prog));
441 let top = self.mip_fbo.len() - 1;
442 gl.bind_framebuffer(glow::FRAMEBUFFER, Some(self.up_fbo[top]));
443 let (w, h) = self.mip_size[top];
444 gl.viewport(0, 0, w, h);
445 gl.clear(glow::COLOR_BUFFER_BIT);
446 gl.active_texture(glow::TEXTURE0);
447 gl.bind_texture(glow::TEXTURE_2D, Some(self.mip_tex[top]));
448 gl.active_texture(glow::TEXTURE1);
449 gl.bind_texture(glow::TEXTURE_2D, Some(self.mip_tex[top]));
450 set_u_f32(gl, self.upsample_prog, "u_radius", 1.0);
451 set_u_f32(gl, self.upsample_prog, "u_strength", 0.0);
452 gl.draw_arrays(glow::TRIANGLES, 0, 3);
453 draws += 1;
454
455 if fx.auto_shafts && config.light_shafts > 0.0 {
456 self.read_auto_shaft(gl);
457 }
458
459 for level in (0..top).rev() {
460 let (w, h) = self.mip_size[level];
461 gl.bind_framebuffer(glow::FRAMEBUFFER, Some(self.up_fbo[level]));
462 gl.viewport(0, 0, w, h);
463 gl.clear(glow::COLOR_BUFFER_BIT);
464 gl.active_texture(glow::TEXTURE0);
465 gl.bind_texture(glow::TEXTURE_2D, Some(self.up_tex[level + 1]));
466 gl.active_texture(glow::TEXTURE1);
467 gl.bind_texture(glow::TEXTURE_2D, Some(self.mip_tex[level]));
468 set_u_f32(gl, self.upsample_prog, "u_radius", radius);
469 let up = (level + 1) as f32 / top.max(1) as f32;
470 set_u_f32(gl, self.upsample_prog, "u_strength", 0.55 + up * 0.30);
471 gl.draw_arrays(glow::TRIANGLES, 0, 3);
472 draws += 1;
473 }
474 gl.active_texture(glow::TEXTURE0);
475 }
476
477 let aa = config.fxaa;
479 if aa {
480 gl.bind_framebuffer(glow::FRAMEBUFFER, Some(self.ldr_fbo));
481 } else {
482 gl.bind_framebuffer(glow::FRAMEBUFFER, None);
483 }
484 gl.viewport(0, 0, full_w as i32, full_h as i32);
485 gl.clear(glow::COLOR_BUFFER_BIT);
486 gl.use_program(Some(self.composite_prog));
487
488 gl.active_texture(glow::TEXTURE0);
489 gl.bind_texture(glow::TEXTURE_2D, Some(scene_tex));
490 gl.active_texture(glow::TEXTURE2);
491 gl.bind_texture(glow::TEXTURE_2D, Some(self.light_tex));
492 gl.active_texture(glow::TEXTURE3);
493 gl.bind_texture(glow::TEXTURE_2D, Some(self.scene_emission_tex));
494
495 gl.active_texture(glow::TEXTURE1);
499 if config.bloom_enabled && !self.up_tex.is_empty() {
500 gl.bind_texture(glow::TEXTURE_2D, Some(self.up_tex[0]));
501 } else {
502 gl.bind_texture(glow::TEXTURE_2D, Some(self.scene_emission_tex));
503 }
504
505 let bloom_intensity = if config.bloom_enabled { config.bloom_intensity } else { 0.0 };
506 let p = self.composite_prog;
507 set_u_f32(gl, p, "u_bloom_intensity", bloom_intensity);
508 set_u_f32(gl, p, "u_exposure", config.exposure);
509 set_u_f32(gl, p, "u_tonemap", config.tonemap);
510 set_u_f32(gl, p, "u_halation", config.halation);
511 set_u_vec3(gl, p, "u_tint", config.tint);
512 set_u_vec3(gl, p, "u_lift", config.lift);
513 set_u_vec3(gl, p, "u_gain", config.gain);
514 set_u_f32(gl, p, "u_saturation", config.saturation);
515 set_u_f32(gl, p, "u_contrast", config.contrast);
516 set_u_f32(gl, p, "u_brightness", config.brightness);
517 set_u_f32(gl, p, "u_vignette", config.vignette);
518 set_u_f32(gl, p, "u_vignette_softness", config.vignette_softness);
519 set_u_f32(gl, p, "u_sharpen", config.sharpen);
520 set_u_f32(gl, p, "u_dither", config.dither);
521 set_u_f32(gl, p, "u_barrel", config.barrel);
522 set_u_f32(gl, p, "u_grain_intensity", config.film_grain);
523 set_u_f32(gl, p, "u_grain_seed", time);
524 set_u_f32(gl, p, "u_chromatic", config.chromatic_aberration);
525 set_u_f32(gl, p, "u_scanline_intensity",
526 if config.scanlines_enabled { config.scanline_intensity } else { 0.0 });
527 set_u_bool(gl, p, "u_scanlines_enabled", config.scanlines_enabled);
528
529 let (fw, fh) = (full_w as f32, full_h as f32);
531 set_u_vec2(gl, p, "u_screen", [fw, fh]);
532 set_u_f32(gl, p, "u_indirect", config.indirect_light);
533 set_u_f32(gl, p, "u_light_shafts", config.light_shafts);
534 let explicit = fx.pack_shaft(fw, fh);
536 if explicit[2] > 0.0 || !fx.auto_shafts || !config.bloom_enabled {
537 set_u_vec3(gl, p, "u_shaft", explicit);
538 set_u_vec3(gl, p, "u_shaft_tint", fx.shaft_tint.to_array());
539 } else {
540 let (src, tint) = self.auto_shaft.get();
541 set_u_vec3(gl, p, "u_shaft", [src[0], src[1], src[2]]);
542 set_u_vec3(gl, p, "u_shaft_tint", tint);
543 }
544 let (refl, blur) = fx.pack_reflection(fw, fh);
545 if let Some(loc) = gl.get_uniform_location(p, "u_reflection") {
546 gl.uniform_4_f32(Some(&loc), refl[0], refl[1], refl[2], refl[3]);
547 }
548 set_u_f32(gl, p, "u_reflection_blur", blur);
549 set_u_f32(gl, p, "u_haze", fx.haze.max(0.0));
550 set_u_f32(gl, p, "u_time", fx.time);
551 set_u_bool(gl, p, "u_lighting", lighting);
552 set_u_f32(gl, p, "u_lens_flare", config.lens_flare);
553 set_u_vec3(gl, p, "u_flash", fx.flash.to_array());
554 let (shock, strength, count) = fx.pack_shockwaves(fw, fh);
555 set_u_i32(gl, p, "u_shock_count", count as i32);
556 if count > 0 {
557 let flat: Vec<f32> = shock.iter().flat_map(|v| v.iter().copied()).collect();
558 if let Some(loc) = gl.get_uniform_location(p, "u_shock[0]") {
559 gl.uniform_4_f32_slice(Some(&loc), &flat);
560 }
561 if let Some(loc) = gl.get_uniform_location(p, "u_shock_strength[0]") {
562 gl.uniform_1_f32_slice(Some(&loc), &strength[..MAX_SHOCKWAVES]);
563 }
564 }
565
566 gl.draw_arrays(glow::TRIANGLES, 0, 3);
567 draws += 1;
568
569 if aa {
571 gl.bind_framebuffer(glow::FRAMEBUFFER, None);
572 gl.viewport(0, 0, full_w as i32, full_h as i32);
573 gl.use_program(Some(self.fxaa_prog));
574 gl.active_texture(glow::TEXTURE0);
575 gl.bind_texture(glow::TEXTURE_2D, Some(self.ldr_tex));
576 gl.draw_arrays(glow::TRIANGLES, 0, 3);
577 draws += 1;
578 }
579
580 gl.active_texture(glow::TEXTURE0);
581 gl.enable(glow::BLEND);
582 gl.bind_vertex_array(None);
583 draws
584 }
585}
586
587unsafe fn make_tex(gl: &glow::Context, w: u32, h: u32, hdr: bool) -> glow::Texture {
592 let tex = gl.create_texture().expect("postfx texture");
593 gl.bind_texture(glow::TEXTURE_2D, Some(tex));
594 if hdr {
595 gl.tex_image_2d(
596 glow::TEXTURE_2D, 0, glow::RGBA16F as i32,
597 w as i32, h as i32, 0,
598 glow::RGBA, glow::HALF_FLOAT, glow::PixelUnpackData::Slice(None),
599 );
600 } else {
601 gl.tex_image_2d(
602 glow::TEXTURE_2D, 0, glow::RGBA8 as i32,
603 w as i32, h as i32, 0,
604 glow::RGBA, glow::UNSIGNED_BYTE, glow::PixelUnpackData::Slice(None),
605 );
606 }
607 gl.tex_parameter_i32(glow::TEXTURE_2D, glow::TEXTURE_MIN_FILTER, glow::LINEAR as i32);
608 gl.tex_parameter_i32(glow::TEXTURE_2D, glow::TEXTURE_MAG_FILTER, glow::LINEAR as i32);
609 gl.tex_parameter_i32(glow::TEXTURE_2D, glow::TEXTURE_WRAP_S, glow::CLAMP_TO_EDGE as i32);
610 gl.tex_parameter_i32(glow::TEXTURE_2D, glow::TEXTURE_WRAP_T, glow::CLAMP_TO_EDGE as i32);
611 tex
612}
613
614unsafe fn check_complete(gl: &glow::Context, what: &str) {
615 let status = gl.check_framebuffer_status(glow::FRAMEBUFFER);
616 if status != glow::FRAMEBUFFER_COMPLETE {
617 log::error!("{what} FBO incomplete: 0x{status:X}");
618 }
619}
620
621unsafe fn create_scene_fbo(
622 gl: &glow::Context, w: u32, h: u32,
623) -> (glow::Framebuffer, glow::Texture, glow::Texture, glow::Texture) {
624 let color_tex = make_tex(gl, w, h, true);
625 let emission_tex = make_tex(gl, w, h, true);
626 let occluder_tex = make_tex(gl, w, h, true);
627
628 let fbo = gl.create_framebuffer().expect("scene fbo");
629 gl.bind_framebuffer(glow::FRAMEBUFFER, Some(fbo));
630 gl.framebuffer_texture_2d(
631 glow::FRAMEBUFFER, glow::COLOR_ATTACHMENT0, glow::TEXTURE_2D, Some(color_tex), 0,
632 );
633 gl.framebuffer_texture_2d(
634 glow::FRAMEBUFFER, glow::COLOR_ATTACHMENT1, glow::TEXTURE_2D, Some(emission_tex), 0,
635 );
636 gl.framebuffer_texture_2d(
637 glow::FRAMEBUFFER, glow::COLOR_ATTACHMENT2, glow::TEXTURE_2D, Some(occluder_tex), 0,
638 );
639 gl.draw_buffers(&[glow::COLOR_ATTACHMENT0, glow::COLOR_ATTACHMENT1, glow::COLOR_ATTACHMENT2]);
640 check_complete(gl, "scene");
641 gl.bind_framebuffer(glow::FRAMEBUFFER, None);
642 (fbo, color_tex, emission_tex, occluder_tex)
643}
644
645unsafe fn create_hdr_fbo(gl: &glow::Context, w: u32, h: u32, what: &str) -> (glow::Framebuffer, glow::Texture) {
647 let tex = make_tex(gl, w.max(1), h.max(1), true);
648 let fbo = gl.create_framebuffer().expect("hdr fbo");
649 gl.bind_framebuffer(glow::FRAMEBUFFER, Some(fbo));
650 gl.framebuffer_texture_2d(
651 glow::FRAMEBUFFER, glow::COLOR_ATTACHMENT0, glow::TEXTURE_2D, Some(tex), 0,
652 );
653 gl.draw_buffers(&[glow::COLOR_ATTACHMENT0]);
654 check_complete(gl, what);
655 gl.clear_color(0.0, 0.0, 0.0, 0.0);
657 gl.clear(glow::COLOR_BUFFER_BIT);
658 gl.bind_framebuffer(glow::FRAMEBUFFER, None);
659 (fbo, tex)
660}
661
662unsafe fn create_ldr_fbo(gl: &glow::Context, w: u32, h: u32) -> (glow::Framebuffer, glow::Texture) {
663 let tex = make_tex(gl, w.max(1), h.max(1), false);
664 let fbo = gl.create_framebuffer().expect("ldr fbo");
665 gl.bind_framebuffer(glow::FRAMEBUFFER, Some(fbo));
666 gl.framebuffer_texture_2d(
667 glow::FRAMEBUFFER, glow::COLOR_ATTACHMENT0, glow::TEXTURE_2D, Some(tex), 0,
668 );
669 gl.draw_buffers(&[glow::COLOR_ATTACHMENT0]);
670 check_complete(gl, "ldr");
671 gl.bind_framebuffer(glow::FRAMEBUFFER, None);
672 (fbo, tex)
673}
674
675unsafe fn create_pyramid(
680 gl: &glow::Context,
681 width: u32,
682 height: u32,
683) -> (Vec<glow::Framebuffer>, Vec<glow::Texture>, Vec<(i32, i32)>) {
684 let mut fbos = Vec::new();
685 let mut texs = Vec::new();
686 let mut sizes = Vec::new();
687 let (mut w, mut h) = ((width / 2).max(1), (height / 2).max(1));
688 for _ in 0..6 {
689 if w < 8 || h < 8 {
690 break;
691 }
692 let tex = make_tex(gl, w, h, true);
693 let fbo = gl.create_framebuffer().expect("bloom mip fbo");
694 gl.bind_framebuffer(glow::FRAMEBUFFER, Some(fbo));
695 gl.framebuffer_texture_2d(
696 glow::FRAMEBUFFER,
697 glow::COLOR_ATTACHMENT0,
698 glow::TEXTURE_2D,
699 Some(tex),
700 0,
701 );
702 gl.draw_buffers(&[glow::COLOR_ATTACHMENT0]);
703 fbos.push(fbo);
704 texs.push(tex);
705 sizes.push((w as i32, h as i32));
706 w = (w / 2).max(1);
707 h = (h / 2).max(1);
708 }
709 gl.bind_framebuffer(glow::FRAMEBUFFER, None);
710 (fbos, texs, sizes)
711}
712
713unsafe fn compile_postfx_program(
716 gl: &glow::Context, vert_src: &str, frag_src: &str,
717) -> glow::Program {
718 let vs = gl.create_shader(glow::VERTEX_SHADER).expect("postfx vs");
719 gl.shader_source(vs, vert_src);
720 gl.compile_shader(vs);
721 if !gl.get_shader_compile_status(vs) {
722 panic!("PostFx vertex shader error:\n{}", gl.get_shader_info_log(vs));
723 }
724
725 let fs = gl.create_shader(glow::FRAGMENT_SHADER).expect("postfx fs");
726 gl.shader_source(fs, frag_src);
727 gl.compile_shader(fs);
728 if !gl.get_shader_compile_status(fs) {
729 panic!("PostFx fragment shader error:\n{}", gl.get_shader_info_log(fs));
730 }
731
732 let prog = gl.create_program().expect("postfx program");
733 gl.attach_shader(prog, vs);
734 gl.attach_shader(prog, fs);
735 gl.link_program(prog);
736 if !gl.get_program_link_status(prog) {
737 panic!("PostFx link error:\n{}", gl.get_program_info_log(prog));
738 }
739 gl.detach_shader(prog, vs);
740 gl.detach_shader(prog, fs);
741 gl.delete_shader(vs);
742 gl.delete_shader(fs);
743 prog
744}
745
746unsafe fn set_u_i32(gl: &glow::Context, prog: glow::Program, name: &str, v: i32) {
749 if let Some(loc) = gl.get_uniform_location(prog, name) {
750 gl.uniform_1_i32(Some(&loc), v);
751 }
752}
753
754unsafe fn set_u_bool(gl: &glow::Context, prog: glow::Program, name: &str, v: bool) {
755 set_u_i32(gl, prog, name, if v { 1 } else { 0 });
756}
757
758unsafe fn set_u_f32(gl: &glow::Context, prog: glow::Program, name: &str, v: f32) {
759 if let Some(loc) = gl.get_uniform_location(prog, name) {
760 gl.uniform_1_f32(Some(&loc), v);
761 }
762}
763
764unsafe fn set_u_vec2(gl: &glow::Context, prog: glow::Program, name: &str, v: [f32; 2]) {
765 if let Some(loc) = gl.get_uniform_location(prog, name) {
766 gl.uniform_2_f32(Some(&loc), v[0], v[1]);
767 }
768}
769
770unsafe fn set_u_vec3(gl: &glow::Context, prog: glow::Program, name: &str, v: [f32; 3]) {
771 if let Some(loc) = gl.get_uniform_location(prog, name) {
772 gl.uniform_3_f32(Some(&loc), v[0], v[1], v[2]);
773 }
774}
775
776#[cfg(test)]
777mod tests {
778 use super::scaled;
779
780 #[test]
781 fn scaled_targets_never_collapse() {
782 assert_eq!(scaled(1280, 800, 1.0), (1280, 800));
783 assert_eq!(scaled(1280, 800, 0.5), (640, 400));
784 assert_eq!(scaled(3, 3, 0.1), (1, 1));
785 assert_eq!(scaled(100, 100, 0.0), (100, 100));
786 assert_eq!(scaled(100, 100, f32::NAN), (100, 100));
787 }
788}