From: Joshua Simmons Date: Sun, 19 Oct 2025 19:03:02 +0000 (+0200) Subject: shark: Move ui into its own module X-Git-Url: https://git.nega.tv//gitweb.cgi?a=commitdiff_plain;h=def6e8ea395eda18adc5856b6c6a1826fd15032e;p=josh%2Fnarcissus shark: Move ui into its own module --- diff --git a/title/shark/src/draw.rs b/title/shark/src/draw.rs index 05db1ba..dad79a2 100644 --- a/title/shark/src/draw.rs +++ b/title/shark/src/draw.rs @@ -767,6 +767,13 @@ impl<'gpu> DrawState<'gpu> { microshades::PURPLE_RGBA_F32[3], ); + let glyph_buffer = gpu.request_transient_buffer_with_data( + frame, + thread_token, + BufferUsageFlags::STORAGE, + touched_glyphs, + ); + let tile_buffer = gpu.request_transient_buffer( frame, thread_token, @@ -778,27 +785,22 @@ impl<'gpu> DrawState<'gpu> { ); let tile_buffer_address = gpu.get_buffer_address(tile_buffer.to_arg()); + let draw_cmds = ui_state.draw_cmds(); let draw_buffer = gpu.request_transient_buffer_with_data( frame, thread_token, BufferUsageFlags::STORAGE, - ui_state.draw_cmds.as_slice(), + draw_cmds, ); - let draw_buffer_len = ui_state.draw_cmds.len() as u32; + let draw_buffer_len = draw_cmds.len() as u32; + let scissors = ui_state.scissors(); let scissor_buffer = gpu.request_transient_buffer_with_data( frame, thread_token, BufferUsageFlags::STORAGE, - ui_state.scissors.as_slice(), - ); - - let glyph_buffer = gpu.request_transient_buffer_with_data( - frame, - thread_token, - BufferUsageFlags::STORAGE, - touched_glyphs, + scissors, ); const COARSE_BUFFER_LEN: usize = 1 << 20; diff --git a/title/shark/src/main.rs b/title/shark/src/main.rs index 12a1e8a..8632d5c 100644 --- a/title/shark/src/main.rs +++ b/title/shark/src/main.rs @@ -1,23 +1,17 @@ -use std::fmt::Write; use std::time::{Duration, Instant}; -use draw::DrawState; -use game::{Action, ActionEvent, GameState}; -use narcissus_core::Widen; - -use shark_shaders::pipelines::{Draw2dCmd, Draw2dScissor}; - use renderdoc_sys as rdoc; -use fonts::{FontFamily, Fonts}; use narcissus_app::{Event, Key, WindowDesc, create_app}; -use narcissus_core::default; -use narcissus_font::{FontCollection, GlyphCache, HorizontalMetrics}; use narcissus_gpu::{ ColorSpace, ImageFormat, ImageUsageFlags, PresentMode, SwapchainConfigurator, SwapchainImage, ThreadToken, create_device, }; -use narcissus_maths::{Vec2, vec2}; + +use draw::DrawState; +use fonts::Fonts; +use game::{Action, ActionEvent, GameState}; +use ui::UiState; mod draw; mod fonts; @@ -25,175 +19,7 @@ mod game; mod helpers; pub mod microshades; mod spring; - -const GLYPH_CACHE_SIZE: usize = 1024; - -struct UiState<'a> { - fonts: &'a Fonts<'a>, - glyph_cache: GlyphCache<'a, Fonts<'a>>, - - width: f32, - height: f32, - scale: f32, - - tmp_string: String, - - scissors: Vec, - scissor_stack: Vec, - - draw_cmds: Vec, -} - -#[allow(unused)] -impl<'a> UiState<'a> { - fn new(fonts: &'a Fonts<'a>) -> Self { - let glyph_cache = GlyphCache::new(fonts, GLYPH_CACHE_SIZE, GLYPH_CACHE_SIZE, 1); - - Self { - fonts, - glyph_cache, - width: 0.0, - height: 0.0, - scale: 1.0, - tmp_string: default(), - scissors: vec![], - scissor_stack: vec![], - - draw_cmds: vec![], - } - } - - fn begin_frame(&mut self, width: f32, height: f32, scale: f32) { - self.width = width; - self.height = height; - self.scale = scale; - - self.draw_cmds.clear(); - - self.scissor_stack.clear(); - self.scissors.clear(); - - // Scissor 0 is always the screen bounds. - self.scissors.push(Draw2dScissor { - offset_min: vec2(0.0, 0.0), - offset_max: vec2(width, height), - }); - } - - fn push_scissor( - &mut self, - mut offset_min: Vec2, - mut offset_max: Vec2, - intersect_with_current: bool, - ) { - if intersect_with_current { - let current_scissor_index = self.scissor_stack.last().copied().unwrap_or(0); - let current_scissor = &self.scissors[current_scissor_index.widen()]; - offset_min = Vec2::max(offset_min, current_scissor.offset_min); - offset_max = Vec2::min(offset_max, current_scissor.offset_max); - } - - let scissor_index = self.scissors.len() as u32; - self.scissors.push(Draw2dScissor { - offset_min, - offset_max, - }); - self.scissor_stack.push(scissor_index); - } - - fn push_fullscreen_scissor(&mut self) { - // The fullscreen scissor is always at index 0 - self.scissor_stack.push(0); - } - - fn pop_scissor(&mut self) { - // It's invalid to pop more than we've pushed. - self.scissor_stack.pop().expect("unbalanced push / pop"); - } - - fn rect( - &mut self, - x: f32, - y: f32, - width: f32, - height: f32, - border_width: f32, - border_radii: [f32; 4], - border_color: u32, - background_color: u32, - ) { - let scissor_index = self.scissor_stack.last().copied().unwrap_or(0); - - let border_width = border_width.clamp(0.0, 255.0).floor() as u8; - let border_radii = border_radii.map(|radius| radius.clamp(0.0, 255.0).floor() as u8); - - self.draw_cmds.push(Draw2dCmd::rect( - scissor_index, - vec2(x, y), - vec2(width, height), - border_width, - border_radii, - border_color, - background_color, - )) - } - - fn text_fmt( - &mut self, - mut x: f32, - y: f32, - font_family: FontFamily, - font_size_px: f32, - color: u32, - args: std::fmt::Arguments, - ) -> f32 { - let scissor_index = self.scissor_stack.last().copied().unwrap_or(0); - - let font = self.fonts.font(font_family); - let font_size_px = font_size_px * self.scale; - let scale = font.scale_for_size_px(font_size_px); - - let mut prev_index = None; - - self.tmp_string.clear(); - self.tmp_string.write_fmt(args).unwrap(); - - for c in self.tmp_string.chars() { - let glyph_index = font - .glyph_index(c) - .unwrap_or_else(|| font.glyph_index('□').unwrap()); - - let touched_glyph_index = - self.glyph_cache - .touch_glyph(font_family, glyph_index, font_size_px); - - let HorizontalMetrics { - advance_width, - left_side_bearing: _, - } = font.horizontal_metrics(glyph_index); - - let advance = if let Some(prev_index) = prev_index { - font.kerning_advance(prev_index, glyph_index) - } else { - 0.0 - }; - prev_index = Some(glyph_index); - - x += advance * scale; - - self.draw_cmds.push(Draw2dCmd::glyph( - scissor_index, - touched_glyph_index, - color, - vec2(x, y), - )); - - x += advance_width * scale; - } - - (font.ascent() - font.descent() + font.line_gap()) * scale - } -} +mod ui; pub fn main() { #[cfg(debug_assertions)] @@ -366,49 +192,13 @@ pub fn main() { let draw_start = Instant::now(); let tick_duration = draw_start - tick_start; - ui_state.begin_frame( - width as f32, - height as f32, + ui_state.draw( + width, + height, ui_scale_override.unwrap_or(window_display_scale), + tick_duration, ); - { - let width = width as f32; - - let font_size_px = 20.0; - - let font = ui_state.fonts.font(FontFamily::NotoSansJapanese); - let font_size_px_scaled = font_size_px * ui_state.scale; - let font_scale = font.scale_for_size_px(font_size_px_scaled); - - let y = font.ascent() * font_scale; - let h = (font.ascent() - font.descent()) * font_scale; - - ui_state.rect(0.0, 0.0, width, h, 0.0, [0.0; 4], 0x0, 0x40000000); - - ui_state.text_fmt( - 10.0, - y, - FontFamily::NotoSansJapanese, - font_size_px, - 0xffffffff, - format_args!("tick: {:?}", tick_duration), - ); - - for i in 1..=3 { - ui_state.rect( - width - font_size_px_scaled * i as f32, - 0.0, - font_size_px_scaled * 0.75, - font_size_px_scaled * 0.75, - 4.0 * ui_state.scale, - [5.0 * ui_state.scale; 4], - microshades::GREEN_RGBA8[3].rotate_right(8), - microshades::GRAY_RGBA8[0].rotate_right(8), - ); - } - } - draw_state.draw( thread_token, frame, diff --git a/title/shark/src/ui.rs b/title/shark/src/ui.rs new file mode 100644 index 0000000..ecc7bbe --- /dev/null +++ b/title/shark/src/ui.rs @@ -0,0 +1,229 @@ +use std::{fmt::Write as _, time::Duration}; + +use narcissus_core::{Widen as _, default}; +use narcissus_font::{FontCollection as _, GlyphCache, HorizontalMetrics}; +use narcissus_maths::{Vec2, vec2}; +use shark_shaders::pipelines::{Draw2dCmd, Draw2dScissor}; + +use crate::{ + fonts::{FontFamily, Fonts}, + microshades, +}; + +const GLYPH_CACHE_SIZE: usize = 1024; + +pub struct UiState<'a> { + fonts: &'a Fonts<'a>, + pub glyph_cache: GlyphCache<'a, Fonts<'a>>, + + width: f32, + height: f32, + scale: f32, + + tmp_string: String, + + scissors: Vec, + scissor_stack: Vec, + + draw_cmds: Vec, +} + +#[allow(unused)] +impl<'a> UiState<'a> { + pub fn new(fonts: &'a Fonts<'a>) -> Self { + let glyph_cache = GlyphCache::new(fonts, GLYPH_CACHE_SIZE, GLYPH_CACHE_SIZE, 1); + + Self { + fonts, + glyph_cache, + width: 0.0, + height: 0.0, + scale: 1.0, + tmp_string: default(), + scissors: vec![], + scissor_stack: vec![], + + draw_cmds: vec![], + } + } + + pub fn draw_cmds(&self) -> &[Draw2dCmd] { + &self.draw_cmds + } + + pub fn scissors(&self) -> &[Draw2dScissor] { + &self.scissors + } + + fn begin_frame(&mut self, width: f32, height: f32, scale: f32) { + self.width = width; + self.height = height; + self.scale = scale; + + self.draw_cmds.clear(); + + self.scissor_stack.clear(); + self.scissors.clear(); + + // Scissor 0 is always the screen bounds. + self.scissors.push(Draw2dScissor { + offset_min: vec2(0.0, 0.0), + offset_max: vec2(width, height), + }); + } + + fn push_scissor( + &mut self, + mut offset_min: Vec2, + mut offset_max: Vec2, + intersect_with_current: bool, + ) { + if intersect_with_current { + let current_scissor_index = self.scissor_stack.last().copied().unwrap_or(0); + let current_scissor = &self.scissors[current_scissor_index.widen()]; + offset_min = Vec2::max(offset_min, current_scissor.offset_min); + offset_max = Vec2::min(offset_max, current_scissor.offset_max); + } + + let scissor_index = self.scissors.len() as u32; + self.scissors.push(Draw2dScissor { + offset_min, + offset_max, + }); + self.scissor_stack.push(scissor_index); + } + + fn push_fullscreen_scissor(&mut self) { + // The fullscreen scissor is always at index 0 + self.scissor_stack.push(0); + } + + fn pop_scissor(&mut self) { + // It's invalid to pop more than we've pushed. + self.scissor_stack.pop().expect("unbalanced push / pop"); + } + + fn rect( + &mut self, + x: f32, + y: f32, + width: f32, + height: f32, + border_width: f32, + border_radii: [f32; 4], + border_color: u32, + background_color: u32, + ) { + let scissor_index = self.scissor_stack.last().copied().unwrap_or(0); + + let border_width = border_width.clamp(0.0, 255.0).floor() as u8; + let border_radii = border_radii.map(|radius| radius.clamp(0.0, 255.0).floor() as u8); + + self.draw_cmds.push(Draw2dCmd::rect( + scissor_index, + vec2(x, y), + vec2(width, height), + border_width, + border_radii, + border_color, + background_color, + )) + } + + fn text_fmt( + &mut self, + mut x: f32, + y: f32, + font_family: FontFamily, + font_size_px: f32, + color: u32, + args: std::fmt::Arguments, + ) -> f32 { + let scissor_index = self.scissor_stack.last().copied().unwrap_or(0); + + let font = self.fonts.font(font_family); + let font_size_px = font_size_px * self.scale; + let scale = font.scale_for_size_px(font_size_px); + + let mut prev_index = None; + + self.tmp_string.clear(); + self.tmp_string.write_fmt(args).unwrap(); + + for c in self.tmp_string.chars() { + let glyph_index = font + .glyph_index(c) + .unwrap_or_else(|| font.glyph_index('□').unwrap()); + + let touched_glyph_index = + self.glyph_cache + .touch_glyph(font_family, glyph_index, font_size_px); + + let HorizontalMetrics { + advance_width, + left_side_bearing: _, + } = font.horizontal_metrics(glyph_index); + + let advance = if let Some(prev_index) = prev_index { + font.kerning_advance(prev_index, glyph_index) + } else { + 0.0 + }; + prev_index = Some(glyph_index); + + x += advance * scale; + + self.draw_cmds.push(Draw2dCmd::glyph( + scissor_index, + touched_glyph_index, + color, + vec2(x, y), + )); + + x += advance_width * scale; + } + + (font.ascent() - font.descent() + font.line_gap()) * scale + } + + pub fn draw(&mut self, width: u32, height: u32, scale: f32, tick_duration: Duration) { + self.begin_frame(width as f32, height as f32, scale); + + { + let width = width as f32; + + let font_size_px = 20.0; + + let font = self.fonts.font(FontFamily::NotoSansJapanese); + let font_size_px_scaled = font_size_px * self.scale; + let font_scale = font.scale_for_size_px(font_size_px_scaled); + + let y = font.ascent() * font_scale; + let h = (font.ascent() - font.descent()) * font_scale; + + self.rect(0.0, 0.0, width, h, 0.0, [0.0; 4], 0x0, 0x40000000); + + self.text_fmt( + 10.0, + y, + FontFamily::NotoSansJapanese, + font_size_px, + 0xffffffff, + format_args!("tick: {:?}", tick_duration), + ); + + for i in 1..=3 { + self.rect( + width - font_size_px_scaled * i as f32, + 0.0, + font_size_px_scaled * 0.75, + font_size_px_scaled * 0.75, + 4.0 * self.scale, + [5.0 * self.scale; 4], + microshades::GREEN_RGBA8[3].rotate_right(8), + microshades::GRAY_RGBA8[0].rotate_right(8), + ); + } + } + } +}