diff --git a/game/log.ts b/game/log.ts new file mode 100644 index 00000000..90b2ca5b --- /dev/null +++ b/game/log.ts @@ -0,0 +1,15 @@ +import { print } from "./graphics"; + +const lines: string[] = []; + +export function log(...args: unknown[]) { + // const line = args.join(" "); + lines.push(args.join(" ")); +} + +export function draw_log() { + for (const line of lines) { + print(line); + break; + } +} diff --git a/game/main.ts b/game/main.ts index 7ba2915c..3fcf3466 100644 --- a/game/main.ts +++ b/game/main.ts @@ -1,4 +1,4 @@ -import { cls, print } from "./graphics"; +import { cls } from "./graphics"; import { since_start } from "./time"; import { new_v2 } from "./vector"; import { load_world, World, Level, draw_level } from "./level"; @@ -10,6 +10,7 @@ import { is_actor_type, spawn_actor, } from "./actor"; +import { log, draw_log } from "./log"; /// A nice looping frame counter. let clock = 0; @@ -25,7 +26,7 @@ let actors: Actor[] = []; function start_load_assets() { // Start this load, but then... load_world("./overworld.ldtk").then((w) => { - print("World loaded at", since_start()); + log("World loaded at", since_start()); world = w; // Assume we start at 0,0 @@ -43,7 +44,7 @@ function start_load_assets() { const props = new_actor_props(entity.id, new_v2(x, y), w); actors.push(spawn_actor(entity.type, props)); } else { - print("WARNING: Ignoring entity of type", entity.type); + log("WARNING: Ignoring entity of type", entity.type); } } }); @@ -52,7 +53,7 @@ function start_load_assets() { // TODO: Build a system whereby the signatures of the fundamental functions can be checked. export function init() { - print("Hello world!"); + log("Hello world!"); start_load_assets(); } @@ -62,7 +63,7 @@ interface Snapshot { } export function suspend(): Snapshot { - print("Suspend! ", actors.length, "actors"); + log("Suspend! ", actors.length, "actors"); return { clock, actors: actors.map((a) => { @@ -75,7 +76,7 @@ export function resume(snapshot: Snapshot | undefined) { if (snapshot) { clock = snapshot.clock || 0; actors = snapshot.actors.map((s) => spawn_actor(s.type, s.props)); - print("Resume! ", actors.length, "actors"); + log("Resume! ", actors.length, "actors"); } } @@ -109,5 +110,6 @@ export function draw() { actor.draw(clock); } - // print("FRAME TIME:", since_last_frame()); + // log("FRAME TIME:", since_last_frame()); + draw_log(); } diff --git a/src/lib.rs b/src/lib.rs index 2202f4d7..d49989c0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -629,7 +629,7 @@ impl State { binding: 0, visibility: wgpu::ShaderStages::FRAGMENT, ty: wgpu::BindingType::Texture { - multisampled: true, + multisampled: false, view_dimension: wgpu::TextureViewDimension::D2, sample_type: wgpu::TextureSampleType::Float { filterable: false }, }, @@ -695,7 +695,7 @@ impl State { }); let inconsolata = include_bytes!("./Inconsolata-Regular.ttf") as &[u8]; - let inconsolata = text::FontCache::new(&device, inconsolata, 16.0); + let inconsolata = text::FontCache::new(&device, inconsolata, 32.0); let mut font_textures = HashMap::new(); { @@ -787,6 +787,7 @@ impl State { // Reset instance buffers. self.sprite_instance_buffers.clear(); self.circle_instance_buffers.clear(); + self.text_instance_buffers.clear(); let mut builder = FrameBuilder::new(self)?; for command in commands { @@ -871,18 +872,25 @@ impl State { .copy_instance_buffers(&self.queue); self.circle_instance_buffers .copy_instance_buffers(&self.queue); + self.text_instance_buffers + .copy_instance_buffers(&self.queue); } } #[derive(Copy, Clone, Debug, Eq, PartialEq)] enum DrawMode { - Sprites, + None, + Sprites(u32), Circles, - Text, + Text(u32), } trait DrawModeInstance: Sized { - const MODE: DrawMode; + // const MODE: DrawMode; + + fn assert_compatible_mode(mode: DrawMode); + + fn new_vertex_buffer(state: &mut State) -> (u32, VertexBufferHandle); fn get_vertex_buffer<'a>( state: &'a mut State, @@ -891,7 +899,19 @@ trait DrawModeInstance: Sized { } impl DrawModeInstance for SpriteInstance { - const MODE: DrawMode = DrawMode::Sprites; + // const MODE: DrawMode = DrawMode::Sprites; + + fn assert_compatible_mode(mode: DrawMode) { + let compat = match mode { + DrawMode::Sprites(_) => true, + _ => false, + }; + assert!(compat, "{mode:?} is not compatible with SpriteInstance"); + } + + fn new_vertex_buffer(state: &mut State) -> (u32, VertexBufferHandle) { + state.sprite_instance_buffers.get_a_buffer(&state.device) + } fn get_vertex_buffer<'a>( state: &'a mut State, @@ -902,7 +922,19 @@ impl DrawModeInstance for SpriteInstance { } impl DrawModeInstance for CircleInstance { - const MODE: DrawMode = DrawMode::Circles; + // const MODE: DrawMode = DrawMode::Circles; + + fn assert_compatible_mode(mode: DrawMode) { + let compat = match mode { + DrawMode::Circles => true, + _ => false, + }; + assert!(compat, "{mode:?} is not compatible with CircleInstance"); + } + + fn new_vertex_buffer(state: &mut State) -> (u32, VertexBufferHandle) { + state.circle_instance_buffers.get_a_buffer(&state.device) + } fn get_vertex_buffer<'a>( state: &'a mut State, @@ -913,7 +945,19 @@ impl DrawModeInstance for CircleInstance { } impl DrawModeInstance for GlyphInstance { - const MODE: DrawMode = DrawMode::Text; + // const MODE: DrawMode = DrawMode::Text; + + fn assert_compatible_mode(mode: DrawMode) { + let compat = match mode { + DrawMode::Text(_) => true, + _ => false, + }; + assert!(compat, "{mode:?} is not compatible with GlyphInstance"); + } + + fn new_vertex_buffer(state: &mut State) -> (u32, VertexBufferHandle) { + state.text_instance_buffers.get_a_buffer(&state.device) + } fn get_vertex_buffer<'a>( state: &'a mut State, @@ -926,7 +970,6 @@ impl DrawModeInstance for GlyphInstance { #[derive(Debug)] struct DrawCall { mode: DrawMode, - texture_id: Option, vertex_buffer: VertexBufferHandle, draw_start: u32, draw_end: u32, @@ -936,7 +979,6 @@ impl DrawCall { pub fn new(mode: DrawMode, vertex_buffer: VertexBufferHandle, draw_start: u32) -> Self { DrawCall { mode, - texture_id: None, vertex_buffer, draw_start, draw_end: draw_start, @@ -947,16 +989,6 @@ impl DrawCall { DrawCall::new(self.mode, self.vertex_buffer.clone(), self.draw_end) } - pub fn switch_textures(&self, id: u32) -> DrawCall { - let mut next = self.new_at_buffer_tail(); - next.texture_id = Some(id); - next - } - - pub fn empty(&self) -> bool { - self.draw_start == self.draw_end - } - pub fn allocate_capacity(&mut self, capacity: u32) -> Option { // TODO: FIX: CAPACITY == 1 ALWAYS?? if self.vertex_buffer.capacity >= (self.draw_end + capacity) as usize { @@ -970,14 +1002,13 @@ impl DrawCall { pub fn draw<'a>(&self, state: &'a State, pass: &mut wgpu::RenderPass<'a>) { if self.draw_end > self.draw_start { let vb = match self.mode { - DrawMode::Sprites => { - match self.texture_id { - Some(id) => { - let bind_group = state.sprite_textures.get(&id).unwrap(); - pass.set_bind_group(1, bind_group, &[]); - } - None => (), - }; + DrawMode::None => return, + DrawMode::Sprites(id) => { + let bind_group = state + .sprite_textures + .get(&id) + .expect("sprite mode has unknown sprite texture"); + pass.set_bind_group(1, bind_group, &[]); &state .sprite_instance_buffers @@ -985,14 +1016,13 @@ impl DrawCall { .buffer } - DrawMode::Text => { - match self.texture_id { - Some(id) => { - let bind_group = state.font_textures.get(&id).unwrap(); - pass.set_bind_group(1, bind_group, &[]); - } - None => (), - } + DrawMode::Text(id) => { + let bind_group = state + .font_textures + .get(&id) + .expect("text mode has unknown font texture"); + pass.set_bind_group(1, bind_group, &[]); + &state.text_instance_buffers.get(&self.vertex_buffer).buffer } @@ -1023,6 +1053,7 @@ struct FrameBuilder<'a> { output: wgpu::SurfaceTexture, mode: DrawMode, + last_texture: Option, fill_color: [f32; 4], stroke_color: [f32; 4], target: Rc, @@ -1054,7 +1085,8 @@ impl<'a> FrameBuilder<'a> { encoder, output, - mode: DrawMode::Sprites, + mode: DrawMode::None, + last_texture: None, stroke_color: [0.0, 0.0, 0.0, 1.0], fill_color: [1.0, 1.0, 1.0, 1.0], target: last_view, @@ -1152,59 +1184,16 @@ impl<'a> FrameBuilder<'a> { } fn use_texture(&mut self, texture_id: u32) { - match self.draw_calls.last_mut() { - Some(call) => match call.texture_id { - Some(id) => { - if id == texture_id { - return; - } else if call.empty() { - call.texture_id = Some(texture_id); - } else { - let next = call.switch_textures(texture_id); - self.draw_calls.push(next); - return; - } - } - None => { - call.texture_id = Some(texture_id); - } - }, - None => { - let (draw_start, buffer) = self.new_instance_buffer(); - let mut call = DrawCall::new(self.mode, buffer, draw_start); - call.texture_id = Some(texture_id); - self.draw_calls.push(call); - } - } + self.last_texture = Some(texture_id); } - fn new_instance_buffer(&mut self) -> (u32, VertexBufferHandle) { - match self.mode { - DrawMode::Sprites => self - .state - .sprite_instance_buffers - .get_a_buffer(&self.state.device), + fn get_instance_buffer(&mut self, mode: DrawMode) -> &mut VertexBuffer { + T::assert_compatible_mode(mode); - DrawMode::Circles => self - .state - .circle_instance_buffers - .get_a_buffer(&self.state.device), - - DrawMode::Text => self - .state - .text_instance_buffers - .get_a_buffer(&self.state.device), - } - } - - fn get_instance_buffer(&mut self) -> &mut VertexBuffer { - // TODO: Re-work the mode shuffle. Keep the most recent vertex buffer - // around from the - - self.mode = T::MODE; + self.mode = mode; match self.draw_calls.last_mut() { Some(call) => { - if call.mode == T::MODE { + if call.mode == self.mode { match call.allocate_capacity(1) { Some(vb) => return T::get_vertex_buffer(self.state, &vb), None => {} @@ -1214,7 +1203,7 @@ impl<'a> FrameBuilder<'a> { None => {} }; - let (draw_start, buffer) = self.new_instance_buffer(); + let (draw_start, buffer) = T::new_vertex_buffer(self.state); let mut call = DrawCall::new(self.mode, buffer, draw_start); let vb = call.allocate_capacity(1).unwrap(); self.draw_calls.push(call); @@ -1222,15 +1211,19 @@ impl<'a> FrameBuilder<'a> { } fn push_sprite(&mut self, si: SpriteInstance) { - let vertex_buffer = self.get_instance_buffer(); - vertex_buffer.vec.push(si); + if let Some(id) = self.last_texture { + let vertex_buffer = self.get_instance_buffer(DrawMode::Sprites(id)); + vertex_buffer.vec.push(si); + } else { + eprintln!("WARNING: sprite drawn with no active texture"); + } } fn push_circle(&mut self, cc: script::graphics::CircleCommand) { let stroke_color = self.stroke_color.clone(); let fill_color = self.fill_color.clone(); - let vertex_buffer = self.get_instance_buffer(); + let vertex_buffer = self.get_instance_buffer(DrawMode::Circles); vertex_buffer.vec.push(CircleInstance { center: cc.center, radius: cc.radius, @@ -1241,14 +1234,14 @@ impl<'a> FrameBuilder<'a> { } fn push_text(&mut self, pc: script::graphics::PrintCommand) { - println!("{}", pc.text); + // println!("{}", pc.text); let mut cursor_x = 0.0; let cursor_y = 0.0; - self.mode = DrawMode::Text; // ? - self.use_texture(0); // ? - let font_id: u32 = 0; + + // self.mode = ; // ? + let font = self.state.fonts.get_mut(&font_id).unwrap(); let mut glyphs = vec![]; for char in pc.text.chars() { @@ -1257,17 +1250,22 @@ impl<'a> FrameBuilder<'a> { glyphs.push(GlyphInstance { src_top_left: [glyph.x, glyph.y], src_dims: [glyph.w, glyph.h], - dest_top_left: [cursor_x, cursor_y], - dest_dims: [glyph.w, glyph.h], + dest_top_left: [ + cursor_x + (glyph.adjust_x / 2.0), + cursor_y + (glyph.adjust_y / 2.0), + ], + dest_dims: [glyph.w / 2.0, glyph.h / 2.0], color, }); - cursor_x += glyph.w; + cursor_x += glyph.advance_width / 2.0; } for glyph in glyphs { // TODO: Get new instance buffer when we don't have space for // this glyph instead of doing this over and over. - self.get_instance_buffer().vec.push(glyph); + self.get_instance_buffer(DrawMode::Text(font_id)) + .vec + .push(glyph); } } @@ -1303,9 +1301,12 @@ impl<'a> FrameBuilder<'a> { }; if need_set_pipeline { match call.mode { - DrawMode::Sprites => pass.set_pipeline(&self.state.sprite_pipeline), + DrawMode::None => { + continue; // Skip this draw call I guess. + } + DrawMode::Sprites(_) => pass.set_pipeline(&self.state.sprite_pipeline), DrawMode::Circles => pass.set_pipeline(&self.state.circle_pipeline), - DrawMode::Text => pass.set_pipeline(&self.state.text_pipeline), + DrawMode::Text(_) => pass.set_pipeline(&self.state.text_pipeline), } last_mode = Some(call.mode); } diff --git a/src/text.rs b/src/text.rs index 25eec3f9..2bf3e18e 100644 --- a/src/text.rs +++ b/src/text.rs @@ -46,6 +46,8 @@ pub struct FontCache { font: Font, size: f32, texture: wgpu::Texture, + cell_width: u16, + cell_height: u16, pub view: wgpu::TextureView, pub sampler: wgpu::Sampler, @@ -69,8 +71,11 @@ struct GlyphCell { pub struct Glyph { pub x: f32, pub y: f32, + pub adjust_x: f32, + pub adjust_y: f32, pub w: f32, pub h: f32, + pub advance_width: f32, } impl FontCache { @@ -90,7 +95,7 @@ impl FontCache { depth_or_array_layers: 1, }, mip_level_count: 1, - sample_count: 4, + sample_count: 1, // 4 for multisample? dimension: wgpu::TextureDimension::D2, format: wgpu::TextureFormat::R8Unorm, usage: wgpu::TextureUsages::TEXTURE_BINDING @@ -124,6 +129,8 @@ impl FontCache { char_width = metrics.width.max(char_width); char_height = metrics.height.max(char_height); + eprintln!("For this font, width={char_width} height={char_height}"); + // Allocate the individual cells in the texture atlas; this records // the state of what's in the cell and whatnot. let mut cells = vec![]; @@ -151,6 +158,9 @@ impl FontCache { font, size, texture, + cell_width: char_width.try_into().unwrap(), + cell_height: char_height.try_into().unwrap(), + view, sampler, cells, @@ -182,9 +192,33 @@ impl FontCache { SlotState::Empty => { let (metrics, bitmap) = self.font.rasterize_indexed(index, self.size); + // eprintln!("Rasterizing '{c}' (index {index}): {metrics:?}"); + // For a good time, call + // { + // eprintln!(); + // let mut i = 0; + // for _ in (0..metrics.height) { + // for _ in (0..metrics.width) { + // let bv = bitmap[i]; + // let rc = if bv == 0 { + // ' ' + // } else if bv < 25 { + // '.' + // } else { + // 'X' + // }; + // eprint!("{rc}"); + // i += 1; + // } + // eprintln!(); + // } + // eprintln!(); + // } + let mut texture = self.texture.as_image_copy(); texture.origin.x = cell.x.into(); texture.origin.y = cell.y.into(); + // eprintln!(" Rendering to {}, {}", texture.origin.x, texture.origin.y); queue.write_texture( texture, @@ -207,14 +241,44 @@ impl FontCache { }; Glyph { - x: cell.x as f32 + metrics.bounds.xmin, - y: cell.y as f32 + metrics.bounds.ymin, - w: metrics.bounds.width, - h: metrics.bounds.height, + x: cell.x as f32, + y: cell.y as f32, + adjust_x: metrics.xmin as f32, + adjust_y: (self.cell_height as f32) + + floor(-metrics.bounds.height - metrics.bounds.ymin), // PositiveYDown, + w: self.cell_width as f32, + h: self.cell_height as f32, + advance_width: metrics.advance_width, } } } +fn floor(x: f32) -> f32 { + let mut ui = x.to_bits(); + let e = (((ui >> 23) as i32) & 0xff) - 0x7f; + + if e >= 23 { + return x; + } + if e >= 0 { + let m: u32 = 0x007fffff >> e; + if (ui & m) == 0 { + return x; + } + if ui >> 31 != 0 { + ui += m; + } + ui &= !m; + } else { + if ui >> 31 == 0 { + ui = 0; + } else if ui << 1 != 0 { + return -1.0; + } + } + f32::from_bits(ui) +} + // pub fn inconsolata() { // let font = include_bytes!("./Inconsolata-Regular.ttf") as &[u8]; // let font = fontdue::Font::from_bytes(font, fontdue::FontSettings::default()).unwrap(); diff --git a/src/text_shader.wgsl b/src/text_shader.wgsl index 2bd11680..7bb9827b 100644 --- a/src/text_shader.wgsl +++ b/src/text_shader.wgsl @@ -34,13 +34,21 @@ struct VertexOutput { // Fragment shader // ---------------------------------------------------------------------------- -@group(1) @binding(0) var t_diffuse : texture_multisampled_2d; +@group(1) @binding(0) var t_diffuse : texture_2d; @group(1) @binding(1) var s_diffuse : sampler; @fragment fn fs_main(in : VertexOutput)->@location(0) vec4 { // TODO: Should we be sampling here for the shader? - let tc = vec2(u32(in.tex_coords.x), u32(in.tex_coords.y)); - return textureLoad(t_diffuse, tc, 0); + + // let tc = vec2(u32(in.tex_coords.x), u32(in.tex_coords.y)); + // let c = textureLoad(t_diffuse, tc, 0); + + let tc = in.tex_coords / vec2(textureDimensions(t_diffuse)); + let c = textureSample(t_diffuse, s_diffuse, tc); + + + return vec4(c.r,c.r,c.r,c.r); + //return vec4(1.0,1.0,1.0,1.0); }