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,
);
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;
-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;
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<Draw2dScissor>,
- scissor_stack: Vec<u32>,
-
- draw_cmds: Vec<Draw2dCmd>,
-}
-
-#[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)]
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,
--- /dev/null
+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<Draw2dScissor>,
+ scissor_stack: Vec<u32>,
+
+ draw_cmds: Vec<Draw2dCmd>,
+}
+
+#[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),
+ );
+ }
+ }
+ }
+}