From aaa4e735b667ae20f33366f162bb1f7be84b4e91 Mon Sep 17 00:00:00 2001 From: LLLL Colonq Date: Fri, 10 Apr 2026 21:01:16 -0400 Subject: Update --- Cargo.lock | 3 + crates/teleia/Cargo.toml | 2 +- crates/teleia/src/assets/fonts/default.png | Bin 0 -> 1042 bytes crates/teleia/src/assets/fonts/font1.png | Bin 1508 -> 0 bytes crates/teleia/src/assets/fonts/font2.png | Bin 1883 -> 0 bytes crates/teleia/src/assets/fonts/simple.png | Bin 1042 -> 0 bytes crates/teleia/src/assets/shaders/uber/frag.glsl | 189 ++++++++++++++++ crates/teleia/src/assets/shaders/uber/vert.glsl | 31 +++ crates/teleia/src/context.rs | 1 + crates/teleia/src/font.rs | 4 +- crates/teleia/src/lib.rs | 1 + crates/teleia/src/renderer.rs | 283 ++++++++++++++++++++++++ crates/teleia/src/state.rs | 21 +- crates/teleia_macros/src/lib.rs | 57 +++-- 14 files changed, 561 insertions(+), 31 deletions(-) create mode 100644 crates/teleia/src/assets/fonts/default.png delete mode 100644 crates/teleia/src/assets/fonts/font1.png delete mode 100644 crates/teleia/src/assets/fonts/font2.png delete mode 100644 crates/teleia/src/assets/fonts/simple.png create mode 100644 crates/teleia/src/assets/shaders/uber/frag.glsl create mode 100644 crates/teleia/src/assets/shaders/uber/vert.glsl create mode 100644 crates/teleia/src/renderer.rs diff --git a/Cargo.lock b/Cargo.lock index f74d194..721bc72 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -262,6 +262,9 @@ name = "bitflags" version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" +dependencies = [ + "serde", +] [[package]] name = "block-buffer" diff --git a/crates/teleia/Cargo.toml b/crates/teleia/Cargo.toml index 290a977..0889a8e 100644 --- a/crates/teleia/Cargo.toml +++ b/crates/teleia/Cargo.toml @@ -24,7 +24,7 @@ enum-map = "*" # fast maps with enums as keys bimap = "*" # bijective maps # reqwest = "*" # http requests bytes = "*" # bytes for http responses -bitflags = "*" # bitwise flags +bitflags = { version = "*", features = ["serde"] } # bitwise flags simple-eyre = { version = "*", default-features = false } # error reporting and formatting rapier3d = "*" # rigid-body physics parry3d = "*" # collision detection diff --git a/crates/teleia/src/assets/fonts/default.png b/crates/teleia/src/assets/fonts/default.png new file mode 100644 index 0000000..7b1d2a3 Binary files /dev/null and b/crates/teleia/src/assets/fonts/default.png differ diff --git a/crates/teleia/src/assets/fonts/font1.png b/crates/teleia/src/assets/fonts/font1.png deleted file mode 100644 index ec06424..0000000 Binary files a/crates/teleia/src/assets/fonts/font1.png and /dev/null differ diff --git a/crates/teleia/src/assets/fonts/font2.png b/crates/teleia/src/assets/fonts/font2.png deleted file mode 100644 index 8435cad..0000000 Binary files a/crates/teleia/src/assets/fonts/font2.png and /dev/null differ diff --git a/crates/teleia/src/assets/fonts/simple.png b/crates/teleia/src/assets/fonts/simple.png deleted file mode 100644 index 7b1d2a3..0000000 Binary files a/crates/teleia/src/assets/fonts/simple.png and /dev/null differ diff --git a/crates/teleia/src/assets/shaders/uber/frag.glsl b/crates/teleia/src/assets/shaders/uber/frag.glsl new file mode 100644 index 0000000..ccdb346 --- /dev/null +++ b/crates/teleia/src/assets/shaders/uber/frag.glsl @@ -0,0 +1,189 @@ +uniform int flags; + +uniform vec3 camera_pos; +uniform float time; + +uniform vec2 sprite_offset; +uniform vec2 sprite_dims; + +uniform vec4 color; +uniform sampler2D texture_color; +uniform sampler2D texture_normal; +uniform vec3 light_ambient_color; +uniform vec3 light_dir; +uniform vec3 light_dir_color; +uniform int light_count; +uniform vec3 light_pos[5]; +uniform vec3 light_color[5]; +uniform vec2 light_attenuation[5]; + +uniform int effect_flip; +uniform float effect_flash; +uniform float effect_hueshift; +uniform float effect_huescale; + +in vec2 vertex_texcoord; +in vec3 vertex_normal; +in vec3 vertex_fragpos; +in vec4 vertex_fragpos_shadow_dir; +in vec3 vertex_view_vector; + +out vec4 frag_color; + +bool flag(int mask) { + return (flags & mask) != 0; +} + +mat3 compute_tbn() { + vec3 p = -vertex_view_vector; + vec3 normal = normalize(vertex_normal); + vec3 dpx = dFdx(p); + vec3 dpy = dFdy(p); + vec2 duvx = dFdx(vertex_texcoord); + vec2 duvy = dFdy(vertex_texcoord); + vec3 dpyperp = cross(dpy, normal); + vec3 dpxperp = cross(normal, dpx); + 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 flip = flag(FLIP_TEXTURE) ? 1.0 : -1.0; + return mat3(-tangent * invmax, flip * bitangent * invmax, normal); + +} + +vec4 normal_as_color(vec3 n) { + float r = (128.0 + 127.0 * n.r) / 255.0; + float g = (128.0 + 127.0 * n.g) / 255.0; + float b = (128.0 + 127.0 * n.b) / 255.0; + return vec4(r, g, b, 1.0); +} + +vec3 dir_light(vec3 normal) { + return max(dot(normal, -normalize(light_dir)), 0.0) * light_dir_color; +} + +vec3 point_light(vec3 normal, const int idx) { + vec3 pos = light_pos[idx]; + vec3 color = light_color[idx]; + float linear = light_attenuation[idx].x; + float quadratic = light_attenuation[idx].y; + vec3 light_vector = pos - vertex_fragpos; + float distance = length(light_vector); + float attenuation = 1.0 / (1.0 + distance * linear + distance * distance * quadratic); + + float directional = max(dot(normal.xyz, normalize(light_vector)), 0.0); + vec3 directional_light = color * directional; + + vec3 view_dir = normalize(camera_pos - vertex_fragpos); + vec3 reflect_dir = reflect(-normalize(light_vector), normalize(normal.xyz)); + float specular = pow(max(dot(view_dir, reflect_dir), 0.0), 32.0); + vec3 specular_light = 0.5 * specular * color; + // return (directional_light + specular_light) * attenuation; + return directional_light * attenuation; +} + +vec3 point_light_billboard(const int idx) { + vec3 pos = light_pos[idx]; + vec3 color = light_color[idx]; + float linear = light_attenuation[idx].x; + float quadratic = light_attenuation[idx].y; + vec3 light_vector = pos - vertex_fragpos; + float distance = length(light_vector); + float attenuation = 1.0 / (1.0 + distance * linear + distance * distance * quadratic); + + return color * attenuation; +} + +vec3 rgb_to_hsl(vec3 rgb) { + vec3 ret; + float min = min(min(rgb.r, rgb.g), rgb.b); + float max = max(max(rgb.r, rgb.g), rgb.b); + float lum = (max + min) / 2.0; + ret.z = lum; + if (max == min) { + ret.x = ret.y = 0.0; + } else { + float chroma = max - min; + ret.y = chroma / (1.0 - abs(2.0 * lum - 1.0)); + if (max == rgb.r) { + ret.x = (rgb.g - rgb.b) / chroma + (rgb.g < rgb.b ? 6.0 : 0.0); + } else if (max == rgb.g) { + ret.x = (rgb.b - rgb.r) / chroma + 2.0; + } else { + ret.x = (rgb.r - rgb.g) / chroma + 4.0; + } + ret.x /= 6.0; + } + return ret; +} + +float hue_to_rgb(float p, float q, float t) { + if (t < 0.0) t += 1.0; + if (t > 1.0) t -= 1.0; + if (t < 1.0/6.0) return p + (q - p) * 6.0 * t; + if (t < 1.0/2.0) return q; + if (t < 2.0/3.0) return p + (q - p) * (2.0/3.0 - t) * 6.0; + return p; +} + +vec3 hsl_to_rgb(vec3 hsl) { + vec3 ret; + if (hsl.y == 0.0) { + ret.r = ret.g = ret.b = hsl.z; + } else { + float q = hsl.z < 0.5 ? hsl.z * (1.0 + hsl.y) : hsl.z + hsl.y - hsl.z * hsl.y; + float p = 2.0 * hsl.z - q; + ret.r = hue_to_rgb(p, q, hsl.x + 1.0/3.0); + ret.g = hue_to_rgb(p, q, hsl.x); + ret.b = hue_to_rgb(p, q, hsl.x - 1.0/3.0); + } + return ret; +} + +void main() { + float tcy = flag(FLIP_TEXTURE) ? vertex_texcoord.y : 1.0 - vertex_texcoord.y; + vec2 tc = vec2(vertex_texcoord.x, tcy); + mat3 tbn = compute_tbn(); + if (flag(EFFECTS)) { + float fbase = float(effect_flip); + float fmul = 1.0 - 2.0 * fbase; + tc = vec2(fbase + fmul * vertex_texcoord.x, tcy); + } + if (flag(SPRITE)) { + tc *= sprite_dims; + tc += sprite_offset; + } + frag_color = color; + if (flag(TEXTURE_COLOR)) { + frag_color = texture(texture_color, tc); + } + if (flag(EFFECTS)) { + vec3 hsl = rgb_to_hsl(frag_color.rgb); + hsl.x = mod(hsl.x * effect_huescale + effect_hueshift, 1.0); + vec3 p = hsl_to_rgb(hsl); + frag_color.rgb = vec3(p.r + effect_flash, p.g + effect_flash, p.b + effect_flash); + } + vec3 normal = vertex_normal; + if (flag(TEXTURE_NORMAL)) { + normal = normalize(tbn * (texture(texture_normal, tc).xyz * 2.0 - 1.0)); + } + vec3 from_ambient = vec3(1.0, 1.0, 1.0); + if (flag(LIGHT_AMBIENT)) { + from_ambient = light_ambient_color; + } + vec3 from_dir = vec3(0.0, 0.0, 0.0); + if (flag(LIGHT_DIR)) { + from_dir = dir_light(normal); + } + vec3 from_points = vec3(0.0, 0.0, 0.0); + if (flag(LIGHT_POINT)) { + for (int i = 0; i < light_count; ++i) { + vec3 pl = point_light(normal, i); + from_points += pl; + } + } + frag_color.rgb *= (from_ambient + from_dir + from_points); + if (frag_color.a == 0.0) { + discard; + } +} diff --git a/crates/teleia/src/assets/shaders/uber/vert.glsl b/crates/teleia/src/assets/shaders/uber/vert.glsl new file mode 100644 index 0000000..15223a0 --- /dev/null +++ b/crates/teleia/src/assets/shaders/uber/vert.glsl @@ -0,0 +1,31 @@ +#version 300 es +precision highp float; + +in vec3 vertex; +in vec3 normal; +in vec2 texcoord; + +uniform mat4 view; +uniform mat4 position; +uniform mat4 projection; +uniform mat4 normal_matrix; +uniform mat4 lightspace_matrix; +uniform vec3 camera_pos; +uniform vec3 offset; + +out vec2 vertex_texcoord; +out vec3 vertex_normal; +out vec3 vertex_fragpos; +out vec4 vertex_fragpos_shadow_dir; +out vec3 vertex_view_vector; + +void main() { + vertex_texcoord = texcoord; + vertex_normal = (normal_matrix * vec4(normal, 1.0)).xyz; + vec3 pos = (position * vec4(vertex, 1.0)).xyz; + vertex_fragpos = pos; + vertex_fragpos_shadow_dir = lightspace_matrix * vec4(pos, 1.0); + vertex_view_vector = camera_pos - pos; + gl_Position = projection * view * vec4(pos, 1.0); + gl_Position.xyz += offset; +} diff --git a/crates/teleia/src/context.rs b/crates/teleia/src/context.rs index 4bcdea0..602e2cc 100644 --- a/crates/teleia/src/context.rs +++ b/crates/teleia/src/context.rs @@ -101,6 +101,7 @@ impl Context { self.gl.clear_color(0.1, 0.1, 0.1, 1.0); self.gl.clear_depth_f32(1.0); + #[cfg(debug_assertions)] self.gl.enable(glow::DEBUG_OUTPUT); self.gl.enable(glow::DEPTH_TEST); diff --git a/crates/teleia/src/font.rs b/crates/teleia/src/font.rs index b4dc68d..0587556 100644 --- a/crates/teleia/src/font.rs +++ b/crates/teleia/src/font.rs @@ -63,8 +63,8 @@ impl Bitmap { } } - pub fn new(ctx: &context::Context) -> Self { - Self::from_image(ctx, 7, 9, 112, 54, include_bytes!("assets/fonts/simple.png")) + pub fn default(ctx: &context::Context) -> Self { + Self::from_image(ctx, 7, 9, 112, 54, include_bytes!("assets/fonts/default.png")) } pub fn render_text_parameterized(&self, diff --git a/crates/teleia/src/lib.rs b/crates/teleia/src/lib.rs index cb7aefb..8e991ef 100644 --- a/crates/teleia/src/lib.rs +++ b/crates/teleia/src/lib.rs @@ -11,6 +11,7 @@ pub mod texture; pub mod scene; pub mod font; pub mod shadow; +pub mod renderer; pub mod audio; pub mod net; pub mod physics; diff --git a/crates/teleia/src/renderer.rs b/crates/teleia/src/renderer.rs new file mode 100644 index 0000000..d40c0cd --- /dev/null +++ b/crates/teleia/src/renderer.rs @@ -0,0 +1,283 @@ +use crate::{context, state, shader, texture, mesh}; + +use bitflags::bitflags; + +bitflags! { + #[derive(Clone, Copy, Debug, PartialEq, Eq)] + pub struct UberFlags: u32 { + const TEXTURE_COLOR = 0b00000001; + const TEXTURE_NORMAL = 0b00000010; + const FLIP_TEXTURE = 0b00000100; + const LIGHT_AMBIENT = 0b00001000; + const LIGHT_DIR = 0b00010000; + const LIGHT_POINT = 0b00100000; + const SPRITE = 0b01000000; + const EFFECTS = 0b10000000; + } +} +impl UberFlags { + fn prelude() -> String { + let mut s = String::new(); + s += "#version 300 es\nprecision highp float;"; + for (nm, f) in Self::all().iter_names() { + s += &format!("const int {} = {};\n", nm, f.bits()); + } + s + } + fn set_flags(self, ctx: &context::Context, shader: &shader::Shader) { + shader.set_i32(ctx, "flags", self.bits() as i32); + } +} + +pub trait Assets { + type Shader: PartialEq + Eq + Clone + Copy; + fn shader(&self, i: Self::Shader) -> &shader::Shader; + type Texture: PartialEq + Eq + Clone + Copy; + fn texture(&self, i: Self::Texture) -> &texture::Texture; + type Mesh: PartialEq + Eq + Clone + Copy; + fn mesh(&self, i: Self::Mesh) -> &mesh::Mesh; + type Material: PartialEq + Eq + Clone + Copy; + fn material(&self, i: Self::Material) -> &texture::Material; +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum ShaderMode { TwoDimension, ThreeDimension, } +#[derive(Debug, Clone, Copy)] +enum BoundShader { None, Uber(UberFlags, ShaderMode), Shader(A::Shader, ShaderMode) } +impl PartialEq for BoundShader { + fn eq(&self, other: &Self) -> bool { + match (self, other) { + (Self::None, Self::None) => true, + (Self::Uber(sf, sm), Self::Uber(of, om)) => sf == of && sm == om, + (Self::Shader(ss, sm), Self::Shader(os, om)) => ss == os && sm == om, + _ => false, + } + } +} +#[derive(Debug, Clone, Copy)] +enum BoundTexture { None, Texture(A::Texture), Material(A::Material) } +impl PartialEq for BoundTexture { + fn eq(&self, other: &Self) -> bool { + match (self, other) { + (Self::None, Self::None) => true, + (Self::Texture(s), Self::Texture(o)) => s == o, + (Self::Material(s), Self::Material(o)) => s == o, + _ => false, + } + } +} +pub struct Renderer { + pub assets: A, + shader_uber: shader::Shader, + shader: BoundShader, + texture: BoundTexture, +} +impl Renderer { + pub fn new(ctx: &context::Context, f: F) -> Self + where F: FnOnce(&context::Context) -> A { + let shader_uber = shader::Shader::new_nolib(ctx, + include_str!("assets/shaders/uber/vert.glsl"), + &format!("{}{}", UberFlags::prelude(), include_str!("assets/shaders/uber/frag.glsl")), + ); + shader_uber.bind(ctx); + shader_uber.set_i32(ctx, "texture_normal", 1); + Self { + assets: f(ctx), + shader_uber, + shader: BoundShader::None, + 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 bind_texture(&mut self, ctx: &context::Context, _st: &mut state::State, texture: A::Texture) { + if self.texture != BoundTexture::Texture(texture) { + self.assets.texture(texture).bind(ctx); + self.texture = BoundTexture::Texture(texture); + } + } + pub fn bind_material(&mut self, ctx: &context::Context, _st: &mut state::State, mat: A::Material) { + if self.texture != BoundTexture::Material(mat) { + self.assets.material(mat).bind(ctx); + self.texture = BoundTexture::Material(mat); + } + } + fn shader(&self) -> Option<(&shader::Shader, ShaderMode)> { + match self.shader { + BoundShader::Uber(_, sm) => Some((&self.shader_uber, sm)), + BoundShader::Shader(s, sm) => Some((self.assets.shader(s), sm)), + _ => None, + } + } + fn bind_uber(&mut self, + ctx: &context::Context, st: &mut state::State, + flags: UberFlags, mode: ShaderMode, + ) { + if let BoundShader::Uber(f, sm) = self.shader { + if f == flags && sm == mode { + return; + } + } + match mode { + ShaderMode::TwoDimension => st.bind_2d(ctx, &self.shader_uber), + ShaderMode::ThreeDimension => st.bind_3d(ctx, &self.shader_uber), + } + flags.set_flags(ctx, &self.shader_uber); + self.shader = BoundShader::Uber(flags, mode) + } + fn bind_shader(&mut self, + ctx: &context::Context, st: &mut state::State, + shader: A::Shader, mode: ShaderMode, + ) { + if let BoundShader::Shader(s, sm) = self.shader { + if s == shader && sm == mode { + return; + } + } + match mode { + ShaderMode::TwoDimension => st.bind_2d(ctx, &self.assets.shader(shader)), + ShaderMode::ThreeDimension => st.bind_3d(ctx, &self.assets.shader(shader)), + } + self.shader = BoundShader::Shader(shader, mode) + } + pub fn bind_uber_2d(&mut self, ctx: &context::Context, st: &mut state::State, flags: UberFlags) { + self.bind_uber(ctx, st, flags, ShaderMode::TwoDimension); + } + pub fn bind_uber_3d(&mut self, ctx: &context::Context, st: &mut state::State, flags: UberFlags) { + self.bind_uber(ctx, st, flags, ShaderMode::ThreeDimension); + } + pub fn bind_shader_2d(&mut self, ctx: &context::Context, st: &mut state::State, shader: A::Shader) { + self.bind_shader(ctx, st, shader, ShaderMode::TwoDimension); + } + pub fn bind_shader_3d(&mut self, ctx: &context::Context, st: &mut state::State, shader: A::Shader) { + self.bind_shader(ctx, st, shader, ShaderMode::ThreeDimension); + } + pub fn render(&self, ctx: &context::Context, _st: &state::State, mesh: A::Mesh) { + self.assets.mesh(mesh).render(ctx) + } + pub fn render_square(&self, ctx: &context::Context, st: &state::State) { + st.mesh_square.render(ctx) + } + pub fn set_position_2d(&self, ctx: &context::Context, st: &state::State, pos: glam::Vec2, dims: glam::Vec2) { + if let Some((s, sm)) = self.shader() { + debug_assert!(sm == ShaderMode::TwoDimension, "attempted to set_position_2d in wrong mode"); + s.set_position_2d(ctx, st, &pos, &dims) + } + } + pub fn set_position_3d(&self, ctx: &context::Context, st: &state::State, pos: glam::Mat4) { + if let Some((s, sm)) = self.shader() { + debug_assert!(sm == ShaderMode::ThreeDimension, "attempted to set_position_3d in wrong mode"); + s.set_position_3d(ctx, st, &pos) + } + } + pub fn set_i32(&self, ctx: &context::Context, _st: &state::State, nm: &str, val: i32) { + if let Some((s, _)) = self.shader() { s.set_i32(ctx, nm, val) } + } + pub fn set_f32(&self, ctx: &context::Context, _st: &state::State, nm: &str, val: f32) { + if let Some((s, _)) = self.shader() { s.set_f32(ctx, nm, val) } + } + pub fn set_vec2(&self, ctx: &context::Context, _st: &state::State, nm: &str, val: glam::Vec2) { + if let Some((s, _)) = self.shader() { s.set_vec2(ctx, nm, &val) } + } + pub fn set_vec3(&self, ctx: &context::Context, _st: &state::State, nm: &str, val: glam::Vec3) { + if let Some((s, _)) = self.shader() { s.set_vec3(ctx, nm, &val) } + } + pub fn set_vec4(&self, ctx: &context::Context, _st: &state::State, nm: &str, val: glam::Vec4) { + if let Some((s, _)) = self.shader() { s.set_vec4(ctx, nm, &val) } + } + pub fn set_mat4(&self, ctx: &context::Context, _st: &state::State, nm: &str, val: glam::Mat4) { + if let Some((s, _)) = self.shader() { s.set_mat4(ctx, nm, &val) } + } + pub fn set_texture_offset(&self, ctx: &context::Context, st: &state::State, inc: i32, x: i32, y: i32) { + let count = inc as f32; + let ratio = 1.0 / count; + self.set_vec2( + ctx, st, "sprite_dims", + glam::Vec2::new(ratio, ratio), + ); + self.set_vec2( + ctx, st, "sprite_offset", + glam::Vec2::new((x % inc) as f32 * ratio, (y % inc) as f32 * ratio), + ); + } + + /// 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, + 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); + } + + /// Common case: draw the given color in a rectangle on the screen (units are pixels, pos is top left) + pub fn color_screen(&mut self, + ctx: &context::Context, st: &mut state::State, + color: glam::Vec4, + pos: glam::Vec2, + dims: glam::Vec2, + ) { + self.bind_uber_2d(ctx, st, UberFlags::empty()); + self.set_vec4(ctx, st, "color", color); + self.set_position_2d(ctx, st, pos, dims); + self.render_square(ctx, st); + } + + /// 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, + pos: glam::Vec2, + dims: glam::Vec2, + ) { + self.bind_uber_2d(ctx, st, UberFlags::TEXTURE_COLOR); + self.bind_texture(ctx, st, texture); + self.set_position_2d(ctx, st, pos, dims); + self.render_square(ctx, st); + } + + /// 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 shader and texture + self.shader = BoundShader::None; self.texture = BoundTexture::None; + st.font_default.render_text(ctx, st, &pos, 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, + pos: glam::Vec2, + col: glam::Vec3, + s: &str, + ) { + // drawing text might bind the shader and texture + self.shader = BoundShader::None; self.texture = BoundTexture::None; + st.font_default.render_text_helper(ctx, st, &pos, 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, + ) { + // drawing text might bind the shader and texture + self.shader = BoundShader::None; self.texture = BoundTexture::None; + let width = s.len() as f32 * st.font_default.char_width as f32; + let height = st.font_default.char_height as f32; + st.font_default.render_text(ctx, st, + &(pos - glam::Vec2::new((width / 2.0).round(), (height / 2.0).round())), + s + ); + } +} diff --git a/crates/teleia/src/state.rs b/crates/teleia/src/state.rs index 46b46b1..2b23ae7 100644 --- a/crates/teleia/src/state.rs +++ b/crates/teleia/src/state.rs @@ -6,7 +6,7 @@ use glow::HasContext; use serde::{Serialize, Deserialize}; use strum::EnumIter; -use crate::{audio, context, framebuffer, mesh, shader, utils}; +use crate::{audio, context, font, framebuffer, mesh, shader, utils}; const DELTA_TIME: f64 = 0.016; // todo @@ -167,6 +167,7 @@ pub struct State { pub render_dims: glam::Vec2, pub shader_upscale: shader::Shader, pub mesh_square: mesh::Mesh, + pub font_default: font::Bitmap, pub audio: Option, pub projection: glam::Mat4, @@ -261,6 +262,7 @@ impl State { render_dims: glam::Vec2::new(ctx.render_width, ctx.render_height), shader_upscale, mesh_square, + font_default: font::Bitmap::default(ctx), audio: None, projection: glam::Mat4::perspective_lh( @@ -534,13 +536,16 @@ impl State { self.shader_upscale.bind(&ctx); self.render_framebuffer.bind_texture(&ctx); ctx.render_no_geometry(); - let err = unsafe { ctx.gl.get_error() }; - if err != glow::NO_ERROR { - log::warn!("opengl error: {}", err); - } - let log = unsafe { ctx.gl.get_debug_message_log(5) }; - for m in log { - log::warn!("opengl debug message: {:?}", m); + #[cfg(debug_assertions)] + { + let err = unsafe { ctx.gl.get_error() }; + if err != glow::NO_ERROR { + log::warn!("opengl error: {}", err); + } + let log = unsafe { ctx.gl.get_debug_message_log(5) }; + for m in log { + log::warn!("opengl debug message: {:?}", m); + } } Ok(()) } diff --git a/crates/teleia_macros/src/lib.rs b/crates/teleia_macros/src/lib.rs index ead8f50..9e358d9 100644 --- a/crates/teleia_macros/src/lib.rs +++ b/crates/teleia_macros/src/lib.rs @@ -76,24 +76,20 @@ impl Field { ents.push((d.enum_entry(&self.nm), exp)); } } - if ents.len() > 0 { - let (enm, ty) = match self.nm.as_str() { - "meshes" => ("Mesh", "teleia::mesh::Mesh"), - "textures" => ("Texture", "teleia::texture::Texture"), - "materials" => ("Material", "teleia::texture::Material"), - "shaders" => ("Shader", "teleia::shader::Shader"), - _ => panic!("unknown asset type: {}", self.nm), - }; - let enums: Vec<_> = ents.iter().map(|(e, _)| e.clone()).collect(); - let edecl = format!("#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize, enum_map::Enum)] + let (enm, ty) = match self.nm.as_str() { + "meshes" => ("Mesh", "teleia::mesh::Mesh"), + "textures" => ("Texture", "teleia::texture::Texture"), + "materials" => ("Material", "teleia::texture::Material"), + "shaders" => ("Shader", "teleia::shader::Shader"), + _ => return None, + }; + let enums: Vec<_> = ents.iter().map(|(e, _)| e.clone()).collect(); + let edecl = format!("#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize, enum_map::Enum)] pub enum {} {{ {} }}", enm, enums.join(", ")); - let decl = format!("pub {}: enum_map::EnumMap<{}, {}>", self.nm, enm, ty); - let inits: Vec<_> = ents.into_iter().map(|(e, exp)| format!("{}::{} => {}", enm, e, exp)).collect(); - let init = format!("{}: enum_map::enum_map!({})", self.nm, inits.join(", ")); - Some((edecl, decl, init)) - } else { - None - } + let decl = format!("pub {}: enum_map::EnumMap<{}, {}>", self.nm, enm, ty); + let inits: Vec<_> = ents.into_iter().map(|(e, exp)| format!("{}::{} => {}", enm, e, exp)).collect(); + let init = format!("{}: enum_map::enum_map!({})", self.nm, inits.join(", ")); + Some((edecl, decl, init)) } } @@ -105,11 +101,24 @@ impl AssetData { fn new(base: &str) -> Self { let mut fields = Vec::new(); let dirs = std::fs::read_dir(base).expect(&format!("failed to read assets directory: {}", base)); + let (mut has_meshes, mut has_textures, mut has_materials, mut has_shaders) = (false, false, false, false); for dir in dirs { if let Ok(d) = dir { - fields.push(Field::new(base, &d.file_name().into_string().unwrap())); + let nm = d.file_name().into_string().unwrap(); + fields.push(Field::new(base, &nm)); + match &*nm { + "meshes" => has_meshes = true, + "textures" => has_textures = true, + "materials" => has_materials = true, + "shaders" => has_shaders = true, + _ => {}, + }; } } + if !has_meshes { fields.push(Field { nm: "meshes".to_owned(), entries: HashSet::new() }); } + if !has_textures { fields.push(Field { nm: "textures".to_owned(), entries: HashSet::new() }); } + if !has_materials { fields.push(Field { nm: "materials".to_owned(), entries: HashSet::new() }); } + if !has_shaders { fields.push(Field { nm: "shaders".to_owned(), entries: HashSet::new() }); } Self { fields, } @@ -121,16 +130,24 @@ impl AssetData { res += edecl; res += "\n"; } res += "pub struct Assets {\n"; - res += "pub font_default: teleia::font::Bitmap,\n"; for (_, decl, _) in fdata.iter() { res += decl; res += ",\n"; } res += "}\nimpl Assets {\npub fn new(ctx: &teleia::context::Context) -> Self {\nSelf {\n"; - res += "font_default: teleia::font::Bitmap::new(ctx),\n"; for (_, _, init) in fdata.iter() { res += init; res += ",\n"; } res += "}\n}\n}\n"; + res += "impl teleia::renderer::Assets for Assets {\n"; + res += "type Shader = Shader;\n"; + res += "fn shader(&self, i: Self::Shader) -> &teleia::shader::Shader { &self.shaders[i] }\n"; + res += "type Texture = Texture;\n"; + res += "fn texture(&self, i: Self::Texture) -> &teleia::texture::Texture { &self.textures[i] }\n"; + res += "type Material = Material;\n"; + res += "fn material(&self, i: Self::Material) -> &teleia::texture::Material { &self.materials[i] }\n"; + res += "type Mesh = Mesh;\n"; + res += "fn mesh(&self, i: Self::Mesh) -> &teleia::mesh::Mesh { &self.meshes[i] }\n"; + res += "}\n"; res } } -- cgit v1.2.3