diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/fshader_main.glsl | 2 | ||||
-rw-r--r-- | src/main.rs | 110 | ||||
-rw-r--r-- | src/sdf.rs | 1 | ||||
-rw-r--r-- | src/sdl.rs | 10 | ||||
-rw-r--r-- | src/vshader_post.glsl | 2 | ||||
-rw-r--r-- | src/win.rs | 43 |
6 files changed, 117 insertions, 51 deletions
diff --git a/src/fshader_main.glsl b/src/fshader_main.glsl index 0f13a01..2a6864a 100644 --- a/src/fshader_main.glsl +++ b/src/fshader_main.glsl @@ -20,6 +20,7 @@ uniform int u_flash_icon; #define ICON_PLAY 2 #define ICON_PAUSE 3 #define ICON_REWIND 4 +#define ICON_SCREENSHOT 5 %COMMON% %SDF% @@ -131,6 +132,7 @@ void main() { switch (u_flash_icon) { case 0: break; case ICON_COPY: + case ICON_SCREENSHOT: icon = abs(pos.x) > u_aspect_ratio - 0.1 || abs(pos.y) > 0.9; break; case ICON_PLAY: diff --git a/src/main.rs b/src/main.rs index 2651c9b..782ef32 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,11 +1,9 @@ /* @TODO: -- screenshot - pause screen -- start time at -3 seconds - autoplay setting - strip ' ' and '\n' from *inside* string -- flash error on bad string +- flash error on bad string (see @TODO(error handling)) - RnToRn functions (& add back in RToR) - also add PerComponent(Box<RToR>,Box<RToR>,Box<RToR>) in R3ToR3 - ProjectX, ProjectY, ProjectZ in R3ToR? @@ -20,18 +18,21 @@ */ #![windows_subsystem = "windows"] +extern crate chrono; extern crate nalgebra; +extern crate png; pub mod sdf; mod sdl; pub mod win; +use chrono::prelude::*; use nalgebra::{Matrix3, Matrix4, Rotation3, Vector3}; use sdf::ImportExport; use std::{ collections::HashMap, fs::File, - io::{prelude::*, BufReader}, + io::{prelude::*, BufReader, BufWriter}, time::Instant, }; use win::{ColorF32, ColorGrayscaleF32, ColorU8}; @@ -49,6 +50,7 @@ enum Icon { Play = 2, Pause = 3, Rewind = 4, + Screenshot = 5, } #[derive(Clone)] @@ -160,7 +162,7 @@ impl Programs { .link_program(&mut self.test, vsource_test, &fsource_test) .map_err(|e| format!("Error compiling shader:\n{e}"))?; window - .link_program(&mut self.post, vsource_post, &fsource_post) + .link_program(&mut self.post, vsource_post, fsource_post) .map_err(|e| format!("Error compiling shader:\n{e}"))?; Ok(()) } @@ -275,15 +277,14 @@ impl State { win::FramebufferAttachment::Color0, &test_framebuffer_texture, ); - - + let main_texconfig = win::TextureParams { mag_filter: win::TextureFilter::Nearest, ..Default::default() }; let main_framebuffer_texture = window.create_texture(&main_texconfig); let main_framebuffer = window.create_framebuffer(); - + let mut main_buffer = window.create_buffer(); let mut test_buffer = window.create_buffer(); let mut post_buffer = window.create_buffer(); @@ -368,8 +369,8 @@ impl State { self.window.bind_framebuffer(None); let mut sdf_values: Vec<f32> = self - .window - .get_texture_data_vec::<ColorGrayscaleF32>(&self.test_framebuffer_texture) + .test_framebuffer_texture + .get_data_vec::<ColorGrayscaleF32>() .iter() .map(|c| c.value) .collect(); @@ -394,20 +395,64 @@ impl State { fn flash(&mut self, icon: Icon) { self.flash = match icon { Icon::None => ColorF32::BLACK, - Icon::Copy => ColorF32::GREEN, + Icon::Copy | Icon::Screenshot => ColorF32::GREEN, _ => ColorF32::rgb(1.0, 0.5, 0.0), }; self.flash_icon = icon; } - + fn render_resolution(&self) -> (i32, i32) { let scale = self.settings.get_f32("scale").unwrap_or(1.0); - if scale <= 0.0 || scale > 1.0 { + if scale <= 0.0 || scale > 100.0 { win::display_error_message(&format!("bad scale: {scale}")); std::process::exit(1); } let (w, h) = self.window.size(); - ((w as f32 * scale) as i32, (h as f32 * scale) as i32) + let w = (w as f32 * scale) as i32; + let h = (h as f32 * scale) as i32; + assert!(w >= 0); + assert!(h >= 0); + (w, h) + } + + /// save a screenshot + fn take_screenshot(&mut self) -> Result<(), String> { + let texture = &self.main_framebuffer_texture; + let size = (texture.width(), texture.height()); + let texture_data = texture.get_data_vec(); + if size.0 == 0 || size.1 == 0 { + // there isnt anything to save . why did you set the scale so small... + return Ok(()); + } + let time = Utc::now(); + let filename = time + .format("screenshots/autosdf-%Y-%m-%d-%H-%M-%S.png") + .to_string(); + if std::fs::create_dir("screenshots").is_err() { + // (do nothing.) + // we get an error if it already exists. + // even if this is another error, that will just make File::create fail. + } + let file = + File::create(&filename).map_err(|e| format!("error creating {filename}: {e}"))?; + let mut writer = BufWriter::new(file); + let mut encoder = png::Encoder::new(&mut writer, size.0 as u32, size.1 as u32); + encoder.set_color(png::ColorType::Rgba); + encoder.set_depth(png::BitDepth::Eight); + encoder + .add_text_chunk( + "\n\n\n\nAutoSDF scene".to_string(), + "\n".to_string() + &self.scene.export_string() + "\n\n\n\n", + ) + .map_err(|e| format!("error adding PNG tEXt chunk for {filename}: {e}"))?; + let mut png_writer = encoder + .write_header() + .map_err(|e| format!("error writing PNG header for {filename}: {e}"))?; + png_writer + .write_image_data(ColorU8::slice_to_bytes(&texture_data)) + .map_err(|e| format!("error writing {filename}: {e}"))?; + self.flash(Icon::Screenshot); + Ok(()) } // returns false if we should quit @@ -441,6 +486,7 @@ impl State { match self.window.set_clipboard_text(&self.scene.export_string()) { Ok(()) => {} Err(e) => { + // @TODO(error handling) eprintln!("couldn't copy text to clipboard: {e}") } } @@ -460,16 +506,27 @@ impl State { self.load_scene(new_scene); } None => { + // @TODO(error handling) eprintln!("bad string") } }, Err(e) => { - // very unlikely to happen + // @TODO(error handling) eprintln!("couldn't get clipboard text: {e}") } } } KeyDown { key: N0, .. } => self.view = self.initial_view.clone(), + KeyDown { key: F10, .. } => { + // screenshot + match self.take_screenshot() { + Ok(()) => {} + Err(e) => { + // @TODO(error handling) + eprintln!("screenshot fail: {e}"); + } + } + } KeyDown { key: Space, modifier, @@ -564,20 +621,24 @@ impl State { let render_resolution = self.render_resolution(); if render_resolution != self.main_framebuffer_size { - let result = self.main_framebuffer_texture.set_data::<ColorU8>(None, render_resolution.0 as usize, render_resolution.1 as usize); - + let result = self.main_framebuffer_texture.set_data::<ColorU8>( + None, + render_resolution.0 as usize, + render_resolution.1 as usize, + ); + match result { - Ok(()) => {}, + Ok(()) => {} Err(e) => eprintln!("warning:{e}"), } - + self.main_framebuffer.set_texture( win::FramebufferAttachment::Color0, &self.main_framebuffer_texture, ); self.main_framebuffer_size = render_resolution; } - + let window = &mut self.window; let view = &self.view; window.viewport(0, 0, render_resolution.0, render_resolution.1); @@ -585,7 +646,10 @@ impl State { window.clear_screen(win::ColorF32::BLACK); window.use_program(&self.programs.main); window.bind_framebuffer(Some(&self.main_framebuffer)); - window.uniform1f("u_aspect_ratio", render_resolution.0 as f32 / render_resolution.1 as f32); + window.uniform1f( + "u_aspect_ratio", + render_resolution.0 as f32 / render_resolution.1 as f32, + ); { let (w, h) = window.size(); window.uniform2f("u_screen_size", w as f32, h as f32); @@ -623,7 +687,7 @@ impl State { } self.main_array.draw(); - + window.bind_framebuffer(None); window.viewport_full_screen(); window.use_program(&self.programs.post); @@ -632,7 +696,7 @@ impl State { self.post_array.draw(); window.swap(); - + if self.show_debug_info { println!("frame time = {:?}ms", frame_dt * 1000.0); } @@ -30,7 +30,6 @@ impl GenRandomParams for SdfParams { fn inc_depth(self) -> Self { Self { max_depth: self.max_depth - 1, - ..self } } } @@ -738,16 +738,18 @@ impl SDL_Surface { self.flags } pub fn width(&self) -> i32 { - self.w.try_into().unwrap() + assert!(self.w >= 0); + self.w } pub fn height(&self) -> i32 { - self.h.try_into().unwrap() + assert!(self.h >= 0); + self.h } pub fn pitch(&self) -> i32 { - self.pitch.try_into().unwrap() + self.pitch } pub fn locked(&self) -> i32 { - self.locked.try_into().unwrap() + self.locked } pub fn pixel_format(&self) -> &SDL_PixelFormat { // SAFETY: this should be a valid pointer as long as self is a valid SDL_Surface diff --git a/src/vshader_post.glsl b/src/vshader_post.glsl index 89e76ff..964ace3 100644 --- a/src/vshader_post.glsl +++ b/src/vshader_post.glsl @@ -1,4 +1,4 @@ -attribute vec2 v_pos; +IN vec2 v_pos; OUT vec2 uv; void main() { @@ -561,6 +561,15 @@ impl ColorU8 { assert_eq!(suffix.len(), 0); colors } + + pub fn slice_to_bytes(slice: &[ColorU8]) -> &[u8] { + // SAFETY: it is safe to transmute since ColorU8 is repr(C) + let (prefix, bytes, suffix) = unsafe { slice.align_to() }; + // these should never panic since align_of(u8) == 1 + assert_eq!(prefix.len(), 0); + assert_eq!(suffix.len(), 0); + bytes + } } impl From<u32> for ColorU8 { @@ -1003,19 +1012,21 @@ impl Texture { } /// panicks if `data` is the wrong length (should be exactly `self.width() * self.height()`). - unsafe fn get_data<T: Color>(&self, data: &mut [T]) { + pub fn get_data<T: Color>(&self, data: &mut [T]) { assert_eq!(data.len(), self.width * self.height, "Bad data size."); self.bind(); - gl::GetTexImage( - gl::TEXTURE_2D, - 0, - T::GL_FORMAT, - T::GL_TYPE, - data.as_ptr() as *mut GLvoid, - ); + unsafe { + gl::GetTexImage( + gl::TEXTURE_2D, + 0, + T::GL_FORMAT, + T::GL_TYPE, + data.as_ptr() as *mut GLvoid, + ); + } } - unsafe fn get_data_vec<T: Color>(&self) -> Vec<T> { + pub fn get_data_vec<T: Color>(&self) -> Vec<T> { let mut data = vec![T::default(); self.width * self.height]; self.get_data(&mut data); data @@ -1120,7 +1131,7 @@ impl Framebuffer { pub fn set_texture(&mut self, attachment: FramebufferAttachment, texture: &Texture) { self.bind(); texture.bind(); - unsafe { + unsafe { gl::FramebufferTexture2D( gl::FRAMEBUFFER, attachment.to_gl(), @@ -1366,18 +1377,6 @@ impl Window { unsafe { Texture::new(params) } } - /// get texture image - /// - /// panicks if `data.len() != texture.width() * texture.height()` - pub fn get_texture_data<T: Color>(&mut self, texture: &Texture, data: &mut [T]) { - unsafe { texture.get_data(data) }; - } - - /// get texture image as a newly-allocated `Vec` - pub fn get_texture_data_vec<T: Color>(&mut self, texture: &Texture) -> Vec<T> { - unsafe { texture.get_data_vec() } - } - pub fn set_audio_callback(&mut self, callback: AudioCallback) -> Result<(), String> { if self.audio_data.is_some() { return Err("audio callback already set.".into()); |