diff options
Diffstat (limited to 'wgpu/src')
-rw-r--r-- | wgpu/src/lib.rs | 12 | ||||
-rw-r--r-- | wgpu/src/mouse_cursor.rs | 35 | ||||
-rw-r--r-- | wgpu/src/primitive.rs | 22 | ||||
-rw-r--r-- | wgpu/src/quad.rs | 275 | ||||
-rw-r--r-- | wgpu/src/renderer.rs | 254 | ||||
-rw-r--r-- | wgpu/src/renderer/button.rs | 49 | ||||
-rw-r--r-- | wgpu/src/renderer/checkbox.rs | 18 | ||||
-rw-r--r-- | wgpu/src/renderer/column.rs | 22 | ||||
-rw-r--r-- | wgpu/src/renderer/image.rs | 16 | ||||
-rw-r--r-- | wgpu/src/renderer/radio.rs | 17 | ||||
-rw-r--r-- | wgpu/src/renderer/row.rs | 22 | ||||
-rw-r--r-- | wgpu/src/renderer/slider.rs | 17 | ||||
-rw-r--r-- | wgpu/src/renderer/text.rs | 80 | ||||
-rw-r--r-- | wgpu/src/shader/quad.frag | 37 | ||||
-rw-r--r-- | wgpu/src/shader/quad.frag.spv | bin | 0 -> 3196 bytes | |||
-rw-r--r-- | wgpu/src/shader/quad.vert | 32 | ||||
-rw-r--r-- | wgpu/src/shader/quad.vert.spv | bin | 0 -> 2544 bytes | |||
-rw-r--r-- | wgpu/src/transformation.rs | 30 |
18 files changed, 938 insertions, 0 deletions
diff --git a/wgpu/src/lib.rs b/wgpu/src/lib.rs new file mode 100644 index 00000000..8f8d50e9 --- /dev/null +++ b/wgpu/src/lib.rs @@ -0,0 +1,12 @@ +mod mouse_cursor; +mod primitive; +mod quad; +mod renderer; +mod transformation; + +pub(crate) use quad::Quad; +pub(crate) use transformation::Transformation; + +pub use mouse_cursor::MouseCursor; +pub use primitive::Primitive; +pub use renderer::{Renderer, Target}; diff --git a/wgpu/src/mouse_cursor.rs b/wgpu/src/mouse_cursor.rs new file mode 100644 index 00000000..4ef6361a --- /dev/null +++ b/wgpu/src/mouse_cursor.rs @@ -0,0 +1,35 @@ +/// The state of the mouse cursor. +#[derive(Debug, Eq, PartialEq, Clone, Copy)] +pub enum MouseCursor { + /// The cursor is out of the bounds of the user interface. + OutOfBounds, + + /// The cursor is over a non-interactive widget. + Idle, + + /// The cursor is over a clickable widget. + Pointer, + + /// The cursor is over a busy widget. + Working, + + /// The cursor is over a grabbable widget. + Grab, + + /// The cursor is grabbing a widget. + Grabbing, +} + +#[cfg(feature = "winit")] +impl From<MouseCursor> for winit::window::CursorIcon { + fn from(mouse_cursor: MouseCursor) -> winit::window::CursorIcon { + match mouse_cursor { + MouseCursor::OutOfBounds => winit::window::CursorIcon::Default, + MouseCursor::Idle => winit::window::CursorIcon::Default, + MouseCursor::Pointer => winit::window::CursorIcon::Hand, + MouseCursor::Working => winit::window::CursorIcon::Progress, + MouseCursor::Grab => winit::window::CursorIcon::Grab, + MouseCursor::Grabbing => winit::window::CursorIcon::Grabbing, + } + } +} diff --git a/wgpu/src/primitive.rs b/wgpu/src/primitive.rs new file mode 100644 index 00000000..b664689b --- /dev/null +++ b/wgpu/src/primitive.rs @@ -0,0 +1,22 @@ +use iced_native::{text, Background, Color, Rectangle}; + +#[derive(Debug, Clone)] +pub enum Primitive { + None, + Group { + primitives: Vec<Primitive>, + }, + Text { + content: String, + bounds: Rectangle, + color: Color, + size: f32, + horizontal_alignment: text::HorizontalAlignment, + vertical_alignment: text::VerticalAlignment, + }, + Quad { + bounds: Rectangle, + background: Background, + border_radius: u16, + }, +} diff --git a/wgpu/src/quad.rs b/wgpu/src/quad.rs new file mode 100644 index 00000000..adb294f0 --- /dev/null +++ b/wgpu/src/quad.rs @@ -0,0 +1,275 @@ +use crate::Transformation; + +use std::mem; + +pub struct Pipeline { + pipeline: wgpu::RenderPipeline, + constants: wgpu::BindGroup, + transform: wgpu::Buffer, + vertices: wgpu::Buffer, + indices: wgpu::Buffer, + instances: wgpu::Buffer, +} + +impl Pipeline { + pub fn new(device: &mut wgpu::Device) -> Pipeline { + let constant_layout = + device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + bindings: &[wgpu::BindGroupLayoutBinding { + binding: 0, + visibility: wgpu::ShaderStage::VERTEX, + ty: wgpu::BindingType::UniformBuffer { dynamic: false }, + }], + }); + + let matrix: [f32; 16] = Transformation::identity().into(); + + let transform = device + .create_buffer_mapped( + 16, + wgpu::BufferUsage::UNIFORM | wgpu::BufferUsage::COPY_DST, + ) + .fill_from_slice(&matrix[..]); + + let constants = device.create_bind_group(&wgpu::BindGroupDescriptor { + layout: &constant_layout, + bindings: &[wgpu::Binding { + binding: 0, + resource: wgpu::BindingResource::Buffer { + buffer: &transform, + range: 0..64, + }, + }], + }); + + let layout = + device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { + bind_group_layouts: &[&constant_layout], + }); + + let vs = include_bytes!("shader/quad.vert.spv"); + let vs_module = device.create_shader_module( + &wgpu::read_spirv(std::io::Cursor::new(&vs[..])) + .expect("Read quad vertex shader as SPIR-V"), + ); + + let fs = include_bytes!("shader/quad.frag.spv"); + let fs_module = device.create_shader_module( + &wgpu::read_spirv(std::io::Cursor::new(&fs[..])) + .expect("Read quad fragment shader as SPIR-V"), + ); + + let pipeline = + device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { + layout: &layout, + vertex_stage: wgpu::ProgrammableStageDescriptor { + module: &vs_module, + entry_point: "main", + }, + fragment_stage: Some(wgpu::ProgrammableStageDescriptor { + module: &fs_module, + entry_point: "main", + }), + rasterization_state: Some(wgpu::RasterizationStateDescriptor { + front_face: wgpu::FrontFace::Cw, + cull_mode: wgpu::CullMode::None, + depth_bias: 0, + depth_bias_slope_scale: 0.0, + depth_bias_clamp: 0.0, + }), + primitive_topology: wgpu::PrimitiveTopology::TriangleList, + color_states: &[wgpu::ColorStateDescriptor { + format: wgpu::TextureFormat::Bgra8UnormSrgb, + color_blend: wgpu::BlendDescriptor { + src_factor: wgpu::BlendFactor::SrcAlpha, + dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha, + operation: wgpu::BlendOperation::Add, + }, + alpha_blend: wgpu::BlendDescriptor { + src_factor: wgpu::BlendFactor::One, + dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha, + operation: wgpu::BlendOperation::Add, + }, + write_mask: wgpu::ColorWrite::ALL, + }], + depth_stencil_state: None, + index_format: wgpu::IndexFormat::Uint16, + vertex_buffers: &[ + wgpu::VertexBufferDescriptor { + stride: mem::size_of::<Vertex>() as u64, + step_mode: wgpu::InputStepMode::Vertex, + attributes: &[wgpu::VertexAttributeDescriptor { + shader_location: 0, + format: wgpu::VertexFormat::Float2, + offset: 0, + }], + }, + wgpu::VertexBufferDescriptor { + stride: mem::size_of::<Quad>() as u64, + step_mode: wgpu::InputStepMode::Instance, + attributes: &[ + wgpu::VertexAttributeDescriptor { + shader_location: 1, + format: wgpu::VertexFormat::Float2, + offset: 0, + }, + wgpu::VertexAttributeDescriptor { + shader_location: 2, + format: wgpu::VertexFormat::Float2, + offset: 4 * 2, + }, + wgpu::VertexAttributeDescriptor { + shader_location: 3, + format: wgpu::VertexFormat::Float4, + offset: 4 * (2 + 2), + }, + wgpu::VertexAttributeDescriptor { + shader_location: 4, + format: wgpu::VertexFormat::Uint, + offset: 4 * (2 + 2 + 4), + }, + ], + }, + ], + sample_count: 1, + sample_mask: !0, + alpha_to_coverage_enabled: false, + }); + + let vertices = device + .create_buffer_mapped(QUAD_VERTS.len(), wgpu::BufferUsage::VERTEX) + .fill_from_slice(&QUAD_VERTS); + + let indices = device + .create_buffer_mapped(QUAD_INDICES.len(), wgpu::BufferUsage::INDEX) + .fill_from_slice(&QUAD_INDICES); + + let instances = device.create_buffer(&wgpu::BufferDescriptor { + size: mem::size_of::<Quad>() as u64 * Quad::MAX as u64, + usage: wgpu::BufferUsage::VERTEX | wgpu::BufferUsage::COPY_DST, + }); + + Pipeline { + pipeline, + constants, + transform, + vertices, + indices, + instances, + } + } + + pub fn draw( + &mut self, + device: &mut wgpu::Device, + encoder: &mut wgpu::CommandEncoder, + instances: &[Quad], + transformation: Transformation, + target: &wgpu::TextureView, + ) { + let matrix: [f32; 16] = transformation.into(); + + let transform_buffer = device + .create_buffer_mapped(16, wgpu::BufferUsage::COPY_SRC) + .fill_from_slice(&matrix[..]); + + encoder.copy_buffer_to_buffer( + &transform_buffer, + 0, + &self.transform, + 0, + 16 * 4, + ); + + let mut i = 0; + let total = instances.len(); + + while i < total { + let end = (i + Quad::MAX).min(total); + let amount = end - i; + + let instance_buffer = device + .create_buffer_mapped(amount, wgpu::BufferUsage::COPY_SRC) + .fill_from_slice(&instances[i..end]); + + encoder.copy_buffer_to_buffer( + &instance_buffer, + 0, + &self.instances, + 0, + (mem::size_of::<Quad>() * amount) as u64, + ); + + { + let mut render_pass = + encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + color_attachments: &[ + wgpu::RenderPassColorAttachmentDescriptor { + attachment: target, + resolve_target: None, + load_op: wgpu::LoadOp::Load, + store_op: wgpu::StoreOp::Store, + clear_color: wgpu::Color { + r: 0.0, + g: 0.0, + b: 0.0, + a: 0.0, + }, + }, + ], + depth_stencil_attachment: None, + }); + + render_pass.set_pipeline(&self.pipeline); + render_pass.set_bind_group(0, &self.constants, &[]); + render_pass.set_index_buffer(&self.indices, 0); + render_pass.set_vertex_buffers( + 0, + &[(&self.vertices, 0), (&self.instances, 0)], + ); + + render_pass.draw_indexed( + 0..QUAD_INDICES.len() as u32, + 0, + 0..amount as u32, + ); + } + + i += Quad::MAX; + } + } +} + +#[derive(Clone, Copy)] +pub struct Vertex { + _position: [f32; 2], +} + +const QUAD_INDICES: [u16; 6] = [0, 1, 2, 0, 2, 3]; + +const QUAD_VERTS: [Vertex; 4] = [ + Vertex { + _position: [0.0, 0.0], + }, + Vertex { + _position: [1.0, 0.0], + }, + Vertex { + _position: [1.0, 1.0], + }, + Vertex { + _position: [0.0, 1.0], + }, +]; + +#[derive(Debug, Clone, Copy)] +pub struct Quad { + pub position: [f32; 2], + pub scale: [f32; 2], + pub color: [f32; 4], + pub border_radius: u32, +} + +impl Quad { + const MAX: usize = 100_000; +} diff --git a/wgpu/src/renderer.rs b/wgpu/src/renderer.rs new file mode 100644 index 00000000..ae5692e3 --- /dev/null +++ b/wgpu/src/renderer.rs @@ -0,0 +1,254 @@ +use crate::{quad, Primitive, Quad, Transformation}; +use iced_native::{ + renderer::Debugger, Background, Color, Layout, Point, Widget, +}; + +use raw_window_handle::HasRawWindowHandle; +use wgpu::{ + Adapter, BackendBit, CommandEncoderDescriptor, Device, DeviceDescriptor, + Extensions, Limits, PowerPreference, Queue, RequestAdapterOptions, Surface, + SwapChain, SwapChainDescriptor, TextureFormat, TextureUsage, +}; +use wgpu_glyph::{GlyphBrush, GlyphBrushBuilder, Section}; + +use std::{cell::RefCell, rc::Rc}; + +mod button; +mod checkbox; +mod column; +mod image; +mod radio; +mod row; +mod slider; +mod text; + +pub struct Renderer { + surface: Surface, + adapter: Adapter, + device: Device, + queue: Queue, + quad_pipeline: quad::Pipeline, + + quads: Vec<Quad>, + glyph_brush: Rc<RefCell<GlyphBrush<'static, ()>>>, +} + +pub struct Target { + width: u16, + height: u16, + transformation: Transformation, + swap_chain: SwapChain, +} + +impl Renderer { + pub fn new<W: HasRawWindowHandle>(window: &W) -> Self { + let adapter = Adapter::request(&RequestAdapterOptions { + power_preference: PowerPreference::LowPower, + backends: BackendBit::all(), + }) + .expect("Request adapter"); + + let (mut device, queue) = adapter.request_device(&DeviceDescriptor { + extensions: Extensions { + anisotropic_filtering: false, + }, + limits: Limits { max_bind_groups: 1 }, + }); + + let surface = Surface::create(window); + + // TODO: Think about font loading strategy + // Loading system fonts with fallback may be a good idea + let font: &[u8] = + include_bytes!("../../examples/resources/Roboto-Regular.ttf"); + + let glyph_brush = GlyphBrushBuilder::using_font_bytes(font) + .build(&mut device, TextureFormat::Bgra8UnormSrgb); + + let quad_pipeline = quad::Pipeline::new(&mut device); + + Self { + surface, + adapter, + device, + queue, + quad_pipeline, + + quads: Vec::new(), + glyph_brush: Rc::new(RefCell::new(glyph_brush)), + } + } + + pub fn target(&self, width: u16, height: u16) -> Target { + Target { + width, + height, + transformation: Transformation::orthographic(width, height), + swap_chain: self.device.create_swap_chain( + &self.surface, + &SwapChainDescriptor { + usage: TextureUsage::OUTPUT_ATTACHMENT, + format: TextureFormat::Bgra8UnormSrgb, + width: u32::from(width), + height: u32::from(height), + present_mode: wgpu::PresentMode::Vsync, + }, + ), + } + } + + pub fn draw(&mut self, target: &mut Target, primitive: &Primitive) { + log::debug!("Drawing"); + + let frame = target.swap_chain.get_next_texture(); + + let mut encoder = self + .device + .create_command_encoder(&CommandEncoderDescriptor { todo: 0 }); + + let _ = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + color_attachments: &[wgpu::RenderPassColorAttachmentDescriptor { + attachment: &frame.view, + resolve_target: None, + load_op: wgpu::LoadOp::Clear, + store_op: wgpu::StoreOp::Store, + clear_color: wgpu::Color { + r: 1.0, + g: 1.0, + b: 1.0, + a: 1.0, + }, + }], + depth_stencil_attachment: None, + }); + + self.draw_primitive(primitive); + + self.quad_pipeline.draw( + &mut self.device, + &mut encoder, + &self.quads, + target.transformation, + &frame.view, + ); + + self.quads.clear(); + + self.glyph_brush + .borrow_mut() + .draw_queued( + &mut self.device, + &mut encoder, + &frame.view, + u32::from(target.width), + u32::from(target.height), + ) + .expect("Draw text"); + + self.queue.submit(&[encoder.finish()]); + } + + fn draw_primitive(&mut self, primitive: &Primitive) { + match primitive { + Primitive::None => {} + Primitive::Group { primitives } => { + // TODO: Inspect a bit and regroup (?) + for primitive in primitives { + self.draw_primitive(primitive) + } + } + Primitive::Text { + content, + bounds, + size, + color, + horizontal_alignment, + vertical_alignment, + } => { + let x = match horizontal_alignment { + iced_native::text::HorizontalAlignment::Left => bounds.x, + iced_native::text::HorizontalAlignment::Center => { + bounds.x + bounds.width / 2.0 + } + iced_native::text::HorizontalAlignment::Right => { + bounds.x + bounds.width + } + }; + + let y = match vertical_alignment { + iced_native::text::VerticalAlignment::Top => bounds.y, + iced_native::text::VerticalAlignment::Center => { + bounds.y + bounds.height / 2.0 + } + iced_native::text::VerticalAlignment::Bottom => { + bounds.y + bounds.height + } + }; + + self.glyph_brush.borrow_mut().queue(Section { + text: &content, + screen_position: (x, y), + bounds: (bounds.width, bounds.height), + scale: wgpu_glyph::Scale { x: *size, y: *size }, + color: color.into_linear(), + layout: wgpu_glyph::Layout::default() + .h_align(match horizontal_alignment { + iced_native::text::HorizontalAlignment::Left => { + wgpu_glyph::HorizontalAlign::Left + } + iced_native::text::HorizontalAlignment::Center => { + wgpu_glyph::HorizontalAlign::Center + } + iced_native::text::HorizontalAlignment::Right => { + wgpu_glyph::HorizontalAlign::Right + } + }) + .v_align(match vertical_alignment { + iced_native::text::VerticalAlignment::Top => { + wgpu_glyph::VerticalAlign::Top + } + iced_native::text::VerticalAlignment::Center => { + wgpu_glyph::VerticalAlign::Center + } + iced_native::text::VerticalAlignment::Bottom => { + wgpu_glyph::VerticalAlign::Bottom + } + }), + ..Default::default() + }) + } + Primitive::Quad { + bounds, + background, + border_radius, + } => { + self.quads.push(Quad { + position: [bounds.x, bounds.y], + scale: [bounds.width, bounds.height], + color: match background { + Background::Color(color) => color.into_linear(), + }, + border_radius: u32::from(*border_radius), + }); + } + } + } +} + +impl iced_native::Renderer for Renderer { + // TODO: Add `MouseCursor` here (?) + type Primitive = Primitive; +} + +impl Debugger for Renderer { + fn explain<Message>( + &mut self, + widget: &dyn Widget<Message, Self>, + layout: Layout<'_>, + cursor_position: Point, + _color: Color, + ) -> Self::Primitive { + // TODO: Include a bordered box to display layout bounds + widget.draw(self, layout, cursor_position) + } +} diff --git a/wgpu/src/renderer/button.rs b/wgpu/src/renderer/button.rs new file mode 100644 index 00000000..00fcd0eb --- /dev/null +++ b/wgpu/src/renderer/button.rs @@ -0,0 +1,49 @@ +use crate::{Primitive, Renderer}; +use iced_native::{ + button, Align, Background, Button, Color, Layout, Length, Node, Point, + Style, +}; + +impl button::Renderer for Renderer { + fn node<Message>(&self, button: &Button<Message, Self>) -> Node { + let style = Style::default() + .width(button.width) + .padding(button.padding) + .min_width(Length::Units(100)) + .align_self(button.align_self) + .align_items(Align::Stretch); + + Node::with_children(style, vec![button.content.node(self)]) + } + + fn draw<Message>( + &mut self, + button: &Button<Message, Self>, + layout: Layout<'_>, + cursor_position: Point, + ) -> Self::Primitive { + let bounds = layout.bounds(); + + Primitive::Group { + primitives: vec![ + Primitive::Quad { + bounds, + background: button.background.unwrap_or(Background::Color( + Color { + r: 0.8, + b: 0.8, + g: 0.8, + a: 1.0, + }, + )), + border_radius: button.border_radius, + }, + button.content.draw( + self, + layout.children().next().unwrap(), + cursor_position, + ), + ], + } + } +} diff --git a/wgpu/src/renderer/checkbox.rs b/wgpu/src/renderer/checkbox.rs new file mode 100644 index 00000000..16d5734f --- /dev/null +++ b/wgpu/src/renderer/checkbox.rs @@ -0,0 +1,18 @@ +use crate::{Primitive, Renderer}; +use iced_native::{checkbox, Checkbox, Layout, Node, Point, Style}; + +impl checkbox::Renderer for Renderer { + fn node<Message>(&self, _checkbox: &Checkbox<Message>) -> Node { + Node::new(Style::default()) + } + + fn draw<Message>( + &mut self, + _checkbox: &Checkbox<Message>, + _layout: Layout<'_>, + _cursor_position: Point, + ) -> Self::Primitive { + // TODO + Primitive::None + } +} diff --git a/wgpu/src/renderer/column.rs b/wgpu/src/renderer/column.rs new file mode 100644 index 00000000..1b9adad6 --- /dev/null +++ b/wgpu/src/renderer/column.rs @@ -0,0 +1,22 @@ +use crate::{Primitive, Renderer}; +use iced_native::{column, Column, Layout, Point}; + +impl column::Renderer for Renderer { + fn draw<Message>( + &mut self, + column: &Column<'_, Message, Self>, + layout: Layout<'_>, + cursor_position: Point, + ) -> Self::Primitive { + Primitive::Group { + primitives: column + .children + .iter() + .zip(layout.children()) + .map(|(child, layout)| { + child.draw(self, layout, cursor_position) + }) + .collect(), + } + } +} diff --git a/wgpu/src/renderer/image.rs b/wgpu/src/renderer/image.rs new file mode 100644 index 00000000..bacc430d --- /dev/null +++ b/wgpu/src/renderer/image.rs @@ -0,0 +1,16 @@ +use crate::{Primitive, Renderer}; +use iced_native::{image, Image, Layout, Node, Style}; + +impl image::Renderer<&str> for Renderer { + fn node(&self, _image: &Image<&str>) -> Node { + Node::new(Style::default()) + } + + fn draw( + &mut self, + _image: &Image<&str>, + _layout: Layout<'_>, + ) -> Self::Primitive { + Primitive::None + } +} diff --git a/wgpu/src/renderer/radio.rs b/wgpu/src/renderer/radio.rs new file mode 100644 index 00000000..fdc0a0fc --- /dev/null +++ b/wgpu/src/renderer/radio.rs @@ -0,0 +1,17 @@ +use crate::{Primitive, Renderer}; +use iced_native::{radio, Layout, Node, Point, Radio, Style}; + +impl radio::Renderer for Renderer { + fn node<Message>(&self, _checkbox: &Radio<Message>) -> Node { + Node::new(Style::default()) + } + + fn draw<Message>( + &mut self, + _radio: &Radio<Message>, + _layout: Layout<'_>, + _cursor_position: Point, + ) -> Self::Primitive { + Primitive::None + } +} diff --git a/wgpu/src/renderer/row.rs b/wgpu/src/renderer/row.rs new file mode 100644 index 00000000..be9e4ede --- /dev/null +++ b/wgpu/src/renderer/row.rs @@ -0,0 +1,22 @@ +use crate::{Primitive, Renderer}; +use iced_native::{row, Layout, Point, Row}; + +impl row::Renderer for Renderer { + fn draw<Message>( + &mut self, + row: &Row<'_, Message, Self>, + layout: Layout<'_>, + cursor_position: Point, + ) -> Self::Primitive { + Primitive::Group { + primitives: row + .children + .iter() + .zip(layout.children()) + .map(|(child, layout)| { + child.draw(self, layout, cursor_position) + }) + .collect(), + } + } +} diff --git a/wgpu/src/renderer/slider.rs b/wgpu/src/renderer/slider.rs new file mode 100644 index 00000000..2e76022d --- /dev/null +++ b/wgpu/src/renderer/slider.rs @@ -0,0 +1,17 @@ +use crate::{Primitive, Renderer}; +use iced_native::{slider, Layout, Node, Point, Slider, Style}; + +impl slider::Renderer for Renderer { + fn node<Message>(&self, _slider: &Slider<Message>) -> Node { + Node::new(Style::default()) + } + + fn draw<Message>( + &mut self, + _slider: &Slider<Message>, + _layout: Layout<'_>, + _cursor_position: Point, + ) -> Self::Primitive { + Primitive::None + } +} diff --git a/wgpu/src/renderer/text.rs b/wgpu/src/renderer/text.rs new file mode 100644 index 00000000..c89c0b3e --- /dev/null +++ b/wgpu/src/renderer/text.rs @@ -0,0 +1,80 @@ +use crate::{Primitive, Renderer}; +use iced_native::{text, Color, Layout, Node, Style, Text}; + +use wgpu_glyph::{GlyphCruncher, Section}; + +use std::cell::RefCell; +use std::f32; + +impl text::Renderer for Renderer { + fn node(&self, text: &Text) -> Node { + let glyph_brush = self.glyph_brush.clone(); + let content = text.content.clone(); + + // TODO: Investigate why stretch tries to measure this MANY times + // with every ancestor's bounds. + // Bug? Using the library wrong? I should probably open an issue on + // the stretch repository. + // I noticed that the first measure is the one that matters in + // practice. Here, we use a RefCell to store the cached measurement. + let measure = RefCell::new(None); + let size = text.size.map(f32::from).unwrap_or(20.0); + + let style = Style::default().width(text.width); + + iced_native::Node::with_measure(style, move |bounds| { + let mut measure = measure.borrow_mut(); + + if measure.is_none() { + let bounds = ( + match bounds.width { + iced_native::Number::Undefined => f32::INFINITY, + iced_native::Number::Defined(w) => w, + }, + match bounds.height { + iced_native::Number::Undefined => f32::INFINITY, + iced_native::Number::Defined(h) => h, + }, + ); + + let text = Section { + text: &content, + scale: wgpu_glyph::Scale { x: size, y: size }, + bounds, + ..Default::default() + }; + + let (width, height) = if let Some(bounds) = + glyph_brush.borrow_mut().glyph_bounds(&text) + { + (bounds.width(), bounds.height()) + } else { + (0.0, 0.0) + }; + + let size = iced_native::Size { width, height }; + + // If the text has no width boundary we avoid caching as the + // layout engine may just be measuring text in a row. + if bounds.0 == f32::INFINITY { + return size; + } else { + *measure = Some(size); + } + } + + measure.unwrap() + }) + } + + fn draw(&mut self, text: &Text, layout: Layout<'_>) -> Self::Primitive { + Primitive::Text { + content: text.content.clone(), + size: f32::from(text.size.unwrap_or(20)), + bounds: layout.bounds(), + color: text.color.unwrap_or(Color::BLACK), + horizontal_alignment: text.horizontal_alignment, + vertical_alignment: text.vertical_alignment, + } + } +} diff --git a/wgpu/src/shader/quad.frag b/wgpu/src/shader/quad.frag new file mode 100644 index 00000000..987744db --- /dev/null +++ b/wgpu/src/shader/quad.frag @@ -0,0 +1,37 @@ +#version 450 + +layout(location = 0) in vec4 v_Color; +layout(location = 1) in vec2 v_Pos; +layout(location = 2) in vec2 v_Scale; +layout(location = 3) in flat uint v_BorderRadius; + +layout(location = 0) out vec4 o_Color; + +float rounded(in vec2 frag_coord, in vec2 position, in vec2 size, float radius, float s) +{ + vec2 inner_size = size - vec2(radius, radius) * 2.0; + vec2 top_left = position + vec2(radius, radius); + vec2 bottom_right = top_left + inner_size; + + vec2 top_left_distance = top_left - frag_coord; + vec2 bottom_right_distance = frag_coord - bottom_right; + + vec2 distance = vec2( + max(max(top_left_distance.x, bottom_right_distance.x), 0), + max(max(top_left_distance.y, bottom_right_distance.y), 0) + ); + + float d = sqrt(distance.x * distance.x + distance.y * distance.y); + + return 1.0 - smoothstep(radius - s, radius + s, d); +} + +void main() { + float radius_alpha = 1.0; + + if(v_BorderRadius > 0.0) { + radius_alpha = rounded(gl_FragCoord.xy, v_Pos, v_Scale, v_BorderRadius, 1.0); + } + + o_Color = vec4(v_Color.xyz, v_Color.w * radius_alpha); +} diff --git a/wgpu/src/shader/quad.frag.spv b/wgpu/src/shader/quad.frag.spv Binary files differnew file mode 100644 index 00000000..063287b3 --- /dev/null +++ b/wgpu/src/shader/quad.frag.spv diff --git a/wgpu/src/shader/quad.vert b/wgpu/src/shader/quad.vert new file mode 100644 index 00000000..b7c5cf3e --- /dev/null +++ b/wgpu/src/shader/quad.vert @@ -0,0 +1,32 @@ +#version 450 + +layout(location = 0) in vec2 v_Pos; +layout(location = 1) in vec2 i_Pos; +layout(location = 2) in vec2 i_Scale; +layout(location = 3) in vec4 i_Color; +layout(location = 4) in uint i_BorderRadius; + +layout (set = 0, binding = 0) uniform Globals { + mat4 u_Transform; +}; + +layout(location = 0) out vec4 o_Color; +layout(location = 1) out vec2 o_Pos; +layout(location = 2) out vec2 o_Scale; +layout(location = 3) out uint o_BorderRadius; + +void main() { + mat4 i_Transform = mat4( + vec4(i_Scale.x, 0.0, 0.0, 0.0), + vec4(0.0, i_Scale.y, 0.0, 0.0), + vec4(0.0, 0.0, 1.0, 0.0), + vec4(i_Pos, 0.0, 1.0) + ); + + o_Color = i_Color; + o_Pos = i_Pos; + o_Scale = i_Scale; + o_BorderRadius = i_BorderRadius; + + gl_Position = u_Transform * i_Transform * vec4(v_Pos, 0.0, 1.0); +} diff --git a/wgpu/src/shader/quad.vert.spv b/wgpu/src/shader/quad.vert.spv Binary files differnew file mode 100644 index 00000000..f62a160c --- /dev/null +++ b/wgpu/src/shader/quad.vert.spv diff --git a/wgpu/src/transformation.rs b/wgpu/src/transformation.rs new file mode 100644 index 00000000..1101e135 --- /dev/null +++ b/wgpu/src/transformation.rs @@ -0,0 +1,30 @@ +#[derive(Debug, Clone, Copy)] +pub struct Transformation([f32; 16]); + +impl Transformation { + #[rustfmt::skip] + pub fn identity() -> Self { + Transformation([ + 1.0, 0.0, 0.0, 0.0, + 0.0, 1.0, 0.0, 0.0, + 0.0, 0.0, 1.0, 0.0, + 0.0, 0.0, 0.0, 1.0, + ]) + } + + #[rustfmt::skip] + pub fn orthographic(width: u16, height: u16) -> Self { + Transformation([ + 2.0 / width as f32, 0.0, 0.0, 0.0, + 0.0, 2.0 / height as f32, 0.0, 0.0, + 0.0, 0.0, 1.0, 0.0, + -1.0, -1.0, 0.0, 1.0, + ]) + } +} + +impl From<Transformation> for [f32; 16] { + fn from(transformation: Transformation) -> [f32; 16] { + transformation.0 + } +} |