1use std::sync::Arc;
38use std::time::Duration;
39
40use egui::{Color32, Context, LayerId, Painter, Shape};
41use egui_glow::{CallbackFn, ShaderVersion};
42use glow::HasContext as _;
43
44const NOISE_SIZE: usize = 64;
47
48const TIME_SPEED: f32 = 6.0;
51
52const REPAINT_MS: u64 = 33;
53
54const VERTEX_SRC: &str = include_str!("shader/vertex.glsl");
55const FRAGMENT_SRC: &str = include_str!("shader/fragment.glsl");
56
57fn generate_noise_texels(size: usize) -> Vec<u8> {
61 let mut state = 0x9E3779B97F4A7C15u64;
62 (0..size * size)
63 .map(|_| {
64 state ^= state << 13;
65 state ^= state >> 7;
66 state ^= state << 17;
67 (state >> 56) as u8
68 })
69 .collect()
70}
71
72unsafe fn compile_shader(
73 gl: &glow::Context,
74 shader_type: u32,
75 source: &str,
76) -> Result<glow::Shader, String> {
77 unsafe {
78 let shader = gl.create_shader(shader_type)?;
79 gl.shader_source(shader, source);
80 gl.compile_shader(shader);
81 if gl.get_shader_compile_status(shader) {
82 Ok(shader)
83 } else {
84 Err(gl.get_shader_info_log(shader))
85 }
86 }
87}
88
89unsafe fn link_program(
90 gl: &glow::Context,
91 shaders: &[glow::Shader],
92) -> Result<glow::Program, String> {
93 unsafe {
94 let program = gl.create_program()?;
95 for &shader in shaders {
96 gl.attach_shader(program, shader);
97 }
98 gl.link_program(program);
99 let result = if gl.get_program_link_status(program) {
100 Ok(program)
101 } else {
102 Err(gl.get_program_info_log(program))
103 };
104 for &shader in shaders {
105 gl.detach_shader(program, shader);
106 gl.delete_shader(shader);
107 }
108 result
109 }
110}
111
112struct GlResources {
118 gl: Arc<glow::Context>,
119 program: glow::Program,
120 vao: glow::VertexArray,
121 vbo: glow::Buffer,
122 noise_texture: glow::Texture,
123 u_time: Option<glow::UniformLocation>,
124 u_dimensions: Option<glow::UniformLocation>,
125 u_scale: Option<glow::UniformLocation>,
126}
127
128impl GlResources {
129 fn new(gl: Arc<glow::Context>) -> Result<Self, String> {
130 unsafe {
131 let shader_version = ShaderVersion::get(&gl);
132 let new_interface = shader_version.is_new_shader_interface() as i32;
133 let header = format!(
134 "{}\n#define NEW_SHADER_INTERFACE {new_interface}\n",
135 shader_version.version_declaration()
136 );
137
138 let vertex =
139 compile_shader(&gl, glow::VERTEX_SHADER, &format!("{header}{VERTEX_SRC}"))?;
140 let fragment = compile_shader(
141 &gl,
142 glow::FRAGMENT_SHADER,
143 &format!("{header}{FRAGMENT_SRC}"),
144 )?;
145 let program = link_program(&gl, &[vertex, fragment])?;
146
147 let vertices: [f32; 6] = [-1.0, -1.0, 3.0, -1.0, -1.0, 3.0];
149 let vbo = gl.create_buffer()?;
150 gl.bind_buffer(glow::ARRAY_BUFFER, Some(vbo));
151 gl.buffer_data_u8_slice(
152 glow::ARRAY_BUFFER,
153 f32_slice_as_bytes(&vertices),
154 glow::STATIC_DRAW,
155 );
156
157 let vao = gl.create_vertex_array()?;
158 gl.bind_vertex_array(Some(vao));
159 let a_pos = gl.get_attrib_location(program, "a_pos");
160 if let Some(a_pos) = a_pos {
161 gl.enable_vertex_attrib_array(a_pos);
162 gl.vertex_attrib_pointer_f32(a_pos, 2, glow::FLOAT, false, 0, 0);
163 }
164 gl.bind_vertex_array(None);
165 gl.bind_buffer(glow::ARRAY_BUFFER, None);
166
167 let texels = generate_noise_texels(NOISE_SIZE);
172 let mut rgba = Vec::with_capacity(texels.len() * 4);
173 for t in texels {
174 rgba.extend_from_slice(&[t, t, t, t]);
175 }
176 let noise_texture = gl.create_texture()?;
177 gl.bind_texture(glow::TEXTURE_2D, Some(noise_texture));
178 gl.tex_parameter_i32(glow::TEXTURE_2D, glow::TEXTURE_WRAP_S, glow::REPEAT as i32);
179 gl.tex_parameter_i32(glow::TEXTURE_2D, glow::TEXTURE_WRAP_T, glow::REPEAT as i32);
180 gl.tex_parameter_i32(
181 glow::TEXTURE_2D,
182 glow::TEXTURE_MIN_FILTER,
183 glow::LINEAR as i32,
184 );
185 gl.tex_parameter_i32(
186 glow::TEXTURE_2D,
187 glow::TEXTURE_MAG_FILTER,
188 glow::LINEAR as i32,
189 );
190 gl.tex_image_2d(
191 glow::TEXTURE_2D,
192 0,
193 glow::RGBA as i32,
194 NOISE_SIZE as i32,
195 NOISE_SIZE as i32,
196 0,
197 glow::RGBA,
198 glow::UNSIGNED_BYTE,
199 glow::PixelUnpackData::Slice(Some(&rgba)),
200 );
201 gl.bind_texture(glow::TEXTURE_2D, None);
202
203 gl.use_program(Some(program));
204 if let Some(loc) = gl.get_uniform_location(program, "mapSampler") {
205 gl.uniform_1_i32(Some(&loc), 0);
206 }
207 let u_time = gl.get_uniform_location(program, "time");
208 let u_dimensions = gl.get_uniform_location(program, "dimensions");
209 let u_scale = gl.get_uniform_location(program, "scale");
210 gl.use_program(None);
211
212 Ok(Self {
213 gl,
214 program,
215 vao,
216 vbo,
217 noise_texture,
218 u_time,
219 u_dimensions,
220 u_scale,
221 })
222 }
223 }
224
225 fn draw(&self, time: f32, scale: f32, screen_size_px: [u32; 2]) {
226 unsafe {
227 let gl = &self.gl;
228 gl.use_program(Some(self.program));
229 gl.active_texture(glow::TEXTURE0);
230 gl.bind_texture(glow::TEXTURE_2D, Some(self.noise_texture));
231 gl.uniform_1_f32(self.u_time.as_ref(), time);
232 gl.uniform_1_f32(self.u_scale.as_ref(), scale);
233 gl.uniform_2_f32(
234 self.u_dimensions.as_ref(),
235 screen_size_px[0] as f32,
236 screen_size_px[1] as f32,
237 );
238 gl.bind_vertex_array(Some(self.vao));
239 gl.enable(glow::BLEND);
240 gl.blend_func(glow::ONE, glow::ONE_MINUS_SRC_ALPHA);
241 gl.draw_arrays(glow::TRIANGLES, 0, 3);
242 gl.bind_vertex_array(None);
243 }
244 }
245}
246
247impl Drop for GlResources {
248 fn drop(&mut self) {
249 unsafe {
250 self.gl.delete_program(self.program);
251 self.gl.delete_vertex_array(self.vao);
252 self.gl.delete_buffer(self.vbo);
253 self.gl.delete_texture(self.noise_texture);
254 }
255 }
256}
257
258fn f32_slice_as_bytes(floats: &[f32]) -> &[u8] {
259 unsafe {
262 std::slice::from_raw_parts(floats.as_ptr().cast::<u8>(), std::mem::size_of_val(floats))
263 }
264}
265
266pub struct FlameBackground {
271 time: f32,
272 last_time: Option<f64>,
273 gl_resources: Option<Arc<GlResources>>,
274 pub scale: f32,
280 pub speed: f32,
283}
284
285impl Default for FlameBackground {
286 fn default() -> Self {
287 Self {
288 time: 0.0,
289 last_time: None,
290 gl_resources: None,
291 scale: 1.0,
292 speed: 1.0,
293 }
294 }
295}
296
297impl FlameBackground {
298 pub fn paint(&mut self, ctx: &Context, gl: Option<&Arc<glow::Context>>) {
306 ctx.request_repaint_after(Duration::from_millis(REPAINT_MS));
307
308 let now = ctx.input(|i| i.time);
309 let dt = match self.last_time {
310 Some(last) => (now - last).clamp(0.0, 0.1) as f32,
311 None => 0.0,
312 };
313 self.last_time = Some(now);
314 self.time += dt * TIME_SPEED * self.speed;
315
316 if self.gl_resources.is_none()
317 && let Some(gl) = gl
318 {
319 match GlResources::new(gl.clone()) {
320 #[allow(clippy::arc_with_non_send_sync)]
324 Ok(resources) => self.gl_resources = Some(Arc::new(resources)),
325 Err(err) => log::error!("egui-screensaver-flame: GL setup failed: {err}"),
326 }
327 }
328
329 let screen_rect = ctx.viewport_rect();
330 let painter = Painter::new(ctx.clone(), LayerId::background(), screen_rect);
331 painter.rect_filled(screen_rect, 0.0, Color32::BLACK);
332
333 if let Some(resources) = self.gl_resources.clone() {
334 let time = self.time;
335 let scale = self.scale;
336 let resources = AssertSendSync(resources);
337 painter.add(Shape::Callback(egui::PaintCallback {
338 rect: screen_rect,
339 callback: Arc::new(CallbackFn::new(move |info, _painter| {
340 resources.get().draw(time, scale, info.screen_size_px);
341 })),
342 }));
343 }
344 }
345}
346
347struct AssertSendSync<T>(T);
354unsafe impl<T> Send for AssertSendSync<T> {}
355unsafe impl<T> Sync for AssertSendSync<T> {}
356
357impl<T> AssertSendSync<T> {
358 fn get(&self) -> &T {
363 &self.0
364 }
365}