#![feature(format_args_capture, try_blocks, drain_filter)]
#![warn(
clippy::correctness,
clippy::perf,
clippy::style,
clippy::pedantic,
clippy::complexity,
clippy::cargo,
clippy::nursery
)]
#![warn(unsafe_op_in_unsafe_fn)]
#![allow(clippy::single_match_else, clippy::match_bool)]
#![allow(clippy::cargo_common_metadata)]
#![allow(clippy::module_name_repetitions)]
#![allow(clippy::multiple_crate_versions)]
mod args;
mod glium_backend;
mod glium_facade;
mod images;
mod uvs;
mod window;
use crate::{glium_backend::GliumBackend, glium_facade::GliumFacade, images::Images, uvs::ImageUvs};
use anyhow::Context;
use args::Args;
use glium::Surface;
use std::{mem, rc::Rc};
use window::Window;
fn main() -> Result<(), anyhow::Error> {
simplelog::TermLogger::init(
log::LevelFilter::Info,
simplelog::Config::default(),
simplelog::TerminalMode::Stderr,
simplelog::ColorChoice::Auto,
)
.expect("Unable to initialize logger");
let args = Args::new().context("Unable to retrieve arguments")?;
let window = Window::from_window_id(args.window_id)
.map(Rc::new)
.context("Unable to create window")?;
let mut images = Images::new(args.images_dir.clone(), args.image_backlog, Rc::clone(&window))
.context("Unable to load images")?;
let backend = GliumBackend::new(Rc::clone(&window)).context("Unable to create backend")?;
let facade = GliumFacade::new(backend).context("Unable to create glium facade")?;
let indices =
glium::IndexBuffer::<u32>::new(&facade, glium::index::PrimitiveType::TrianglesList, &[0, 1, 3, 0, 3, 2])
.context("Unable to create index buffer")?;
let program = {
glium::Program::new(&facade, glium::program::ProgramCreationInput::SourceCode {
vertex_shader: include_str!("vertex.glsl"),
fragment_shader: include_str!("frag.glsl"),
geometry_shader: None,
tessellation_control_shader: None,
tessellation_evaluation_shader: None,
transform_feedback_varyings: None,
outputs_srgb: true,
uses_point_size: false,
})
}
.context("Unable to build program")?;
let mut cur_image = Image::new(&facade, &mut images, &window).context("Unable to create image")?;
let mut next_image = Image::new(&facade, &mut images, &window).context("Unable to create image")?;
let mut progress = 0.0;
let mut next_image_is_loaded = false;
loop {
window.process_events();
let mut target = facade.draw();
let draw_res = self::draw(
&mut target,
progress,
&args,
&cur_image,
&next_image,
&indices,
&program,
);
if let Err(err) = draw_res {
mem::forget(target);
log::warn!("Unable to draw: {err:?}");
}
let update_res = self::update(
&mut progress,
&mut next_image_is_loaded,
&args,
&mut cur_image,
&mut next_image,
&facade,
&mut images,
&window,
);
if let Err(err) = update_res {
log::warn!("Unable to update: {err:?}");
}
}
}
#[allow(clippy::too_many_arguments)] fn update(
progress: &mut f32, next_image_is_loaded: &mut bool, args: &Args, cur_image: &mut Image, next_image: &mut Image,
facade: &GliumFacade, images: &mut Images, window: &Window,
) -> Result<(), anyhow::Error> {
*progress += (1.0 / 60.0) / args.duration.as_secs_f32();
if !*next_image_is_loaded {
let force_wait = *progress >= args.fade;
if force_wait {
log::info!("Next image hasn't arrived yet at the end of current image, waiting for it");
}
*next_image_is_loaded ^= next_image
.try_update(facade, images, window, force_wait)
.context("Unable to update image")?;
if force_wait && !*next_image_is_loaded {
return Err(anyhow::anyhow!("Unable to load next image even while force-waiting"));
}
}
if *progress >= 1.0 {
*progress = 1.0 - args.fade;
mem::swap(cur_image, next_image);
*next_image_is_loaded = false;
*next_image_is_loaded ^= next_image
.try_update(facade, images, window, false)
.context("Unable to update image")?;
}
Ok(())
}
fn draw(
target: &mut glium::Frame, progress: f32, args: &Args, cur_image: &Image, next_image: &Image,
indices: &glium::IndexBuffer<u32>, program: &glium::Program,
) -> Result<(), anyhow::Error> {
let (base_alpha, next_progress) = match progress {
f if f >= args.fade => ((progress - args.fade) / (1.0 - args.fade), progress - args.fade),
_ => (0.0, 0.0),
};
target.clear_color(0.0, 0.0, 0.0, 1.0);
for (image, alpha, progress) in [
(cur_image, 1.0 - base_alpha, progress),
(next_image, base_alpha, next_progress),
] {
if alpha == 0.0 {
continue;
}
let sampler = image.texture.sampled();
let tex_offset = image.uvs.offset(progress);
let uniforms = glium::uniform! {
tex_sampler: sampler,
tex_offset: tex_offset,
alpha: alpha,
};
let draw_parameters = glium::DrawParameters {
blend: glium::Blend::alpha_blending(),
..glium::DrawParameters::default()
};
target
.draw(&image.vertex_buffer, indices, program, &uniforms, &draw_parameters)
.context("Unable to draw")?;
}
target.set_finish().context("Unable to finish drawing")?;
Ok(())
}
#[derive(Debug)]
struct Image {
texture: glium::Texture2d,
uvs: ImageUvs,
vertex_buffer: glium::VertexBuffer<Vertex>,
}
impl Image {
pub fn new(facade: &GliumFacade, images: &mut Images, window: &Window) -> Result<Self, anyhow::Error> {
let image = images.next_image();
let image_dims = image.dimensions();
let texture = glium::texture::Texture2d::new(
facade,
glium::texture::RawImage2d::from_raw_rgba(image.into_raw(), image_dims),
)
.context("Unable to create texture")?;
#[allow(clippy::cast_precision_loss)] let uvs = ImageUvs::new(
image_dims.0 as f32,
image_dims.1 as f32,
window.width() as f32,
window.height() as f32,
rand::random(),
);
let vertex_buffer = glium::VertexBuffer::dynamic(facade, &Self::vertices(uvs.start()))
.context("Unable to create vertex buffer")?;
Ok(Self {
texture,
uvs,
vertex_buffer,
})
}
pub fn try_update(
&mut self, facade: &GliumFacade, images: &mut Images, window: &Window, force_wait: bool,
) -> Result<bool, anyhow::Error> {
let image = match images.try_next_image() {
Some(image) => image,
None if force_wait => images.next_image(),
None => return Ok(false),
};
let image_dims = image.dimensions();
self.texture = glium::texture::Texture2d::new(
facade,
glium::texture::RawImage2d::from_raw_rgba(image.into_raw(), image_dims),
)
.context("Unable to create texture")?;
#[allow(clippy::cast_precision_loss)] let uvs = ImageUvs::new(
image_dims.0 as f32,
image_dims.1 as f32,
window.width() as f32,
window.height() as f32,
rand::random(),
);
self.uvs = uvs;
self.vertex_buffer
.as_mut_slice()
.write(&Self::vertices(self.uvs.start()));
Ok(true)
}
const fn vertices(uvs_start: [f32; 2]) -> [Vertex; 4] {
[
Vertex {
vertex_pos: [-1.0, -1.0],
vertex_tex: [0.0, 0.0],
},
Vertex {
vertex_pos: [1.0, -1.0],
vertex_tex: [uvs_start[0], 0.0],
},
Vertex {
vertex_pos: [-1.0, 1.0],
vertex_tex: [0.0, uvs_start[1]],
},
Vertex {
vertex_pos: [1.0, 1.0],
vertex_tex: uvs_start,
},
]
}
}
#[derive(Clone, Copy, Debug)]
struct Vertex {
vertex_pos: [f32; 2],
vertex_tex: [f32; 2],
}
glium::implement_vertex!(Vertex, vertex_pos, vertex_tex);