1use glow::HasContext;
11use crate::render::shaders::{FULLSCREEN_VERT, BLOOM_FRAG, COMPOSITE_FRAG};
12use crate::config::RenderConfig;
13
14pub struct PostFxPipeline {
15 pub scene_fbo: glow::Framebuffer,
17 pub scene_color_tex: glow::Texture, pub scene_emission_tex: glow::Texture, bloom_fbo: [glow::Framebuffer; 2],
22 bloom_tex: [glow::Texture; 2],
23
24 bloom_prog: glow::Program,
26 composite_prog: glow::Program,
27
28 fullscreen_vao: glow::VertexArray,
30}
31
32impl PostFxPipeline {
33 pub unsafe fn new(gl: &glow::Context, width: u32, height: u32) -> Self {
34 let bloom_prog = compile_postfx_program(gl, FULLSCREEN_VERT, BLOOM_FRAG);
35 let composite_prog = compile_postfx_program(gl, FULLSCREEN_VERT, COMPOSITE_FRAG);
36 let fullscreen_vao = gl.create_vertex_array().expect("postfx fullscreen_vao");
37
38 gl.use_program(Some(bloom_prog));
40 set_u_i32(gl, bloom_prog, "u_texture", 0);
41
42 gl.use_program(Some(composite_prog));
43 set_u_i32(gl, composite_prog, "u_scene", 0);
44 set_u_i32(gl, composite_prog, "u_bloom", 1);
45
46 let (scene_fbo, scene_color_tex, scene_emission_tex) =
47 create_scene_fbo(gl, width, height);
48 let (bloom_fbo, bloom_tex) =
49 create_bloom_fbos(gl, (width / 2).max(1), (height / 2).max(1));
50
51 Self {
52 scene_fbo,
53 scene_color_tex,
54 scene_emission_tex,
55 bloom_fbo,
56 bloom_tex,
57 bloom_prog,
58 composite_prog,
59 fullscreen_vao,
60 }
61 }
62
63 pub unsafe fn resize(&mut self, gl: &glow::Context, width: u32, height: u32) {
65 gl.delete_framebuffer(self.scene_fbo);
67 gl.delete_texture(self.scene_color_tex);
68 gl.delete_texture(self.scene_emission_tex);
69 for i in 0..2 {
70 gl.delete_framebuffer(self.bloom_fbo[i]);
71 gl.delete_texture(self.bloom_tex[i]);
72 }
73
74 let (scene_fbo, scene_color_tex, scene_emission_tex) =
75 create_scene_fbo(gl, width, height);
76 let (bloom_fbo, bloom_tex) =
77 create_bloom_fbos(gl, (width / 2).max(1), (height / 2).max(1));
78
79 self.scene_fbo = scene_fbo;
80 self.scene_color_tex = scene_color_tex;
81 self.scene_emission_tex = scene_emission_tex;
82 self.bloom_fbo = bloom_fbo;
83 self.bloom_tex = bloom_tex;
84 }
85
86 pub unsafe fn run(
90 &self,
91 gl: &glow::Context,
92 config: &RenderConfig,
93 full_w: u32,
94 full_h: u32,
95 time: f32,
96 ) {
97 let bw = (full_w / 2).max(1) as i32;
98 let bh = (full_h / 2).max(1) as i32;
99
100 gl.bind_vertex_array(Some(self.fullscreen_vao));
101
102 if config.bloom_enabled {
104 gl.use_program(Some(self.bloom_prog));
105 gl.active_texture(glow::TEXTURE0);
106
107 gl.bind_framebuffer(glow::FRAMEBUFFER, Some(self.bloom_fbo[0]));
109 gl.viewport(0, 0, bw, bh);
110 gl.clear(glow::COLOR_BUFFER_BIT);
111 gl.bind_texture(glow::TEXTURE_2D, Some(self.scene_emission_tex));
112 set_u_bool(gl, self.bloom_prog, "u_horizontal", true);
113 set_u_f32(gl, self.bloom_prog, "u_radius", 1.5);
114 gl.draw_arrays(glow::TRIANGLES, 0, 3);
115
116 gl.bind_framebuffer(glow::FRAMEBUFFER, Some(self.bloom_fbo[1]));
118 gl.clear(glow::COLOR_BUFFER_BIT);
119 gl.bind_texture(glow::TEXTURE_2D, Some(self.bloom_tex[0]));
120 set_u_bool(gl, self.bloom_prog, "u_horizontal", false);
121 gl.draw_arrays(glow::TRIANGLES, 0, 3);
122
123 gl.bind_framebuffer(glow::FRAMEBUFFER, Some(self.bloom_fbo[0]));
125 gl.clear(glow::COLOR_BUFFER_BIT);
126 gl.bind_texture(glow::TEXTURE_2D, Some(self.bloom_tex[1]));
127 set_u_bool(gl, self.bloom_prog, "u_horizontal", true);
128 set_u_f32(gl, self.bloom_prog, "u_radius", 2.5);
129 gl.draw_arrays(glow::TRIANGLES, 0, 3);
130
131 gl.bind_framebuffer(glow::FRAMEBUFFER, Some(self.bloom_fbo[1]));
133 gl.clear(glow::COLOR_BUFFER_BIT);
134 gl.bind_texture(glow::TEXTURE_2D, Some(self.bloom_tex[0]));
135 set_u_bool(gl, self.bloom_prog, "u_horizontal", false);
136 gl.draw_arrays(glow::TRIANGLES, 0, 3);
137 }
138
139 gl.bind_framebuffer(glow::FRAMEBUFFER, None);
141 gl.viewport(0, 0, full_w as i32, full_h as i32);
142 gl.clear(glow::COLOR_BUFFER_BIT);
143 gl.use_program(Some(self.composite_prog));
144
145 gl.active_texture(glow::TEXTURE0);
147 gl.bind_texture(glow::TEXTURE_2D, Some(self.scene_color_tex));
148
149 gl.active_texture(glow::TEXTURE1);
151 if config.bloom_enabled {
152 gl.bind_texture(glow::TEXTURE_2D, Some(self.bloom_tex[1]));
153 } else {
154 gl.bind_texture(glow::TEXTURE_2D, Some(self.scene_color_tex));
155 }
156
157 let bloom_intensity = if config.bloom_enabled { config.bloom_intensity } else { 0.0 };
158 set_u_f32(gl, self.composite_prog, "u_bloom_intensity", bloom_intensity);
159 set_u_vec3(gl, self.composite_prog, "u_tint", [1.0, 1.0, 1.0]);
160 set_u_f32(gl, self.composite_prog, "u_saturation", 1.0);
161 set_u_f32(gl, self.composite_prog, "u_contrast", 1.05);
162 set_u_f32(gl, self.composite_prog, "u_brightness", 0.0);
163 set_u_f32(gl, self.composite_prog, "u_vignette", 0.25);
164 set_u_f32(gl, self.composite_prog, "u_grain_intensity", config.film_grain);
165 set_u_f32(gl, self.composite_prog, "u_grain_seed", time);
166 set_u_f32(gl, self.composite_prog, "u_chromatic", config.chromatic_aberration);
167 set_u_f32(gl, self.composite_prog, "u_scanline_intensity",
168 if config.scanlines_enabled { 0.15 } else { 0.0 });
169 set_u_bool(gl, self.composite_prog, "u_scanlines_enabled", config.scanlines_enabled);
170
171 gl.draw_arrays(glow::TRIANGLES, 0, 3);
172
173 gl.bind_vertex_array(None);
174 }
175}
176
177unsafe fn make_rgba_tex(gl: &glow::Context, w: u32, h: u32) -> glow::Texture {
180 let tex = gl.create_texture().expect("postfx texture");
181 gl.bind_texture(glow::TEXTURE_2D, Some(tex));
182 gl.tex_image_2d(
183 glow::TEXTURE_2D, 0, glow::RGBA as i32,
184 w as i32, h as i32, 0,
185 glow::RGBA, glow::UNSIGNED_BYTE, None,
186 );
187 gl.tex_parameter_i32(glow::TEXTURE_2D, glow::TEXTURE_MIN_FILTER, glow::LINEAR as i32);
188 gl.tex_parameter_i32(glow::TEXTURE_2D, glow::TEXTURE_MAG_FILTER, glow::LINEAR as i32);
189 gl.tex_parameter_i32(glow::TEXTURE_2D, glow::TEXTURE_WRAP_S, glow::CLAMP_TO_EDGE as i32);
190 gl.tex_parameter_i32(glow::TEXTURE_2D, glow::TEXTURE_WRAP_T, glow::CLAMP_TO_EDGE as i32);
191 tex
192}
193
194unsafe fn create_scene_fbo(
195 gl: &glow::Context, w: u32, h: u32,
196) -> (glow::Framebuffer, glow::Texture, glow::Texture) {
197 let color_tex = make_rgba_tex(gl, w, h);
198 let emission_tex = make_rgba_tex(gl, w, h);
199
200 let fbo = gl.create_framebuffer().expect("scene fbo");
201 gl.bind_framebuffer(glow::FRAMEBUFFER, Some(fbo));
202 gl.framebuffer_texture_2d(
203 glow::FRAMEBUFFER, glow::COLOR_ATTACHMENT0, glow::TEXTURE_2D, Some(color_tex), 0,
204 );
205 gl.framebuffer_texture_2d(
206 glow::FRAMEBUFFER, glow::COLOR_ATTACHMENT1, glow::TEXTURE_2D, Some(emission_tex), 0,
207 );
208 gl.draw_buffers(&[glow::COLOR_ATTACHMENT0, glow::COLOR_ATTACHMENT1]);
209
210 let status = gl.check_framebuffer_status(glow::FRAMEBUFFER);
211 if status != glow::FRAMEBUFFER_COMPLETE {
212 log::error!("Scene FBO incomplete: 0x{status:X}");
213 }
214 gl.bind_framebuffer(glow::FRAMEBUFFER, None);
215 (fbo, color_tex, emission_tex)
216}
217
218unsafe fn create_bloom_fbos(
219 gl: &glow::Context, w: u32, h: u32,
220) -> ([glow::Framebuffer; 2], [glow::Texture; 2]) {
221 let textures = [make_rgba_tex(gl, w, h), make_rgba_tex(gl, w, h)];
222 let fbos = [
223 gl.create_framebuffer().expect("bloom fbo 0"),
224 gl.create_framebuffer().expect("bloom fbo 1"),
225 ];
226 for i in 0..2 {
227 gl.bind_framebuffer(glow::FRAMEBUFFER, Some(fbos[i]));
228 gl.framebuffer_texture_2d(
229 glow::FRAMEBUFFER, glow::COLOR_ATTACHMENT0, glow::TEXTURE_2D, Some(textures[i]), 0,
230 );
231 gl.draw_buffers(&[glow::COLOR_ATTACHMENT0]);
232 }
233 gl.bind_framebuffer(glow::FRAMEBUFFER, None);
234 (fbos, textures)
235}
236
237unsafe fn compile_postfx_program(
240 gl: &glow::Context, vert_src: &str, frag_src: &str,
241) -> glow::Program {
242 let vs = gl.create_shader(glow::VERTEX_SHADER).expect("postfx vert shader");
243 gl.shader_source(vs, vert_src);
244 gl.compile_shader(vs);
245 if !gl.get_shader_compile_status(vs) {
246 panic!("PostFx vert compile error:\n{}", gl.get_shader_info_log(vs));
247 }
248
249 let fs = gl.create_shader(glow::FRAGMENT_SHADER).expect("postfx frag shader");
250 gl.shader_source(fs, frag_src);
251 gl.compile_shader(fs);
252 if !gl.get_shader_compile_status(fs) {
253 panic!("PostFx frag compile error:\n{}", gl.get_shader_info_log(fs));
254 }
255
256 let prog = gl.create_program().expect("postfx program");
257 gl.attach_shader(prog, vs);
258 gl.attach_shader(prog, fs);
259 gl.link_program(prog);
260 if !gl.get_program_link_status(prog) {
261 panic!("PostFx link error:\n{}", gl.get_program_info_log(prog));
262 }
263 gl.detach_shader(prog, vs);
264 gl.detach_shader(prog, fs);
265 gl.delete_shader(vs);
266 gl.delete_shader(fs);
267 prog
268}
269
270unsafe fn set_u_i32(gl: &glow::Context, prog: glow::Program, name: &str, v: i32) {
273 if let Some(loc) = gl.get_uniform_location(prog, name) {
274 gl.uniform_1_i32(Some(&loc), v);
275 }
276}
277
278unsafe fn set_u_bool(gl: &glow::Context, prog: glow::Program, name: &str, v: bool) {
279 if let Some(loc) = gl.get_uniform_location(prog, name) {
280 gl.uniform_1_i32(Some(&loc), v as i32);
281 }
282}
283
284unsafe fn set_u_f32(gl: &glow::Context, prog: glow::Program, name: &str, v: f32) {
285 if let Some(loc) = gl.get_uniform_location(prog, name) {
286 gl.uniform_1_f32(Some(&loc), v);
287 }
288}
289
290unsafe fn set_u_vec3(gl: &glow::Context, prog: glow::Program, name: &str, v: [f32; 3]) {
291 if let Some(loc) = gl.get_uniform_location(prog, name) {
292 gl.uniform_3_f32(Some(&loc), v[0], v[1], v[2]);
293 }
294}