From ad0691c146cf7cbc3b6f80f3698a7a64008c8bab Mon Sep 17 00:00:00 2001 From: Patrick Walton Date: Tue, 5 Mar 2019 15:13:55 -0800 Subject: [PATCH] Move the generic UI code in the renderer crate to a separate crate --- Cargo.lock | 12 + demo/common/Cargo.toml | 3 + demo/common/src/lib.rs | 2 +- demo/common/src/ui.rs | 96 +++--- geometry/src/color.rs | 78 +++++ geometry/src/lib.rs | 1 + renderer/Cargo.toml | 3 + renderer/src/gpu/debug.rs | 600 ++--------------------------------- renderer/src/gpu/renderer.rs | 5 +- renderer/src/paint.rs | 69 +--- svg/src/lib.rs | 3 +- ui/Cargo.toml | 18 ++ ui/src/lib.rs | 576 +++++++++++++++++++++++++++++++++ 13 files changed, 776 insertions(+), 690 deletions(-) create mode 100644 geometry/src/color.rs diff --git a/Cargo.lock b/Cargo.lock index 07162b41..681a69a4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -271,6 +271,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "byteorder 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)", "scopeguard 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.88 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -514,6 +515,7 @@ dependencies = [ "pathfinder_gpu 0.1.0", "pathfinder_renderer 0.1.0", "pathfinder_svg 0.1.0", + "pathfinder_ui 0.1.0", "rayon 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", "sdl2 0.32.1 (registry+https://github.com/rust-lang/crates.io-index)", "usvg 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -562,6 +564,7 @@ dependencies = [ "pathfinder_geometry 0.3.0", "pathfinder_gpu 0.1.0", "pathfinder_simd 0.3.0", + "pathfinder_ui 0.1.0", "quickcheck 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)", "rayon 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.88 (registry+https://github.com/rust-lang/crates.io-index)", @@ -586,6 +589,15 @@ dependencies = [ [[package]] name = "pathfinder_ui" version = "0.1.0" +dependencies = [ + "hashbrown 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", + "pathfinder_geometry 0.3.0", + "pathfinder_gpu 0.1.0", + "pathfinder_simd 0.3.0", + "serde 1.0.88 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_derive 1.0.88 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_json 1.0.38 (registry+https://github.com/rust-lang/crates.io-index)", +] [[package]] name = "phf" diff --git a/demo/common/Cargo.toml b/demo/common/Cargo.toml index 4d9ca20e..b38dfffc 100644 --- a/demo/common/Cargo.toml +++ b/demo/common/Cargo.toml @@ -32,3 +32,6 @@ path = "../../renderer" [dependencies.pathfinder_svg] path = "../../svg" + +[dependencies.pathfinder_ui] +path = "../../ui" diff --git a/demo/common/src/lib.rs b/demo/common/src/lib.rs index f4ad68a4..e6a92004 100644 --- a/demo/common/src/lib.rs +++ b/demo/common/src/lib.rs @@ -19,13 +19,13 @@ use pathfinder_geometry::basic::point::{Point2DF32, Point2DI32, Point3DF32}; use pathfinder_geometry::basic::rect::RectF32; use pathfinder_geometry::basic::transform2d::Transform2DF32; use pathfinder_geometry::basic::transform3d::{Perspective, Transform3DF32}; +use pathfinder_geometry::color::ColorU; use pathfinder_gl::GLDevice; use pathfinder_gpu::{DepthFunc, DepthState, Device, Primitive, RenderState, Resources}; use pathfinder_gpu::{StencilFunc, StencilState, UniformData}; use pathfinder_renderer::builder::{RenderOptions, RenderTransform, SceneBuilder}; use pathfinder_renderer::gpu::renderer::Renderer; use pathfinder_renderer::gpu_data::BuiltScene; -use pathfinder_renderer::paint::ColorU; use pathfinder_renderer::post::{DEFRINGING_KERNEL_CORE_GRAPHICS, STEM_DARKENING_FACTORS}; use pathfinder_renderer::scene::Scene; use pathfinder_renderer::z_buffer::ZBuffer; diff --git a/demo/common/src/ui.rs b/demo/common/src/ui.rs index 5fe23cc0..f7a82583 100644 --- a/demo/common/src/ui.rs +++ b/demo/common/src/ui.rs @@ -12,9 +12,10 @@ use crate::Options; use nfd::Response; use pathfinder_geometry::basic::point::Point2DI32; use pathfinder_geometry::basic::rect::RectI32; +use pathfinder_geometry::color::ColorU; use pathfinder_gpu::{Device, Resources}; -use pathfinder_renderer::gpu::debug::{DebugUI, PADDING, TEXT_COLOR, WINDOW_COLOR}; -use pathfinder_renderer::paint::ColorU; +use pathfinder_renderer::gpu::debug::DebugUI; +use pathfinder_ui::{PADDING, TEXT_COLOR, WINDOW_COLOR}; use std::f32::consts::PI; use std::path::PathBuf; @@ -120,7 +121,7 @@ impl DemoUI where D: Device { debug_ui: &mut DebugUI, event: &mut UIEvent, action: &mut UIAction) { - let bottom = debug_ui.framebuffer_size().y() - PADDING; + let bottom = debug_ui.ui.framebuffer_size().y() - PADDING; // Draw effects button. let effects_button_position = Point2DI32::new(PADDING, bottom - BUTTON_HEIGHT); @@ -218,13 +219,13 @@ impl DemoUI where D: Device { return; } - let bottom = debug_ui.framebuffer_size().y() - PADDING; + let bottom = debug_ui.ui.framebuffer_size().y() - PADDING; let effects_panel_y = bottom - (BUTTON_HEIGHT + PADDING + EFFECTS_PANEL_HEIGHT); - debug_ui.draw_solid_rounded_rect(device, - RectI32::new(Point2DI32::new(PADDING, effects_panel_y), - Point2DI32::new(EFFECTS_PANEL_WIDTH, - EFFECTS_PANEL_HEIGHT)), - WINDOW_COLOR); + debug_ui.ui.draw_solid_rounded_rect(device, + RectI32::new(Point2DI32::new(PADDING, effects_panel_y), + Point2DI32::new(EFFECTS_PANEL_WIDTH, + EFFECTS_PANEL_HEIGHT)), + WINDOW_COLOR); self.gamma_correction_effect_enabled = self.draw_effects_switch(device, @@ -262,13 +263,13 @@ impl DemoUI where D: Device { return; } - let bottom = debug_ui.framebuffer_size().y() - PADDING; + let bottom = debug_ui.ui.framebuffer_size().y() - PADDING; let rotate_panel_y = bottom - (BUTTON_HEIGHT + PADDING + ROTATE_PANEL_HEIGHT); let rotate_panel_origin = Point2DI32::new(ROTATE_PANEL_X, rotate_panel_y); let rotate_panel_size = Point2DI32::new(ROTATE_PANEL_WIDTH, ROTATE_PANEL_HEIGHT); - debug_ui.draw_solid_rounded_rect(device, - RectI32::new(rotate_panel_origin, rotate_panel_size), - WINDOW_COLOR); + debug_ui.ui.draw_solid_rounded_rect(device, + RectI32::new(rotate_panel_origin, rotate_panel_size), + WINDOW_COLOR); let (widget_x, widget_y) = (ROTATE_PANEL_X + PADDING, rotate_panel_y + PADDING); let widget_rect = RectI32::new(Point2DI32::new(widget_x, widget_y), @@ -283,13 +284,13 @@ impl DemoUI where D: Device { let slider_track_rect = RectI32::new(Point2DI32::new(widget_x, slider_track_y), Point2DI32::new(SLIDER_WIDTH, SLIDER_TRACK_HEIGHT)); - debug_ui.draw_rect_outline(device, slider_track_rect, TEXT_COLOR); + debug_ui.ui.draw_rect_outline(device, slider_track_rect, TEXT_COLOR); let slider_knob_x = widget_x + self.rotation - SLIDER_KNOB_WIDTH / 2; let slider_knob_rect = RectI32::new(Point2DI32::new(slider_knob_x, widget_y), Point2DI32::new(SLIDER_KNOB_WIDTH, SLIDER_KNOB_HEIGHT)); - debug_ui.draw_solid_rect(device, slider_knob_rect, TEXT_COLOR); + debug_ui.ui.draw_solid_rect(device, slider_knob_rect, TEXT_COLOR); } fn draw_button(&self, @@ -300,12 +301,12 @@ impl DemoUI where D: Device { texture: &D::Texture) -> bool { let button_rect = RectI32::new(origin, Point2DI32::new(BUTTON_WIDTH, BUTTON_HEIGHT)); - debug_ui.draw_solid_rounded_rect(device, button_rect, WINDOW_COLOR); - debug_ui.draw_rounded_rect_outline(device, button_rect, OUTLINE_COLOR); - debug_ui.draw_texture(device, - origin + Point2DI32::new(PADDING, PADDING), - texture, - BUTTON_ICON_COLOR); + debug_ui.ui.draw_solid_rounded_rect(device, button_rect, WINDOW_COLOR); + debug_ui.ui.draw_rounded_rect_outline(device, button_rect, OUTLINE_COLOR); + debug_ui.ui.draw_texture(device, + origin + Point2DI32::new(PADDING, PADDING), + texture, + BUTTON_ICON_COLOR); event.handle_mouse_down_in_rect(button_rect).is_some() } @@ -320,7 +321,7 @@ impl DemoUI where D: Device { -> bool { let text_x = PADDING * 2; let text_y = window_y + PADDING + BUTTON_TEXT_OFFSET + (BUTTON_HEIGHT + PADDING) * index; - debug_ui.draw_text(device, text, Point2DI32::new(text_x, text_y), false); + debug_ui.ui.draw_text(device, text, Point2DI32::new(text_x, text_y), false); let switch_x = PADDING + EFFECTS_PANEL_WIDTH - (SWITCH_SIZE + PADDING); let switch_y = window_y + PADDING + (BUTTON_HEIGHT + PADDING) * index; @@ -344,17 +345,20 @@ impl DemoUI where D: Device { -> bool { value = self.draw_switch(device, debug_ui, event, origin, value); - let off_size = debug_ui.measure_text(off_text); - let on_size = debug_ui.measure_text(on_text); + let off_size = debug_ui.ui.measure_text(off_text); + let on_size = debug_ui.ui.measure_text(on_text); let off_offset = SWITCH_HALF_SIZE / 2 - off_size / 2; let on_offset = SWITCH_HALF_SIZE + SWITCH_HALF_SIZE / 2 - on_size / 2; let text_top = BUTTON_TEXT_OFFSET; - debug_ui.draw_text(device, - off_text, - origin + Point2DI32::new(off_offset, text_top), - !value); - debug_ui.draw_text(device, on_text, origin + Point2DI32::new(on_offset, text_top), value); + debug_ui.ui.draw_text(device, + off_text, + origin + Point2DI32::new(off_offset, text_top), + !value); + debug_ui.ui.draw_text(device, + on_text, + origin + Point2DI32::new(on_offset, text_top), + value); value } @@ -377,14 +381,14 @@ impl DemoUI where D: Device { let off_color = if !value { WINDOW_COLOR } else { TEXT_COLOR }; let on_color = if value { WINDOW_COLOR } else { TEXT_COLOR }; - debug_ui.draw_texture(device, - origin + Point2DI32::new(off_offset, PADDING), - off_texture, - off_color); - debug_ui.draw_texture(device, - origin + Point2DI32::new(on_offset, PADDING), - on_texture, - on_color); + debug_ui.ui.draw_texture(device, + origin + Point2DI32::new(off_offset, PADDING), + off_texture, + off_color); + debug_ui.ui.draw_texture(device, + origin + Point2DI32::new(on_offset, PADDING), + on_texture, + on_color); value } @@ -401,20 +405,20 @@ impl DemoUI where D: Device { value = !value; } - debug_ui.draw_solid_rounded_rect(device, widget_rect, WINDOW_COLOR); - debug_ui.draw_rounded_rect_outline(device, widget_rect, OUTLINE_COLOR); + debug_ui.ui.draw_solid_rounded_rect(device, widget_rect, WINDOW_COLOR); + debug_ui.ui.draw_rounded_rect_outline(device, widget_rect, OUTLINE_COLOR); let highlight_size = Point2DI32::new(SWITCH_HALF_SIZE, BUTTON_HEIGHT); if !value { - debug_ui.draw_solid_rounded_rect(device, - RectI32::new(origin, highlight_size), - TEXT_COLOR); + debug_ui.ui.draw_solid_rounded_rect(device, + RectI32::new(origin, highlight_size), + TEXT_COLOR); } else { let x_offset = SWITCH_HALF_SIZE + 1; - debug_ui.draw_solid_rounded_rect(device, - RectI32::new(origin + Point2DI32::new(x_offset, 0), - highlight_size), - TEXT_COLOR); + debug_ui.ui.draw_solid_rounded_rect(device, + RectI32::new(origin + Point2DI32::new(x_offset, 0), + highlight_size), + TEXT_COLOR); } value diff --git a/geometry/src/color.rs b/geometry/src/color.rs new file mode 100644 index 00000000..5b3ed9bb --- /dev/null +++ b/geometry/src/color.rs @@ -0,0 +1,78 @@ +// pathfinder/geometry/src/color.rs +// +// Copyright © 2019 The Pathfinder Project Developers. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use pathfinder_simd::default::F32x4; +use std::fmt::{self, Debug, Formatter}; + +#[derive(Clone, Copy, PartialEq, Eq, Hash, Default)] +pub struct ColorU { + pub r: u8, + pub g: u8, + pub b: u8, + pub a: u8, +} + +impl ColorU { + #[inline] + pub fn black() -> ColorU { + ColorU { + r: 0, + g: 0, + b: 0, + a: 255, + } + } + + #[inline] + pub fn to_f32(&self) -> ColorF { + let color = F32x4::new(self.r as f32, self.g as f32, self.b as f32, self.a as f32); + ColorF(color * F32x4::splat(1.0 / 255.0)) + } +} + +impl Debug for ColorU { + fn fmt(&self, formatter: &mut Formatter) -> fmt::Result { + if self.a == 255 { + write!(formatter, "#{:02x}{:02x}{:02x}", self.r, self.g, self.b) + } else { + write!(formatter, + "rgba({}, {}, {}, {})", + self.r, + self.g, + self.b, + self.a as f32 / 255.0) + } + } +} + +#[derive(Clone, Copy)] +pub struct ColorF(pub F32x4); + +impl ColorF { + #[inline] + pub fn r(&self) -> f32 { + self.0[0] + } + + #[inline] + pub fn g(&self) -> f32 { + self.0[1] + } + + #[inline] + pub fn b(&self) -> f32 { + self.0[2] + } + + #[inline] + pub fn a(&self) -> f32 { + self.0[3] + } +} diff --git a/geometry/src/lib.rs b/geometry/src/lib.rs index d3251ffc..e3cab18e 100644 --- a/geometry/src/lib.rs +++ b/geometry/src/lib.rs @@ -17,6 +17,7 @@ extern crate bitflags; pub mod basic; pub mod clip; +pub mod color; pub mod monotonic; pub mod orientation; pub mod outline; diff --git a/renderer/Cargo.toml b/renderer/Cargo.toml index 92884951..d1b26665 100644 --- a/renderer/Cargo.toml +++ b/renderer/Cargo.toml @@ -23,5 +23,8 @@ path = "../gpu" [dependencies.pathfinder_simd] path = "../simd" +[dependencies.pathfinder_ui] +path = "../ui" + [dev-dependencies] quickcheck = "0.7" diff --git a/renderer/src/gpu/debug.rs b/renderer/src/gpu/debug.rs index 9b256716..6ef183ba 100644 --- a/renderer/src/gpu/debug.rs +++ b/renderer/src/gpu/debug.rs @@ -16,92 +16,23 @@ //! The debug font atlas was generated using: https://evanw.github.io/font-texture-generator/ use crate::gpu_data::Stats; -use crate::paint::ColorU; use pathfinder_geometry::basic::point::Point2DI32; use pathfinder_geometry::basic::rect::RectI32; -use pathfinder_gpu::{BlendState, BufferTarget, BufferUploadMode, Device, Primitive, RenderState}; -use pathfinder_gpu::{Resources, UniformData, VertexAttrType}; -use pathfinder_simd::default::F32x4; -use serde_json; -use std::collections::{HashMap, VecDeque}; -use std::fs::File; -use std::io::BufReader; +use pathfinder_gpu::{Device, Resources}; +use pathfinder_ui::{PADDING, UI, WINDOW_COLOR}; +use std::collections::VecDeque; use std::ops::{Add, Div}; use std::time::Duration; const SAMPLE_BUFFER_SIZE: usize = 60; -const DEBUG_TEXTURE_VERTEX_SIZE: usize = 8; -const DEBUG_SOLID_VERTEX_SIZE: usize = 4; - -pub const PADDING: i32 = 12; - -pub static TEXT_COLOR: ColorU = ColorU { r: 255, g: 255, b: 255, a: 255 }; -pub static WINDOW_COLOR: ColorU = ColorU { r: 0, g: 0, b: 0, a: 255 - 90 }; - -static INVERTED_TEXT_COLOR: ColorU = ColorU { r: 0, g: 0, b: 0, a: 255 }; - const PERF_WINDOW_WIDTH: i32 = 375; const PERF_WINDOW_HEIGHT: i32 = LINE_HEIGHT * 6 + PADDING + 2; const FONT_ASCENT: i32 = 28; const LINE_HEIGHT: i32 = 42; -static FONT_JSON_FILENAME: &'static str = "debug-font.json"; -static FONT_PNG_NAME: &'static str = "debug-font"; - -static CORNER_FILL_PNG_NAME: &'static str = "debug-corner-fill"; -static CORNER_OUTLINE_PNG_NAME: &'static str = "debug-corner-outline"; - -static QUAD_INDICES: [u32; 6] = [0, 1, 3, 1, 2, 3]; -static RECT_LINE_INDICES: [u32; 8] = [0, 1, 1, 2, 2, 3, 3, 0]; -static OUTLINE_RECT_LINE_INDICES: [u32; 8] = [0, 1, 2, 3, 4, 5, 6, 7]; - -#[derive(Deserialize)] -#[allow(dead_code)] -pub struct DebugFont { - name: String, - size: i32, - bold: bool, - italic: bool, - width: u32, - height: u32, - characters: HashMap, -} - -#[derive(Deserialize)] -struct DebugCharacter { - x: i32, - y: i32, - width: i32, - height: i32, - #[serde(rename = "originX")] - origin_x: i32, - #[serde(rename = "originY")] - origin_y: i32, - advance: i32, -} - -impl DebugFont { - fn load(resources: &Resources) -> DebugFont { - let mut path = resources.resources_directory.clone(); - path.push(FONT_JSON_FILENAME); - - serde_json::from_reader(BufReader::new(File::open(path).unwrap())).unwrap() - } -} - pub struct DebugUI where D: Device { - framebuffer_size: Point2DI32, - - texture_program: DebugTextureProgram, - texture_vertex_array: DebugTextureVertexArray, - font: DebugFont, - solid_program: DebugSolidProgram, - solid_vertex_array: DebugSolidVertexArray, - - font_texture: D::Texture, - corner_fill_texture: D::Texture, - corner_outline_texture: D::Texture, + pub ui: UI, cpu_samples: SampleBuffer, gpu_samples: SampleBuffer, @@ -109,42 +40,8 @@ pub struct DebugUI where D: Device { impl DebugUI where D: Device { pub fn new(device: &D, resources: &Resources, framebuffer_size: Point2DI32) -> DebugUI { - let texture_program = DebugTextureProgram::new(device, resources); - let texture_vertex_array = DebugTextureVertexArray::new(device, &texture_program); - let font = DebugFont::load(resources); - - let solid_program = DebugSolidProgram::new(device, resources); - let solid_vertex_array = DebugSolidVertexArray::new(device, &solid_program); - - let font_texture = device.create_texture_from_png(resources, FONT_PNG_NAME); - let corner_fill_texture = device.create_texture_from_png(resources, CORNER_FILL_PNG_NAME); - let corner_outline_texture = device.create_texture_from_png(resources, - CORNER_OUTLINE_PNG_NAME); - - DebugUI { - framebuffer_size, - - texture_program, - texture_vertex_array, - font, - solid_program, - solid_vertex_array, - - font_texture, - corner_fill_texture, - corner_outline_texture, - - cpu_samples: SampleBuffer::new(), - gpu_samples: SampleBuffer::new(), - } - } - - pub fn framebuffer_size(&self) -> Point2DI32 { - self.framebuffer_size - } - - pub fn set_framebuffer_size(&mut self, window_size: Point2DI32) { - self.framebuffer_size = window_size; + let ui = UI::new(device, resources, framebuffer_size); + DebugUI { ui, cpu_samples: SampleBuffer::new(), gpu_samples: SampleBuffer::new() } } pub fn add_sample(&mut self, @@ -159,460 +56,43 @@ impl DebugUI where D: Device { pub fn draw(&self, device: &D) { // Draw performance window. - let bottom = self.framebuffer_size.y() - PADDING; + let framebuffer_size = self.ui.framebuffer_size(); + let bottom = framebuffer_size.y() - PADDING; let window_rect = RectI32::new( - Point2DI32::new(self.framebuffer_size.x() - PADDING - PERF_WINDOW_WIDTH, + Point2DI32::new(framebuffer_size.x() - PADDING - PERF_WINDOW_WIDTH, bottom - PERF_WINDOW_HEIGHT), Point2DI32::new(PERF_WINDOW_WIDTH, PERF_WINDOW_HEIGHT)); - self.draw_solid_rounded_rect(device, window_rect, WINDOW_COLOR); + self.ui.draw_solid_rounded_rect(device, window_rect, WINDOW_COLOR); let origin = window_rect.origin() + Point2DI32::new(PADDING, PADDING + FONT_ASCENT); let mean_cpu_sample = self.cpu_samples.mean(); - self.draw_text(device, + self.ui.draw_text(device, &format!("Objects: {}", mean_cpu_sample.stats.object_count), origin, false); - self.draw_text(device, + self.ui.draw_text(device, &format!("Solid Tiles: {}", mean_cpu_sample.stats.solid_tile_count), origin + Point2DI32::new(0, LINE_HEIGHT * 1), false); - self.draw_text(device, - &format!("Mask Tiles: {}", mean_cpu_sample.stats.mask_tile_count), - origin + Point2DI32::new(0, LINE_HEIGHT * 2), - false); - self.draw_text(device, - &format!("Fills: {}", mean_cpu_sample.stats.fill_count), - origin + Point2DI32::new(0, LINE_HEIGHT * 3), - false); + self.ui.draw_text(device, + &format!("Mask Tiles: {}", mean_cpu_sample.stats.mask_tile_count), + origin + Point2DI32::new(0, LINE_HEIGHT * 2), + false); + self.ui.draw_text(device, + &format!("Fills: {}", mean_cpu_sample.stats.fill_count), + origin + Point2DI32::new(0, LINE_HEIGHT * 3), + false); - self.draw_text(device, - &format!("CPU Time: {:.3} ms", duration_to_ms(mean_cpu_sample.elapsed)), - origin + Point2DI32::new(0, LINE_HEIGHT * 4), - false); + self.ui.draw_text(device, + &format!("CPU Time: {:.3} ms", duration_to_ms(mean_cpu_sample.elapsed)), + origin + Point2DI32::new(0, LINE_HEIGHT * 4), + false); let mean_gpu_sample = self.gpu_samples.mean(); - self.draw_text(device, - &format!("GPU Time: {:.3} ms", duration_to_ms(mean_gpu_sample.elapsed)), - origin + Point2DI32::new(0, LINE_HEIGHT * 5), - false); - } - - pub fn draw_solid_rect(&self, device: &D, rect: RectI32, color: ColorU) { - self.draw_rect(device, rect, color, true); - } - - pub fn draw_rect_outline(&self, device: &D, rect: RectI32, color: ColorU) { - self.draw_rect(device, rect, color, false); - } - - fn draw_rect(&self, device: &D, rect: RectI32, color: ColorU, filled: bool) { - let vertex_data = [ - DebugSolidVertex::new(rect.origin()), - DebugSolidVertex::new(rect.upper_right()), - DebugSolidVertex::new(rect.lower_right()), - DebugSolidVertex::new(rect.lower_left()), - ]; - - if filled { - self.draw_solid_rects_with_vertex_data(device, - &vertex_data, - &QUAD_INDICES, - color, - true); - } else { - self.draw_solid_rects_with_vertex_data(device, - &vertex_data, - &RECT_LINE_INDICES, - color, - false); - } - } - - fn draw_solid_rects_with_vertex_data(&self, - device: &D, - vertex_data: &[DebugSolidVertex], - index_data: &[u32], - color: ColorU, - filled: bool) { - device.bind_vertex_array(&self.solid_vertex_array.vertex_array); - - device.upload_to_buffer(&self.solid_vertex_array.vertex_buffer, - vertex_data, - BufferTarget::Vertex, - BufferUploadMode::Dynamic); - device.upload_to_buffer(&self.solid_vertex_array.index_buffer, - index_data, - BufferTarget::Index, - BufferUploadMode::Dynamic); - - device.use_program(&self.solid_program.program); - device.set_uniform(&self.solid_program.framebuffer_size_uniform, - UniformData::Vec2(self.framebuffer_size.0.to_f32x4())); - set_color_uniform(device, &self.solid_program.color_uniform, color); - - let primitive = if filled { Primitive::Triangles } else { Primitive::Lines }; - device.draw_elements(primitive, index_data.len() as u32, &RenderState { - blend: BlendState::RGBOneAlphaOneMinusSrcAlpha, - ..RenderState::default() - }); - } - - pub fn draw_text(&self, device: &D, string: &str, origin: Point2DI32, invert: bool) { - let mut next = origin; - let char_count = string.chars().count(); - let mut vertex_data = Vec::with_capacity(char_count * 4); - let mut index_data = Vec::with_capacity(char_count * 6); - for mut character in string.chars() { - if !self.font.characters.contains_key(&character) { - character = '?'; - } - - let info = &self.font.characters[&character]; - let position_rect = - RectI32::new(Point2DI32::new(next.x() - info.origin_x, next.y() - info.origin_y), - Point2DI32::new(info.width as i32, info.height as i32)); - let tex_coord_rect = RectI32::new(Point2DI32::new(info.x, info.y), - Point2DI32::new(info.width, info.height)); - let first_vertex_index = vertex_data.len(); - vertex_data.extend_from_slice(&[ - DebugTextureVertex::new(position_rect.origin(), tex_coord_rect.origin()), - DebugTextureVertex::new(position_rect.upper_right(), tex_coord_rect.upper_right()), - DebugTextureVertex::new(position_rect.lower_right(), tex_coord_rect.lower_right()), - DebugTextureVertex::new(position_rect.lower_left(), tex_coord_rect.lower_left()), - ]); - index_data.extend(QUAD_INDICES.iter().map(|&i| i + first_vertex_index as u32)); - - let next_x = next.x() + info.advance; - next.set_x(next_x); - } - - let color = if invert { INVERTED_TEXT_COLOR } else { TEXT_COLOR }; - self.draw_texture_with_vertex_data(device, - &vertex_data, - &index_data, - &self.font_texture, - color); - } - - pub fn draw_texture(&self, - device: &D, - origin: Point2DI32, - texture: &D::Texture, - color: ColorU) { - let position_rect = RectI32::new(origin, device.texture_size(&texture)); - let tex_coord_rect = RectI32::new(Point2DI32::default(), position_rect.size()); - let vertex_data = [ - DebugTextureVertex::new(position_rect.origin(), tex_coord_rect.origin()), - DebugTextureVertex::new(position_rect.upper_right(), tex_coord_rect.upper_right()), - DebugTextureVertex::new(position_rect.lower_right(), tex_coord_rect.lower_right()), - DebugTextureVertex::new(position_rect.lower_left(), tex_coord_rect.lower_left()), - ]; - - self.draw_texture_with_vertex_data(device, &vertex_data, &QUAD_INDICES, texture, color); - } - - pub fn measure_text(&self, string: &str) -> i32 { - let mut next = 0; - for mut character in string.chars() { - if !self.font.characters.contains_key(&character) { - character = '?'; - } - - let info = &self.font.characters[&character]; - next += info.advance; - } - next - } - - pub fn draw_solid_rounded_rect(&self, device: &D, rect: RectI32, color: ColorU) { - let corner_texture = self.corner_texture(true); - let corner_rects = CornerRects::new(device, rect, corner_texture); - self.draw_rounded_rect_corners(device, color, corner_texture, &corner_rects); - - let solid_rect_mid = RectI32::from_points(corner_rects.upper_left.upper_right(), - corner_rects.lower_right.lower_left()); - let solid_rect_left = RectI32::from_points(corner_rects.upper_left.lower_left(), - corner_rects.lower_left.upper_right()); - let solid_rect_right = RectI32::from_points(corner_rects.upper_right.lower_left(), - corner_rects.lower_right.upper_right()); - let vertex_data = vec![ - DebugSolidVertex::new(solid_rect_mid.origin()), - DebugSolidVertex::new(solid_rect_mid.upper_right()), - DebugSolidVertex::new(solid_rect_mid.lower_right()), - DebugSolidVertex::new(solid_rect_mid.lower_left()), - - DebugSolidVertex::new(solid_rect_left.origin()), - DebugSolidVertex::new(solid_rect_left.upper_right()), - DebugSolidVertex::new(solid_rect_left.lower_right()), - DebugSolidVertex::new(solid_rect_left.lower_left()), - - DebugSolidVertex::new(solid_rect_right.origin()), - DebugSolidVertex::new(solid_rect_right.upper_right()), - DebugSolidVertex::new(solid_rect_right.lower_right()), - DebugSolidVertex::new(solid_rect_right.lower_left()), - ]; - - let mut index_data = Vec::with_capacity(18); - index_data.extend(QUAD_INDICES.iter().map(|&index| index + 0)); - index_data.extend(QUAD_INDICES.iter().map(|&index| index + 4)); - index_data.extend(QUAD_INDICES.iter().map(|&index| index + 8)); - - self.draw_solid_rects_with_vertex_data(device, - &vertex_data, - &index_data[0..18], - color, - true); - } - - pub fn draw_rounded_rect_outline(&self, device: &D, rect: RectI32, color: ColorU) { - let corner_texture = self.corner_texture(false); - let corner_rects = CornerRects::new(device, rect, corner_texture); - self.draw_rounded_rect_corners(device, color, corner_texture, &corner_rects); - - let vertex_data = vec![ - DebugSolidVertex::new(corner_rects.upper_left.upper_right()), - DebugSolidVertex::new(corner_rects.upper_right.origin()), - DebugSolidVertex::new(corner_rects.upper_right.lower_right()), - DebugSolidVertex::new(corner_rects.lower_right.upper_right()), - DebugSolidVertex::new(corner_rects.lower_left.lower_right()), - DebugSolidVertex::new(corner_rects.lower_right.lower_left()), - DebugSolidVertex::new(corner_rects.upper_left.lower_left()), - DebugSolidVertex::new(corner_rects.lower_left.origin()), - ]; - - let index_data = &OUTLINE_RECT_LINE_INDICES; - self.draw_solid_rects_with_vertex_data(device, &vertex_data, index_data, color, false); - } - - fn draw_rounded_rect_corners(&self, - device: &D, - color: ColorU, - texture: &D::Texture, - corner_rects: &CornerRects) { - let corner_size = device.texture_size(&texture); - let tex_coord_rect = RectI32::new(Point2DI32::default(), corner_size); - - let vertex_data = vec![ - DebugTextureVertex::new( - corner_rects.upper_left.origin(), tex_coord_rect.origin()), - DebugTextureVertex::new( - corner_rects.upper_left.upper_right(), tex_coord_rect.upper_right()), - DebugTextureVertex::new( - corner_rects.upper_left.lower_right(), tex_coord_rect.lower_right()), - DebugTextureVertex::new( - corner_rects.upper_left.lower_left(), tex_coord_rect.lower_left()), - - DebugTextureVertex::new( - corner_rects.upper_right.origin(), tex_coord_rect.lower_left()), - DebugTextureVertex::new( - corner_rects.upper_right.upper_right(), tex_coord_rect.origin()), - DebugTextureVertex::new( - corner_rects.upper_right.lower_right(), tex_coord_rect.upper_right()), - DebugTextureVertex::new( - corner_rects.upper_right.lower_left(), tex_coord_rect.lower_right()), - - DebugTextureVertex::new( - corner_rects.lower_left.origin(), tex_coord_rect.upper_right()), - DebugTextureVertex::new( - corner_rects.lower_left.upper_right(), tex_coord_rect.lower_right()), - DebugTextureVertex::new( - corner_rects.lower_left.lower_right(), tex_coord_rect.lower_left()), - DebugTextureVertex::new( - corner_rects.lower_left.lower_left(), tex_coord_rect.origin()), - - DebugTextureVertex::new( - corner_rects.lower_right.origin(), tex_coord_rect.lower_right()), - DebugTextureVertex::new( - corner_rects.lower_right.upper_right(), tex_coord_rect.lower_left()), - DebugTextureVertex::new( - corner_rects.lower_right.lower_right(), tex_coord_rect.origin()), - DebugTextureVertex::new( - corner_rects.lower_right.lower_left(), tex_coord_rect.upper_right()), - ]; - - let mut index_data = Vec::with_capacity(24); - index_data.extend(QUAD_INDICES.iter().map(|&index| index + 0)); - index_data.extend(QUAD_INDICES.iter().map(|&index| index + 4)); - index_data.extend(QUAD_INDICES.iter().map(|&index| index + 8)); - index_data.extend(QUAD_INDICES.iter().map(|&index| index + 12)); - - self.draw_texture_with_vertex_data(device, &vertex_data, &index_data, texture, color); - } - - fn corner_texture(&self, filled: bool) -> &D::Texture { - if filled { &self.corner_fill_texture } else { &self.corner_outline_texture } - } - - fn draw_texture_with_vertex_data(&self, - device: &D, - vertex_data: &[DebugTextureVertex], - index_data: &[u32], - texture: &D::Texture, - color: ColorU) { - device.upload_to_buffer(&self.texture_vertex_array.vertex_buffer, - vertex_data, - BufferTarget::Vertex, - BufferUploadMode::Dynamic); - device.upload_to_buffer(&self.texture_vertex_array.index_buffer, - index_data, - BufferTarget::Index, - BufferUploadMode::Dynamic); - - device.bind_vertex_array(&self.texture_vertex_array.vertex_array); - device.use_program(&self.texture_program.program); - device.set_uniform(&self.texture_program.framebuffer_size_uniform, - UniformData::Vec2(self.framebuffer_size.0.to_f32x4())); - device.set_uniform(&self.texture_program.texture_size_uniform, - UniformData::Vec2(device.texture_size(&texture).0.to_f32x4())); - set_color_uniform(device, &self.texture_program.color_uniform, color); - device.bind_texture(texture, 0); - device.set_uniform(&self.texture_program.texture_uniform, UniformData::TextureUnit(0)); - - device.draw_elements(Primitive::Triangles, index_data.len() as u32, &RenderState { - blend: BlendState::RGBOneAlphaOneMinusSrcAlpha, - ..RenderState::default() - }); - } -} - -struct DebugTextureVertexArray where D: Device { - vertex_array: D::VertexArray, - vertex_buffer: D::Buffer, - index_buffer: D::Buffer, -} - -impl DebugTextureVertexArray where D: Device { - fn new(device: &D, debug_texture_program: &DebugTextureProgram) - -> DebugTextureVertexArray { - let (vertex_buffer, index_buffer) = (device.create_buffer(), device.create_buffer()); - let vertex_array = device.create_vertex_array(); - - let position_attr = device.get_vertex_attr(&debug_texture_program.program, "Position"); - let tex_coord_attr = device.get_vertex_attr(&debug_texture_program.program, "TexCoord"); - - device.bind_vertex_array(&vertex_array); - device.use_program(&debug_texture_program.program); - device.bind_buffer(&vertex_buffer, BufferTarget::Vertex); - device.bind_buffer(&index_buffer, BufferTarget::Index); - device.configure_float_vertex_attr(&position_attr, - 2, - VertexAttrType::U16, - false, - DEBUG_TEXTURE_VERTEX_SIZE, - 0, - 0); - device.configure_float_vertex_attr(&tex_coord_attr, - 2, - VertexAttrType::U16, - false, - DEBUG_TEXTURE_VERTEX_SIZE, - 4, - 0); - - DebugTextureVertexArray { vertex_array, vertex_buffer, index_buffer } - } -} - -struct DebugSolidVertexArray where D: Device { - vertex_array: D::VertexArray, - vertex_buffer: D::Buffer, - index_buffer: D::Buffer, -} - -impl DebugSolidVertexArray where D: Device { - fn new(device: &D, debug_solid_program: &DebugSolidProgram) -> DebugSolidVertexArray { - let (vertex_buffer, index_buffer) = (device.create_buffer(), device.create_buffer()); - let vertex_array = device.create_vertex_array(); - - let position_attr = device.get_vertex_attr(&debug_solid_program.program, "Position"); - device.bind_vertex_array(&vertex_array); - device.use_program(&debug_solid_program.program); - device.bind_buffer(&vertex_buffer, BufferTarget::Vertex); - device.bind_buffer(&index_buffer, BufferTarget::Index); - device.configure_float_vertex_attr(&position_attr, - 2, - VertexAttrType::U16, - false, - DEBUG_SOLID_VERTEX_SIZE, - 0, - 0); - - DebugSolidVertexArray { vertex_array, vertex_buffer, index_buffer } - } -} - -struct DebugTextureProgram where D: Device { - program: D::Program, - framebuffer_size_uniform: D::Uniform, - texture_size_uniform: D::Uniform, - texture_uniform: D::Uniform, - color_uniform: D::Uniform, -} - -impl DebugTextureProgram where D: Device { - fn new(device: &D, resources: &Resources) -> DebugTextureProgram { - let program = device.create_program(resources, "debug_texture"); - let framebuffer_size_uniform = device.get_uniform(&program, "FramebufferSize"); - let texture_size_uniform = device.get_uniform(&program, "TextureSize"); - let texture_uniform = device.get_uniform(&program, "Texture"); - let color_uniform = device.get_uniform(&program, "Color"); - DebugTextureProgram { - program, - framebuffer_size_uniform, - texture_size_uniform, - texture_uniform, - color_uniform, - } - } -} - -struct DebugSolidProgram where D: Device { - program: D::Program, - framebuffer_size_uniform: D::Uniform, - color_uniform: D::Uniform, -} - -impl DebugSolidProgram where D: Device { - fn new(device: &D, resources: &Resources) -> DebugSolidProgram { - let program = device.create_program(resources, "debug_solid"); - let framebuffer_size_uniform = device.get_uniform(&program, "FramebufferSize"); - let color_uniform = device.get_uniform(&program, "Color"); - DebugSolidProgram { program, framebuffer_size_uniform, color_uniform } - } -} - -#[derive(Clone, Copy, Debug)] -#[allow(dead_code)] -#[repr(C)] -struct DebugTextureVertex { - position_x: i16, - position_y: i16, - tex_coord_x: u16, - tex_coord_y: u16, -} - -impl DebugTextureVertex { - fn new(position: Point2DI32, tex_coord: Point2DI32) -> DebugTextureVertex { - DebugTextureVertex { - position_x: position.x() as i16, - position_y: position.y() as i16, - tex_coord_x: tex_coord.x() as u16, - tex_coord_y: tex_coord.y() as u16, - } - } -} - -#[derive(Clone, Copy)] -#[allow(dead_code)] -#[repr(C)] -struct DebugSolidVertex { - position_x: i16, - position_y: i16, -} - -impl DebugSolidVertex { - fn new(position: Point2DI32) -> DebugSolidVertex { - DebugSolidVertex { position_x: position.x() as i16, position_y: position.y() as i16 } + self.ui.draw_text(device, + &format!("GPU Time: {:.3} ms", duration_to_ms(mean_gpu_sample.elapsed)), + origin + Point2DI32::new(0, LINE_HEIGHT * 5), + false); } } @@ -646,11 +126,6 @@ impl SampleBuffer where S: Add + Div + Clone + } } -fn set_color_uniform(device: &D, uniform: &D::Uniform, color: ColorU) where D: Device { - let color = F32x4::new(color.r as f32, color.g as f32, color.b as f32, color.a as f32); - device.set_uniform(uniform, UniformData::Vec4(color * F32x4::splat(1.0 / 255.0))); -} - #[derive(Clone, Default)] struct CPUSample { elapsed: Duration, @@ -709,22 +184,3 @@ impl Div for GPUSample { fn duration_to_ms(time: Duration) -> f64 { time.as_secs() as f64 * 1000.0 + time.subsec_nanos() as f64 / 1000000.0 } - -struct CornerRects { - upper_left: RectI32, - upper_right: RectI32, - lower_left: RectI32, - lower_right: RectI32, -} - -impl CornerRects { - fn new(device: &D, rect: RectI32, texture: &D::Texture) -> CornerRects where D: Device { - let size = device.texture_size(texture); - CornerRects { - upper_left: RectI32::new(rect.origin(), size), - upper_right: RectI32::new(rect.upper_right() - Point2DI32::new(size.x(), 0), size), - lower_left: RectI32::new(rect.lower_left() - Point2DI32::new(0, size.y()), size), - lower_right: RectI32::new(rect.lower_right() - size, size), - } - } -} diff --git a/renderer/src/gpu/renderer.rs b/renderer/src/gpu/renderer.rs index d94dbd12..473537c9 100644 --- a/renderer/src/gpu/renderer.rs +++ b/renderer/src/gpu/renderer.rs @@ -10,10 +10,11 @@ use crate::gpu::debug::DebugUI; use crate::gpu_data::{Batch, BuiltScene, SolidTileScenePrimitive}; -use crate::paint::{ColorU, ObjectShader}; +use crate::paint::ObjectShader; use crate::post::DefringingKernel; use crate::tiles::{TILE_HEIGHT, TILE_WIDTH}; use pathfinder_geometry::basic::point::{Point2DI32, Point3DF32}; +use pathfinder_geometry::color::ColorU; use pathfinder_gpu::{BlendState, BufferTarget, BufferUploadMode, DepthFunc, DepthState, Device}; use pathfinder_gpu::{Primitive, RenderState, Resources, StencilFunc, StencilState, TextureFormat}; use pathfinder_gpu::{UniformData, VertexAttrType}; @@ -193,7 +194,7 @@ impl Renderer where D: Device { #[inline] pub fn set_main_framebuffer_size(&mut self, new_framebuffer_size: Point2DI32) { self.main_framebuffer_size = new_framebuffer_size; - self.debug_ui.set_framebuffer_size(new_framebuffer_size); + self.debug_ui.ui.set_framebuffer_size(new_framebuffer_size); } #[inline] diff --git a/renderer/src/paint.rs b/renderer/src/paint.rs index 6c8b72cd..324201ec 100644 --- a/renderer/src/paint.rs +++ b/renderer/src/paint.rs @@ -10,8 +10,7 @@ //! How a path is to be filled. -use pathfinder_simd::default::F32x4; -use std::fmt::{self, Debug, Formatter}; +use pathfinder_geometry::color::ColorU; #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] pub struct Paint { @@ -21,72 +20,6 @@ pub struct Paint { #[derive(Clone, Copy, PartialEq, Debug)] pub struct PaintId(pub u16); -#[derive(Clone, Copy, PartialEq, Eq, Hash, Default)] -pub struct ColorU { - pub r: u8, - pub g: u8, - pub b: u8, - pub a: u8, -} - -impl ColorU { - #[inline] - pub fn black() -> ColorU { - ColorU { - r: 0, - g: 0, - b: 0, - a: 255, - } - } - - #[inline] - pub fn to_f32(&self) -> ColorF { - let color = F32x4::new(self.r as f32, self.g as f32, self.b as f32, self.a as f32); - ColorF(color * F32x4::splat(1.0 / 255.0)) - } -} - -impl Debug for ColorU { - fn fmt(&self, formatter: &mut Formatter) -> fmt::Result { - if self.a == 255 { - write!(formatter, "#{:02x}{:02x}{:02x}", self.r, self.g, self.b) - } else { - write!(formatter, - "rgba({}, {}, {}, {})", - self.r, - self.g, - self.b, - self.a as f32 / 255.0) - } - } -} - -#[derive(Clone, Copy)] -pub struct ColorF(pub F32x4); - -impl ColorF { - #[inline] - pub fn r(&self) -> f32 { - self.0[0] - } - - #[inline] - pub fn g(&self) -> f32 { - self.0[1] - } - - #[inline] - pub fn b(&self) -> f32 { - self.0[2] - } - - #[inline] - pub fn a(&self) -> f32 { - self.0[3] - } -} - #[derive(Clone, Copy, Debug, PartialEq)] pub struct ShaderId(pub u16); diff --git a/svg/src/lib.rs b/svg/src/lib.rs index 309c492e..419bce68 100644 --- a/svg/src/lib.rs +++ b/svg/src/lib.rs @@ -14,10 +14,11 @@ use pathfinder_geometry::basic::line_segment::LineSegmentF32; use pathfinder_geometry::basic::point::Point2DF32; use pathfinder_geometry::basic::rect::RectF32; use pathfinder_geometry::basic::transform2d::{Transform2DF32, Transform2DF32PathIter}; +use pathfinder_geometry::color::ColorU; use pathfinder_geometry::outline::Outline; use pathfinder_geometry::segment::{Segment, SegmentFlags}; use pathfinder_geometry::stroke::OutlineStrokeToFill; -use pathfinder_renderer::paint::{ColorU, Paint}; +use pathfinder_renderer::paint::Paint; use pathfinder_renderer::scene::{PathObject, PathObjectKind, Scene}; use std::mem; use usvg::{Color as SvgColor, Node, NodeExt, NodeKind, Paint as UsvgPaint}; diff --git a/ui/Cargo.toml b/ui/Cargo.toml index 28835996..259f8c26 100644 --- a/ui/Cargo.toml +++ b/ui/Cargo.toml @@ -3,3 +3,21 @@ name = "pathfinder_ui" version = "0.1.0" authors = ["Patrick Walton "] edition = "2018" + +[dependencies] +serde = "1.0" +serde_derive = "1.0" +serde_json = "1.0" + +[dependencies.hashbrown] +version = "0.1" +features = ["serde"] + +[dependencies.pathfinder_geometry] +path = "../geometry" + +[dependencies.pathfinder_gpu] +path = "../gpu" + +[dependencies.pathfinder_simd] +path = "../simd" diff --git a/ui/src/lib.rs b/ui/src/lib.rs index 8b137891..9960da65 100644 --- a/ui/src/lib.rs +++ b/ui/src/lib.rs @@ -1 +1,577 @@ +// pathfinder/ui/src/lib.rs +// +// Copyright © 2019 The Pathfinder Project Developers. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. +//! A minimal immediate mode UI, for debugging. +//! +//! This can be used in your own applications as an ultra-minimal lightweight +//! alternative to dear imgui, Conrod, etc. + +#[macro_use] +extern crate serde_derive; + +use hashbrown::HashMap; +use pathfinder_geometry::basic::point::Point2DI32; +use pathfinder_geometry::basic::rect::RectI32; +use pathfinder_geometry::color::ColorU; +use pathfinder_gpu::{BlendState, BufferTarget, BufferUploadMode, Device, Primitive, RenderState}; +use pathfinder_gpu::{Resources, UniformData, VertexAttrType}; +use pathfinder_simd::default::F32x4; +use serde_json; +use std::fs::File; +use std::io::BufReader; + +const DEBUG_TEXTURE_VERTEX_SIZE: usize = 8; +const DEBUG_SOLID_VERTEX_SIZE: usize = 4; + +pub const PADDING: i32 = 12; + +pub static TEXT_COLOR: ColorU = ColorU { r: 255, g: 255, b: 255, a: 255 }; +pub static WINDOW_COLOR: ColorU = ColorU { r: 0, g: 0, b: 0, a: 255 - 90 }; + +static INVERTED_TEXT_COLOR: ColorU = ColorU { r: 0, g: 0, b: 0, a: 255 }; + +static FONT_JSON_FILENAME: &'static str = "debug-font.json"; +static FONT_PNG_NAME: &'static str = "debug-font"; + +static CORNER_FILL_PNG_NAME: &'static str = "debug-corner-fill"; +static CORNER_OUTLINE_PNG_NAME: &'static str = "debug-corner-outline"; + +static QUAD_INDICES: [u32; 6] = [0, 1, 3, 1, 2, 3]; +static RECT_LINE_INDICES: [u32; 8] = [0, 1, 1, 2, 2, 3, 3, 0]; +static OUTLINE_RECT_LINE_INDICES: [u32; 8] = [0, 1, 2, 3, 4, 5, 6, 7]; + +#[derive(Deserialize)] +#[allow(dead_code)] +pub struct DebugFont { + name: String, + size: i32, + bold: bool, + italic: bool, + width: u32, + height: u32, + characters: HashMap, +} + +#[derive(Deserialize)] +struct DebugCharacter { + x: i32, + y: i32, + width: i32, + height: i32, + #[serde(rename = "originX")] + origin_x: i32, + #[serde(rename = "originY")] + origin_y: i32, + advance: i32, +} + +impl DebugFont { + fn load(resources: &Resources) -> DebugFont { + let mut path = resources.resources_directory.clone(); + path.push(FONT_JSON_FILENAME); + + serde_json::from_reader(BufReader::new(File::open(path).unwrap())).unwrap() + } +} + +pub struct UI where D: Device { + framebuffer_size: Point2DI32, + + texture_program: DebugTextureProgram, + texture_vertex_array: DebugTextureVertexArray, + solid_program: DebugSolidProgram, + solid_vertex_array: DebugSolidVertexArray, + font: DebugFont, + + font_texture: D::Texture, + corner_fill_texture: D::Texture, + corner_outline_texture: D::Texture, +} + +impl UI where D: Device { + pub fn new(device: &D, resources: &Resources, framebuffer_size: Point2DI32) -> UI { + let texture_program = DebugTextureProgram::new(device, resources); + let texture_vertex_array = DebugTextureVertexArray::new(device, &texture_program); + let font = DebugFont::load(resources); + + let solid_program = DebugSolidProgram::new(device, resources); + let solid_vertex_array = DebugSolidVertexArray::new(device, &solid_program); + + let font_texture = device.create_texture_from_png(resources, FONT_PNG_NAME); + let corner_fill_texture = device.create_texture_from_png(resources, CORNER_FILL_PNG_NAME); + let corner_outline_texture = device.create_texture_from_png(resources, + CORNER_OUTLINE_PNG_NAME); + + UI { + framebuffer_size, + + texture_program, + texture_vertex_array, + font, + solid_program, + solid_vertex_array, + + font_texture, + corner_fill_texture, + corner_outline_texture, + } + } + + pub fn framebuffer_size(&self) -> Point2DI32 { + self.framebuffer_size + } + + pub fn set_framebuffer_size(&mut self, window_size: Point2DI32) { + self.framebuffer_size = window_size; + } + + + pub fn draw_solid_rect(&self, device: &D, rect: RectI32, color: ColorU) { + self.draw_rect(device, rect, color, true); + } + + pub fn draw_rect_outline(&self, device: &D, rect: RectI32, color: ColorU) { + self.draw_rect(device, rect, color, false); + } + + fn draw_rect(&self, device: &D, rect: RectI32, color: ColorU, filled: bool) { + let vertex_data = [ + DebugSolidVertex::new(rect.origin()), + DebugSolidVertex::new(rect.upper_right()), + DebugSolidVertex::new(rect.lower_right()), + DebugSolidVertex::new(rect.lower_left()), + ]; + + if filled { + self.draw_solid_rects_with_vertex_data(device, + &vertex_data, + &QUAD_INDICES, + color, + true); + } else { + self.draw_solid_rects_with_vertex_data(device, + &vertex_data, + &RECT_LINE_INDICES, + color, + false); + } + } + + fn draw_solid_rects_with_vertex_data(&self, + device: &D, + vertex_data: &[DebugSolidVertex], + index_data: &[u32], + color: ColorU, + filled: bool) { + device.bind_vertex_array(&self.solid_vertex_array.vertex_array); + + device.upload_to_buffer(&self.solid_vertex_array.vertex_buffer, + vertex_data, + BufferTarget::Vertex, + BufferUploadMode::Dynamic); + device.upload_to_buffer(&self.solid_vertex_array.index_buffer, + index_data, + BufferTarget::Index, + BufferUploadMode::Dynamic); + + device.use_program(&self.solid_program.program); + device.set_uniform(&self.solid_program.framebuffer_size_uniform, + UniformData::Vec2(self.framebuffer_size.0.to_f32x4())); + set_color_uniform(device, &self.solid_program.color_uniform, color); + + let primitive = if filled { Primitive::Triangles } else { Primitive::Lines }; + device.draw_elements(primitive, index_data.len() as u32, &RenderState { + blend: BlendState::RGBOneAlphaOneMinusSrcAlpha, + ..RenderState::default() + }); + } + + pub fn draw_text(&self, device: &D, string: &str, origin: Point2DI32, invert: bool) { + let mut next = origin; + let char_count = string.chars().count(); + let mut vertex_data = Vec::with_capacity(char_count * 4); + let mut index_data = Vec::with_capacity(char_count * 6); + for mut character in string.chars() { + if !self.font.characters.contains_key(&character) { + character = '?'; + } + + let info = &self.font.characters[&character]; + let position_rect = + RectI32::new(Point2DI32::new(next.x() - info.origin_x, next.y() - info.origin_y), + Point2DI32::new(info.width as i32, info.height as i32)); + let tex_coord_rect = RectI32::new(Point2DI32::new(info.x, info.y), + Point2DI32::new(info.width, info.height)); + let first_vertex_index = vertex_data.len(); + vertex_data.extend_from_slice(&[ + DebugTextureVertex::new(position_rect.origin(), tex_coord_rect.origin()), + DebugTextureVertex::new(position_rect.upper_right(), tex_coord_rect.upper_right()), + DebugTextureVertex::new(position_rect.lower_right(), tex_coord_rect.lower_right()), + DebugTextureVertex::new(position_rect.lower_left(), tex_coord_rect.lower_left()), + ]); + index_data.extend(QUAD_INDICES.iter().map(|&i| i + first_vertex_index as u32)); + + let next_x = next.x() + info.advance; + next.set_x(next_x); + } + + let color = if invert { INVERTED_TEXT_COLOR } else { TEXT_COLOR }; + self.draw_texture_with_vertex_data(device, + &vertex_data, + &index_data, + &self.font_texture, + color); + } + + pub fn draw_texture(&self, + device: &D, + origin: Point2DI32, + texture: &D::Texture, + color: ColorU) { + let position_rect = RectI32::new(origin, device.texture_size(&texture)); + let tex_coord_rect = RectI32::new(Point2DI32::default(), position_rect.size()); + let vertex_data = [ + DebugTextureVertex::new(position_rect.origin(), tex_coord_rect.origin()), + DebugTextureVertex::new(position_rect.upper_right(), tex_coord_rect.upper_right()), + DebugTextureVertex::new(position_rect.lower_right(), tex_coord_rect.lower_right()), + DebugTextureVertex::new(position_rect.lower_left(), tex_coord_rect.lower_left()), + ]; + + self.draw_texture_with_vertex_data(device, &vertex_data, &QUAD_INDICES, texture, color); + } + + pub fn measure_text(&self, string: &str) -> i32 { + let mut next = 0; + for mut character in string.chars() { + if !self.font.characters.contains_key(&character) { + character = '?'; + } + + let info = &self.font.characters[&character]; + next += info.advance; + } + next + } + + pub fn draw_solid_rounded_rect(&self, device: &D, rect: RectI32, color: ColorU) { + let corner_texture = self.corner_texture(true); + let corner_rects = CornerRects::new(device, rect, corner_texture); + self.draw_rounded_rect_corners(device, color, corner_texture, &corner_rects); + + let solid_rect_mid = RectI32::from_points(corner_rects.upper_left.upper_right(), + corner_rects.lower_right.lower_left()); + let solid_rect_left = RectI32::from_points(corner_rects.upper_left.lower_left(), + corner_rects.lower_left.upper_right()); + let solid_rect_right = RectI32::from_points(corner_rects.upper_right.lower_left(), + corner_rects.lower_right.upper_right()); + let vertex_data = vec![ + DebugSolidVertex::new(solid_rect_mid.origin()), + DebugSolidVertex::new(solid_rect_mid.upper_right()), + DebugSolidVertex::new(solid_rect_mid.lower_right()), + DebugSolidVertex::new(solid_rect_mid.lower_left()), + + DebugSolidVertex::new(solid_rect_left.origin()), + DebugSolidVertex::new(solid_rect_left.upper_right()), + DebugSolidVertex::new(solid_rect_left.lower_right()), + DebugSolidVertex::new(solid_rect_left.lower_left()), + + DebugSolidVertex::new(solid_rect_right.origin()), + DebugSolidVertex::new(solid_rect_right.upper_right()), + DebugSolidVertex::new(solid_rect_right.lower_right()), + DebugSolidVertex::new(solid_rect_right.lower_left()), + ]; + + let mut index_data = Vec::with_capacity(18); + index_data.extend(QUAD_INDICES.iter().map(|&index| index + 0)); + index_data.extend(QUAD_INDICES.iter().map(|&index| index + 4)); + index_data.extend(QUAD_INDICES.iter().map(|&index| index + 8)); + + self.draw_solid_rects_with_vertex_data(device, + &vertex_data, + &index_data[0..18], + color, + true); + } + + pub fn draw_rounded_rect_outline(&self, device: &D, rect: RectI32, color: ColorU) { + let corner_texture = self.corner_texture(false); + let corner_rects = CornerRects::new(device, rect, corner_texture); + self.draw_rounded_rect_corners(device, color, corner_texture, &corner_rects); + + let vertex_data = vec![ + DebugSolidVertex::new(corner_rects.upper_left.upper_right()), + DebugSolidVertex::new(corner_rects.upper_right.origin()), + DebugSolidVertex::new(corner_rects.upper_right.lower_right()), + DebugSolidVertex::new(corner_rects.lower_right.upper_right()), + DebugSolidVertex::new(corner_rects.lower_left.lower_right()), + DebugSolidVertex::new(corner_rects.lower_right.lower_left()), + DebugSolidVertex::new(corner_rects.upper_left.lower_left()), + DebugSolidVertex::new(corner_rects.lower_left.origin()), + ]; + + let index_data = &OUTLINE_RECT_LINE_INDICES; + self.draw_solid_rects_with_vertex_data(device, &vertex_data, index_data, color, false); + } + + fn draw_rounded_rect_corners(&self, + device: &D, + color: ColorU, + texture: &D::Texture, + corner_rects: &CornerRects) { + let corner_size = device.texture_size(&texture); + let tex_coord_rect = RectI32::new(Point2DI32::default(), corner_size); + + let vertex_data = vec![ + DebugTextureVertex::new( + corner_rects.upper_left.origin(), tex_coord_rect.origin()), + DebugTextureVertex::new( + corner_rects.upper_left.upper_right(), tex_coord_rect.upper_right()), + DebugTextureVertex::new( + corner_rects.upper_left.lower_right(), tex_coord_rect.lower_right()), + DebugTextureVertex::new( + corner_rects.upper_left.lower_left(), tex_coord_rect.lower_left()), + + DebugTextureVertex::new( + corner_rects.upper_right.origin(), tex_coord_rect.lower_left()), + DebugTextureVertex::new( + corner_rects.upper_right.upper_right(), tex_coord_rect.origin()), + DebugTextureVertex::new( + corner_rects.upper_right.lower_right(), tex_coord_rect.upper_right()), + DebugTextureVertex::new( + corner_rects.upper_right.lower_left(), tex_coord_rect.lower_right()), + + DebugTextureVertex::new( + corner_rects.lower_left.origin(), tex_coord_rect.upper_right()), + DebugTextureVertex::new( + corner_rects.lower_left.upper_right(), tex_coord_rect.lower_right()), + DebugTextureVertex::new( + corner_rects.lower_left.lower_right(), tex_coord_rect.lower_left()), + DebugTextureVertex::new( + corner_rects.lower_left.lower_left(), tex_coord_rect.origin()), + + DebugTextureVertex::new( + corner_rects.lower_right.origin(), tex_coord_rect.lower_right()), + DebugTextureVertex::new( + corner_rects.lower_right.upper_right(), tex_coord_rect.lower_left()), + DebugTextureVertex::new( + corner_rects.lower_right.lower_right(), tex_coord_rect.origin()), + DebugTextureVertex::new( + corner_rects.lower_right.lower_left(), tex_coord_rect.upper_right()), + ]; + + let mut index_data = Vec::with_capacity(24); + index_data.extend(QUAD_INDICES.iter().map(|&index| index + 0)); + index_data.extend(QUAD_INDICES.iter().map(|&index| index + 4)); + index_data.extend(QUAD_INDICES.iter().map(|&index| index + 8)); + index_data.extend(QUAD_INDICES.iter().map(|&index| index + 12)); + + self.draw_texture_with_vertex_data(device, &vertex_data, &index_data, texture, color); + } + + fn corner_texture(&self, filled: bool) -> &D::Texture { + if filled { &self.corner_fill_texture } else { &self.corner_outline_texture } + } + + fn draw_texture_with_vertex_data(&self, + device: &D, + vertex_data: &[DebugTextureVertex], + index_data: &[u32], + texture: &D::Texture, + color: ColorU) { + device.upload_to_buffer(&self.texture_vertex_array.vertex_buffer, + vertex_data, + BufferTarget::Vertex, + BufferUploadMode::Dynamic); + device.upload_to_buffer(&self.texture_vertex_array.index_buffer, + index_data, + BufferTarget::Index, + BufferUploadMode::Dynamic); + + device.bind_vertex_array(&self.texture_vertex_array.vertex_array); + device.use_program(&self.texture_program.program); + device.set_uniform(&self.texture_program.framebuffer_size_uniform, + UniformData::Vec2(self.framebuffer_size.0.to_f32x4())); + device.set_uniform(&self.texture_program.texture_size_uniform, + UniformData::Vec2(device.texture_size(&texture).0.to_f32x4())); + set_color_uniform(device, &self.texture_program.color_uniform, color); + device.bind_texture(texture, 0); + device.set_uniform(&self.texture_program.texture_uniform, UniformData::TextureUnit(0)); + + device.draw_elements(Primitive::Triangles, index_data.len() as u32, &RenderState { + blend: BlendState::RGBOneAlphaOneMinusSrcAlpha, + ..RenderState::default() + }); + } +} + +struct DebugTextureProgram where D: Device { + program: D::Program, + framebuffer_size_uniform: D::Uniform, + texture_size_uniform: D::Uniform, + texture_uniform: D::Uniform, + color_uniform: D::Uniform, +} + +impl DebugTextureProgram where D: Device { + fn new(device: &D, resources: &Resources) -> DebugTextureProgram { + let program = device.create_program(resources, "debug_texture"); + let framebuffer_size_uniform = device.get_uniform(&program, "FramebufferSize"); + let texture_size_uniform = device.get_uniform(&program, "TextureSize"); + let texture_uniform = device.get_uniform(&program, "Texture"); + let color_uniform = device.get_uniform(&program, "Color"); + DebugTextureProgram { + program, + framebuffer_size_uniform, + texture_size_uniform, + texture_uniform, + color_uniform, + } + } +} + +struct DebugTextureVertexArray where D: Device { + vertex_array: D::VertexArray, + vertex_buffer: D::Buffer, + index_buffer: D::Buffer, +} + +impl DebugTextureVertexArray where D: Device { + fn new(device: &D, debug_texture_program: &DebugTextureProgram) + -> DebugTextureVertexArray { + let (vertex_buffer, index_buffer) = (device.create_buffer(), device.create_buffer()); + let vertex_array = device.create_vertex_array(); + + let position_attr = device.get_vertex_attr(&debug_texture_program.program, "Position"); + let tex_coord_attr = device.get_vertex_attr(&debug_texture_program.program, "TexCoord"); + + device.bind_vertex_array(&vertex_array); + device.use_program(&debug_texture_program.program); + device.bind_buffer(&vertex_buffer, BufferTarget::Vertex); + device.bind_buffer(&index_buffer, BufferTarget::Index); + device.configure_float_vertex_attr(&position_attr, + 2, + VertexAttrType::U16, + false, + DEBUG_TEXTURE_VERTEX_SIZE, + 0, + 0); + device.configure_float_vertex_attr(&tex_coord_attr, + 2, + VertexAttrType::U16, + false, + DEBUG_TEXTURE_VERTEX_SIZE, + 4, + 0); + + DebugTextureVertexArray { vertex_array, vertex_buffer, index_buffer } + } +} + +struct DebugSolidVertexArray where D: Device { + vertex_array: D::VertexArray, + vertex_buffer: D::Buffer, + index_buffer: D::Buffer, +} + +impl DebugSolidVertexArray where D: Device { + fn new(device: &D, debug_solid_program: &DebugSolidProgram) -> DebugSolidVertexArray { + let (vertex_buffer, index_buffer) = (device.create_buffer(), device.create_buffer()); + let vertex_array = device.create_vertex_array(); + + let position_attr = device.get_vertex_attr(&debug_solid_program.program, "Position"); + device.bind_vertex_array(&vertex_array); + device.use_program(&debug_solid_program.program); + device.bind_buffer(&vertex_buffer, BufferTarget::Vertex); + device.bind_buffer(&index_buffer, BufferTarget::Index); + device.configure_float_vertex_attr(&position_attr, + 2, + VertexAttrType::U16, + false, + DEBUG_SOLID_VERTEX_SIZE, + 0, + 0); + + DebugSolidVertexArray { vertex_array, vertex_buffer, index_buffer } + } +} + +struct DebugSolidProgram where D: Device { + program: D::Program, + framebuffer_size_uniform: D::Uniform, + color_uniform: D::Uniform, +} + +impl DebugSolidProgram where D: Device { + fn new(device: &D, resources: &Resources) -> DebugSolidProgram { + let program = device.create_program(resources, "debug_solid"); + let framebuffer_size_uniform = device.get_uniform(&program, "FramebufferSize"); + let color_uniform = device.get_uniform(&program, "Color"); + DebugSolidProgram { program, framebuffer_size_uniform, color_uniform } + } +} + +#[derive(Clone, Copy, Debug)] +#[allow(dead_code)] +#[repr(C)] +struct DebugTextureVertex { + position_x: i16, + position_y: i16, + tex_coord_x: u16, + tex_coord_y: u16, +} + +impl DebugTextureVertex { + fn new(position: Point2DI32, tex_coord: Point2DI32) -> DebugTextureVertex { + DebugTextureVertex { + position_x: position.x() as i16, + position_y: position.y() as i16, + tex_coord_x: tex_coord.x() as u16, + tex_coord_y: tex_coord.y() as u16, + } + } +} + +#[derive(Clone, Copy)] +#[allow(dead_code)] +#[repr(C)] +struct DebugSolidVertex { + position_x: i16, + position_y: i16, +} + +impl DebugSolidVertex { + fn new(position: Point2DI32) -> DebugSolidVertex { + DebugSolidVertex { position_x: position.x() as i16, position_y: position.y() as i16 } + } +} + +struct CornerRects { + upper_left: RectI32, + upper_right: RectI32, + lower_left: RectI32, + lower_right: RectI32, +} + +impl CornerRects { + fn new(device: &D, rect: RectI32, texture: &D::Texture) -> CornerRects where D: Device { + let size = device.texture_size(texture); + CornerRects { + upper_left: RectI32::new(rect.origin(), size), + upper_right: RectI32::new(rect.upper_right() - Point2DI32::new(size.x(), 0), size), + lower_left: RectI32::new(rect.lower_left() - Point2DI32::new(0, size.y()), size), + lower_right: RectI32::new(rect.lower_right() - size, size), + } + } +} + +fn set_color_uniform(device: &D, uniform: &D::Uniform, color: ColorU) where D: Device { + let color = F32x4::new(color.r as f32, color.g as f32, color.b as f32, color.a as f32); + device.set_uniform(uniform, UniformData::Vec4(color * F32x4::splat(1.0 / 255.0))); +}