diff options
| author | LLLL Colonq <llll@colonq> | 2026-06-08 06:34:27 -0400 |
|---|---|---|
| committer | LLLL Colonq <llll@colonq> | 2026-06-08 06:34:27 -0400 |
| commit | 4851758f8a1b8fdf099db28f4f6756c1f5e757b6 (patch) | |
| tree | c0891f347a922c0c6167a784987e8cc4befe6f77 /crates | |
| parent | 0656a01c0ff7298f5c20f109949a3e78d58d1e30 (diff) | |
Refactor renderer
Diffstat (limited to 'crates')
| -rw-r--r-- | crates/teleia/Cargo.toml | 2 | ||||
| -rw-r--r-- | crates/teleia/src/assets/shaders/uber/frag.glsl | 28 | ||||
| -rw-r--r-- | crates/teleia/src/lib.rs | 28 | ||||
| -rw-r--r-- | crates/teleia/src/postprocessing.rs | 2 | ||||
| -rw-r--r-- | crates/teleia/src/renderer.rs | 294 |
5 files changed, 235 insertions, 119 deletions
diff --git a/crates/teleia/Cargo.toml b/crates/teleia/Cargo.toml index 958cbf0..d5777a3 100644 --- a/crates/teleia/Cargo.toml +++ b/crates/teleia/Cargo.toml @@ -15,7 +15,7 @@ ahash = "=0.8.10" # dependency of tobj gltf = { git = "https://github.com/lcolonq/gltf", features = ["extras", "import", "names", "utils"] } # loader for .gltf scenes image = { version = "0.25.10", default-features = false, features = ["jpeg", "png"] } # texture loader # cosmic-text = { version = "*", default-features = false, features = ["std", "swash"] } # advanced text rendering -glam = "0.29.2" # linear algebra +glam = {version = "0.29.2", features = ["serde"]} # linear algebra log = "0.4.21" # logging rand = { version = "=0.8.5", features = ["small_rng"] } # rng serde = { version = "1.0.228", features = ["derive"] } # serialization diff --git a/crates/teleia/src/assets/shaders/uber/frag.glsl b/crates/teleia/src/assets/shaders/uber/frag.glsl index 9c9e64a..639ad59 100644 --- a/crates/teleia/src/assets/shaders/uber/frag.glsl +++ b/crates/teleia/src/assets/shaders/uber/frag.glsl @@ -3,32 +3,44 @@ uniform highp int flags; uniform vec3 camera_pos; uniform float time; -uniform vec2 sprite_offset; -uniform vec2 sprite_dims; - uniform vec4 color; +// flag(TEXTURE_COLOR) uniform sampler2D texture_color; +// flag(TEXTURE_NORMAL) uniform sampler2D texture_normal; +// flag(TEXTURE_FLIP) uniform vec2 texture_flip; +// flag(LIGHT_AMBIENT) uniform vec3 light_ambient_color; +// flag(LIGHT_DIR) uniform vec3 light_dir; uniform vec3 light_dir_color; +// flag(LIGHT_POINT) uniform int light_count; uniform vec3 light_pos[5]; uniform vec3 light_color[5]; uniform vec2 light_attenuation[5]; -uniform float flash; +// flag(RGB_ADD) +uniform vec3 rgb_add; +// flag(HUE) uniform float hue_shift; uniform float hue_scale; + // flag(OPACITY) uniform float opacity; -in vec2 vertex_texcoord; +// flag(VERTEX_COLOR) in vec3 vertex_color; + +// flag(SPRITE) +uniform vec2 sprite_offset; +uniform vec2 sprite_dims; + +in vec2 vertex_texcoord; in vec3 vertex_normal; in vec3 vertex_fragpos; in vec4 vertex_fragpos_shadow_dir; @@ -52,8 +64,6 @@ mat3 compute_tbn(vec2 tc) { vec3 tangent = dpyperp * duvx.x + dpxperp * duvy.x; vec3 bitangent = dpyperp * duvx.y + dpxperp * duvy.y; float invmax = inversesqrt(max(dot(bitangent, bitangent), dot(bitangent, bitangent))); - // float xflip = -1.0 + 2.0 * float(texture_flip.x); - // float yflip = -1.0 + 2.0 * float(texture_flip.y); return mat3(-tangent * invmax, bitangent * invmax, normal); } @@ -171,8 +181,8 @@ void main() { hsl.x = mod(hsl.x * hue_scale + hue_shift, 1.0); frag_color.rgb = hsl_to_rgb(hsl); } - if (flag(FLASH)) { - frag_color.rgb += vec3(flash); + if (flag(RGB_ADD)) { + frag_color.rgb += vec3(rgb_add); } vec3 normal = vertex_normal; if (flag(TEXTURE_NORMAL)) { diff --git a/crates/teleia/src/lib.rs b/crates/teleia/src/lib.rs index 0f77e82..498085f 100644 --- a/crates/teleia/src/lib.rs +++ b/crates/teleia/src/lib.rs @@ -25,6 +25,8 @@ pub use state::Tick; pub use audio::AudioPlayback; pub use simple_eyre::eyre::WrapErr; +pub use glam::{Vec2, Vec3, Vec4, Quat, Mat4, Vec3Swizzles, Vec4Swizzles}; + #[cfg(target_arch = "wasm32")] use winit::platform::web::EventLoopExtWebSys; @@ -65,16 +67,27 @@ where } #[cfg(not(target_arch = "wasm32"))] -pub fn run<'a, F, G>(title: &str, w: u32, h: u32, options: Options, gnew: F) -> Erm<()> -where - G: state::Game + 'static, - F: (FnOnce(&'a context::Context, &mut state::State) -> G), -{ +pub fn initialize_logging() { env_logger::Builder::new() .filter(None, log::LevelFilter::Info) .init(); install_error_handler(); +} +#[cfg(target_arch = "wasm32")] +pub fn initialize_logging() { + console_log::init_with_level(log::Level::Debug).unwrap(); + console_error_panic_hook::set_once(); + tracing_wasm::set_as_global_default(); + // install_error_handler(); +} +#[cfg(not(target_arch = "wasm32"))] +pub fn run<'a, F, G>(title: &str, w: u32, h: u32, options: Options, gnew: F) -> Erm<()> +where + G: state::Game + 'static, + F: (FnOnce(&'a context::Context, &mut state::State) -> G), +{ + initialize_logging(); log::info!("hello computer, starting up..."); let (rglfw, rwindow, gl, events) = { @@ -203,10 +216,7 @@ where G: state::Game + 'static, F: (Fn(&'a context::Context, &mut state::State) -> G), { - console_log::init_with_level(log::Level::Debug).unwrap(); - console_error_panic_hook::set_once(); - tracing_wasm::set_as_global_default(); - // install_error_handler(); + initialize_logging(); log::info!("hello computer, starting up..."); diff --git a/crates/teleia/src/postprocessing.rs b/crates/teleia/src/postprocessing.rs index 227af09..66c7e0d 100644 --- a/crates/teleia/src/postprocessing.rs +++ b/crates/teleia/src/postprocessing.rs @@ -1,6 +1,6 @@ use crate::{context, framebuffer, shader, state, utils}; -const NUM_BINDINGS: usize = 5; +pub const NUM_BINDINGS: usize = 5; #[derive(Clone, Copy)] pub struct Effect(usize); diff --git a/crates/teleia/src/renderer.rs b/crates/teleia/src/renderer.rs index 4864cc5..299baf2 100644 --- a/crates/teleia/src/renderer.rs +++ b/crates/teleia/src/renderer.rs @@ -1,4 +1,4 @@ -use crate::{context, mesh, postprocessing, shader, state, texture}; +use crate::{context, font, mesh, postprocessing, shader, state, texture}; use bitflags::bitflags; @@ -12,9 +12,9 @@ bitflags! { const LIGHT_AMBIENT = 1 << 4; const LIGHT_DIR = 1 << 5; const LIGHT_POINT = 1 << 6; - const HUE = 1 << 7; - const SPRITE = 1 << 8; - const FLASH = 1 << 9; + const RGB_ADD = 1 << 7; + const HUE = 1 << 8; + const SPRITE = 1 << 9; const YSKEW = 1 << 10; const OPACITY = 1 << 11; } @@ -72,6 +72,148 @@ impl<A: Assets> PartialEq for BoundTexture<A> { } } } + +#[must_use] +pub struct ApplyPostprocessingEffect +<'s, 'r, A: Assets> { + st: &'s mut state::State, + renderer: &'r mut Renderer<A>, + effect: A::Effect, + bindings: [Option<(&'static str, postprocessing::Uniform)>; postprocessing::NUM_BINDINGS], +} +impl<'s, 'r, A: Assets> ApplyPostprocessingEffect<'s, 'r, A> { + pub fn apply(self) { + let eff = *self.renderer.assets.effect(self.effect); + self.st.postprocessing.apply_with_bindings(eff, self.bindings.into_iter().flatten()); + } + pub fn bind_uniform(mut self, nm: &'static str, val: postprocessing::Uniform) -> Self { + if let Some(u) = self.bindings.iter_mut().find(|b| b.is_none()) { + *u = Some((nm, val)); + } else { + log::warn!("too many bindings on postprocessing effect in renderer!"); + } + self + } + pub fn bind_i32(self, nm: &'static str, x: i32) -> Self { + self.bind_uniform(nm, postprocessing::Uniform::I32(x)) + } + pub fn bind_f32(self, nm: &'static str, x: f32) -> Self { + self.bind_uniform(nm, postprocessing::Uniform::F32(x)) + } + pub fn bind_vec2(self, nm: &'static str, x: glam::Vec2) -> Self { + self.bind_uniform(nm, postprocessing::Uniform::Vec2(x)) + } + pub fn bind_vec3(self, nm: &'static str, x: glam::Vec3) -> Self { + self.bind_uniform(nm, postprocessing::Uniform::Vec3(x)) + } + pub fn bind_vec4(self, nm: &'static str, x: glam::Vec4) -> Self { + self.bind_uniform(nm, postprocessing::Uniform::Vec4(x)) + } + pub fn bind_mat4(self, nm: &'static str, x: glam::Mat4) -> Self { + self.bind_uniform(nm, postprocessing::Uniform::Mat4(x)) + } +} + +#[must_use] +pub struct RenderMesh<'c, 's, 'r, A: Assets> { + ctx: &'c context::Context, + st: &'s mut state::State, + renderer: &'r mut Renderer<A>, + mesh: A::Mesh, + pos: glam::Mat4, + texture: Option<A::Texture>, +} +impl<'c, 's, 'r, A: Assets> RenderMesh<'c, 's, 'r, A> { + pub fn render(self) { + self.renderer.bind_uber_2d(self.ctx, self.st, UberFlags::TEXTURE_COLOR); + self.renderer.set_position_3d(self.ctx, self.st, self.pos); + if let Some(texture) = self.texture { + self.renderer.bind_texture(self.ctx, self.st, texture); + } + self.renderer.render(self.ctx, self.st, self.mesh); + } +} + +#[must_use] +pub struct RenderTextureScreen<'c, 's, 'r, A: Assets> { + ctx: &'c context::Context, + st: &'s mut state::State, + renderer: &'r mut Renderer<A>, + texture: A::Texture, + pos: glam::Vec2, + dims: Option<glam::Vec2>, + rot: Option<glam::Quat>, + hue: Option<f32>, +} +impl<'c, 's, 'r, A: Assets> RenderTextureScreen<'c, 's, 'r, A> { + pub fn render(self) { + self.renderer.bind_uber_2d(self.ctx, self.st, UberFlags::TEXTURE_COLOR | UberFlags::TEXTURE_FLIP); + self.renderer.bind_texture(self.ctx, self.st, self.texture); + let dims = if let Some(dims) = self.dims { dims } else { + let t = self.renderer.assets.texture(self.texture); + glam::Vec2::new(t.width as f32, t.height as f32) + }; + if let Some(rot) = self.rot { + self.renderer.set_position_2d_rotate(self.ctx, self.st, self.pos, dims, rot); + } else { + self.renderer.set_position_2d(self.ctx, self.st, self.pos, dims); + } + self.renderer.set_vec2(self.ctx, self.st, "texture_flip", glam::Vec2::new(0.0, 1.0)); + if let Some(hue) = self.hue { + self.renderer.set_f32(self.ctx, self.st, "hue_scale", 0.0); + self.renderer.set_f32(self.ctx, self.st, "hue_shift", hue); + } + self.renderer.render_square(self.ctx, self.st); + } + pub fn dimensions(mut self, dims: glam::Vec2) -> Self { self.dims = Some(dims); self } + pub fn rotation(mut self, rot: glam::Quat) -> Self { self.rot = Some(rot); self } + pub fn hue(mut self, hue: f32) -> Self { self.hue = Some(hue); self } +} + +#[must_use] +pub struct RenderTextScreen<'c, 's, 'r, 'str, 'f, A: Assets> { + ctx: &'c context::Context, + st: &'s mut state::State, + renderer: &'r mut Renderer<A>, + text: &'str str, + pos: glam::Vec2, + font: Option<&'f font::Bitmap>, + centered: bool, + col: Option<glam::Vec3>, + scale: Option<glam::Vec2>, + offset: Option<glam::Vec2>, +} +impl<'c, 's, 'r, 'str, 'f, A: Assets> RenderTextScreen<'c, 's, 'r, 'str, 'f, A> { + pub fn render(self) { + // drawing text might bind the texture + self.renderer.texture = BoundTexture::None; + self.renderer.bind_uber_2d(self.ctx, self.st, UberFlags::TEXTURE_COLOR | UberFlags::VERTEX_COLOR); + let font = if let Some(font) = self.font { font } else { &self.st.font_default }; + let dims = glam::Vec2::new(font.char_width as f32, font.char_height as f32); + let fpos = if self.centered { + let width = self.text.len() as f32 * font.char_width as f32; + self.pos + glam::Vec2::new( + -dims.x / 2.0 - (width / 2.0).round(), + font.char_height as f32 / 2.0 + ) + } else { + self.pos + glam::Vec2::new(-dims.x / 2.0, dims.y / 2.0) + }; + self.renderer.set_position_2d(self.ctx, self.st, fpos, dims); + let color: &[glam::Vec3] = if let Some(col) = self.col { &[col] } else { &[] }; + font.render_text_parameterized(self.ctx, self.st, self.text, font::BitmapParams { + color, + scale: if let Some(scale) = self.scale { scale } else { glam::Vec2::ONE }, + offset: if let Some(off) = self.offset { off } else { glam::Vec2::ZERO }, + }); + } + pub fn font(mut self, font: &'f font::Bitmap) -> Self { self.font = Some(font); self } + pub fn centered(mut self) -> Self { self.centered = true; self } + pub fn color(mut self, col: glam::Vec3) -> Self { self.col = Some(col); self } + pub fn scale(mut self, scale: glam::Vec2) -> Self { self.scale = Some(scale); self } + pub fn offset(mut self, offset: glam::Vec2) -> Self { self.offset = Some(offset); self } +} + pub struct Renderer<A: Assets> { pub assets: A, shader_uber: shader::Shader, @@ -94,8 +236,6 @@ impl<A: Assets> Renderer<A> { texture: BoundTexture::None, } } - pub fn font_char_width(&self, st: &state::State) -> f32 { st.font_default.char_width as f32 } - pub fn font_char_height(&self, st: &state::State) -> f32 { st.font_default.char_height as f32 } pub fn unbind_texture(&mut self, _ctx: &context::Context, _st: &mut state::State) { self.texture = BoundTexture::None; } @@ -225,28 +365,40 @@ impl<A: Assets> Renderer<A> { ); } - pub fn clear(&mut self, + pub fn begin_frame(&mut self, ctx: &context::Context, _st: &mut state::State, - color: glam::Vec4 + clear_color: glam::Vec4 ) { self.texture = BoundTexture::None; self.shader = BoundShader::None; - ctx.clear_color(color); + ctx.clear_color(clear_color); ctx.clear(); } - /// Common case: draw the given textured mesh in the world (units are world tiles) - pub fn textured_mesh_world(&mut self, - ctx: &context::Context, st: &mut state::State, - shader: A::Shader, - texture: A::Texture, - mesh: A::Mesh, + /// Enable the given postprocessing effect for the current frame + pub fn postprocessing_effect<'s, 'r>(&'r mut self, + _ctx: &context::Context, st: &'s mut state::State, + effect: A::Effect, + ) -> ApplyPostprocessingEffect<'s, 'r, A> { + ApplyPostprocessingEffect { + st, renderer: self, + effect, + bindings: [const { None }; _], + } + } + + /// Common case: draw the given textured mesh in the world (units are world coordinates) + pub fn mesh_world<'c, 's, 'r>(&'r mut self, + ctx: &'c context::Context, st: &'s mut state::State, pos: glam::Mat4, - ) { - self.bind_shader_3d(ctx, st, shader); - self.bind_texture(ctx, st, texture); - self.set_position_3d(ctx, st, pos); - self.render(ctx, st, mesh); + mesh: A::Mesh, + ) -> RenderMesh<'c, 's, 'r, A> { + RenderMesh { + ctx, st, renderer: self, + mesh, + pos, + texture: None, + } } /// Common case: draw the given color in a rectangle on the screen (units are pixels, pos is top left) @@ -263,90 +415,34 @@ impl<A: Assets> Renderer<A> { } /// Common case: draw the given texture on the screen (units are pixels, pos is top left) - pub fn texture_screen(&mut self, - ctx: &context::Context, st: &mut state::State, - texture: A::Texture, + pub fn texture_screen<'c, 's, 'r>(&'r mut self, ctx: &'c context::Context, st: &'s mut state::State, pos: glam::Vec2, - dims: glam::Vec2, - ) { - self.bind_uber_2d(ctx, st, UberFlags::TEXTURE_COLOR | UberFlags::TEXTURE_FLIP); - self.bind_texture(ctx, st, texture); - self.set_position_2d(ctx, st, pos, dims); - self.set_vec2(ctx, st, "texture_flip", glam::Vec2::new(0.0, 1.0)); - self.render_square(ctx, st); - } - - pub fn texture_screen_rotate(&mut self, - ctx: &context::Context, st: &mut state::State, texture: A::Texture, - pos: glam::Vec2, - dims: glam::Vec2, - rot: glam::Quat, - ) { - self.bind_uber_2d(ctx, st, UberFlags::TEXTURE_COLOR | UberFlags::TEXTURE_FLIP); - self.bind_texture(ctx, st, texture); - self.set_position_2d_rotate(ctx, st, pos, dims, rot); - self.set_vec2(ctx, st, "texture_flip", glam::Vec2::new(0.0, 1.0)); - self.render_square(ctx, st); - } - - pub fn texture_screen_recolor(&mut self, - ctx: &context::Context, st: &mut state::State, - texture: A::Texture, hue: f32, - pos: glam::Vec2, - dims: glam::Vec2, - ) { - self.bind_uber_2d(ctx, st, UberFlags::TEXTURE_COLOR | UberFlags::TEXTURE_FLIP | UberFlags::HUE); - self.bind_texture(ctx, st, texture); - self.set_vec2(ctx, st, "texture_flip", glam::Vec2::new(0.0, 1.0)); - self.set_f32(ctx, st, "hue_scale", 0.0); - self.set_f32(ctx, st, "hue_shift", hue); - self.set_position_2d(ctx, st, pos, dims); - self.render_square(ctx, st); + ) -> RenderTextureScreen<'c, 's, 'r, A> { + RenderTextureScreen { + ctx, st, renderer: self, + texture, pos, + dims: None, + rot: None, + hue: None, + } } /// Common case: text in the default font (units are pixels, pos is top left) - pub fn text_screen(&mut self, - ctx: &context::Context, st: &mut state::State, - pos: glam::Vec2, - s: &str, - ) { - // drawing text might bind the texture - self.texture = BoundTexture::None; - self.bind_uber_2d(ctx, st, UberFlags::TEXTURE_COLOR | UberFlags::VERTEX_COLOR); - let dims = glam::Vec2::new(st.font_default.char_width as f32, st.font_default.char_height as f32); - let fpos = pos + glam::Vec2::new(-dims.x / 2.0, dims.y / 2.0); - self.set_position_2d(ctx, st, fpos, dims); - st.font_default.render_text(ctx, st, s); - } - - /// Common case: text in the default font, with a color (units are pixels, pos is top left) - pub fn text_colored_screen(&mut self, - ctx: &context::Context, st: &mut state::State, + pub fn text_screen<'c, 's, 'r, 'str, 'f>(&'r mut self, + ctx: &'c context::Context, st: &'s mut state::State, pos: glam::Vec2, - col: glam::Vec3, - s: &str, - ) { - self.texture = BoundTexture::None; - self.bind_uber_2d(ctx, st, UberFlags::TEXTURE_COLOR | UberFlags::VERTEX_COLOR); - let dims = glam::Vec2::new(st.font_default.char_width as f32, st.font_default.char_height as f32); - let fpos = pos + glam::Vec2::new(-dims.x / 2.0, st.font_default.char_height as f32 / 2.0); - self.set_position_2d(ctx, st, fpos, dims); - st.font_default.render_text_helper(ctx, st, s, &[col]); - } - - /// Common case: text in the default font (units are pixels, pos is center) - pub fn text_centered_screen(&mut self, - ctx: &context::Context, st: &mut state::State, - pos: glam::Vec2, - s: &str, - ) { - self.texture = BoundTexture::None; - self.bind_uber_2d(ctx, st, UberFlags::TEXTURE_COLOR | UberFlags::VERTEX_COLOR); - let width = s.len() as f32 * st.font_default.char_width as f32; - let dims = glam::Vec2::new(st.font_default.char_width as f32, st.font_default.char_height as f32); - let fpos = pos + glam::Vec2::new(-dims.x / 2.0 - (width / 2.0).round(), st.font_default.char_height as f32 / 2.0); - self.set_position_2d(ctx, st, fpos, dims); - st.font_default.render_text(ctx, st, s); + text: &'str str, + ) -> RenderTextScreen<'c, 's, 'r, 'str, 'f, A> { + RenderTextScreen { + ctx, st, renderer: self, + text, + pos, + font: None, + centered: false, + col: None, + scale: None, + offset: None, + } } } |
