From e2ee8e41540bcec05246edd99fdeccdee9c3d90b Mon Sep 17 00:00:00 2001 From: bungoboingo Date: Mon, 19 Dec 2022 22:11:18 -0800 Subject: [PATCH] Support for gradients as backgrounds & other optimizations. --- Cargo.toml | 5 + core/Cargo.toml | 3 + core/src/angle.rs | 15 + core/src/background.rs | 14 +- core/src/gradient.rs | 212 +++++ core/src/lib.rs | 4 + core/src/rectangle.rs | 4 +- examples/benches/gradient/Cargo.toml | 11 + examples/benches/gradient/src/main.rs | 150 ++++ examples/benches/gradient_triangle/Cargo.toml | 11 + .../benches/gradient_triangle/src/main.rs | 180 +++++ .../{modern_art => benches/solids}/Cargo.toml | 6 +- examples/benches/solids/src/main.rs | 98 +++ examples/benches/solids_triangle/Cargo.toml | 11 + examples/benches/solids_triangle/src/main.rs | 112 +++ examples/modern_art/src/main.rs | 142 ---- examples/solar_system/src/main.rs | 20 +- examples/tour/src/main.rs | 51 +- glow/src/image.rs | 8 +- glow/src/program.rs | 30 +- glow/src/quad.rs | 2 +- glow/src/quad/compatibility.rs | 753 ++++++++++++------ glow/src/quad/core.rs | 629 +++++++++++---- glow/src/shader/common/gradient.frag | 59 -- glow/src/shader/common/gradient.vert | 9 - glow/src/shader/common/solid.vert | 11 - glow/src/shader/compatibility/quad.frag | 83 -- glow/src/shader/compatibility/quad.vert | 46 -- glow/src/shader/core/quad.frag | 95 --- glow/src/shader/core/quad.vert | 52 -- glow/src/shader/includes/border_radius.vert | 9 + glow/src/shader/includes/gradient.frag | 61 ++ glow/src/shader/includes/quad_distance.vert | 17 + .../shader/quad/compatibility/gradient.frag | 64 ++ .../shader/quad/compatibility/gradient.vert | 72 ++ glow/src/shader/quad/compatibility/solid.frag | 56 ++ glow/src/shader/quad/compatibility/solid.vert | 45 ++ glow/src/shader/quad/core/gradient.frag | 69 ++ glow/src/shader/quad/core/gradient.vert | 80 ++ glow/src/shader/quad/core/solid.frag | 67 ++ glow/src/shader/quad/core/solid.vert | 52 ++ glow/src/shader/triangle/gradient.frag | 23 + glow/src/shader/triangle/gradient.vert | 42 + .../shader/{common => triangle}/image.frag | 0 .../shader/{common => triangle}/image.vert | 0 .../shader/{common => triangle}/solid.frag | 4 +- glow/src/shader/triangle/solid.vert | 11 + glow/src/triangle.rs | 307 +++---- glow/src/window/compositor.rs | 4 +- graphics/src/gradient.rs | 224 ++++-- graphics/src/gradient/linear.rs | 112 --- graphics/src/layer.rs | 61 +- graphics/src/layer/mesh.rs | 7 +- graphics/src/layer/quad.rs | 51 +- graphics/src/primitive.rs | 8 +- graphics/src/triangle.rs | 15 +- graphics/src/widget/canvas.rs | 4 +- graphics/src/widget/canvas/frame.rs | 74 +- native/src/lib.rs | 4 +- native/src/renderer.rs | 2 +- src/lib.rs | 5 +- style/src/button.rs | 3 + style/src/theme.rs | 3 + wgpu/src/buffer.rs | 259 +++++- wgpu/src/buffer/dynamic.rs | 219 ----- wgpu/src/buffer/static.rs | 117 --- wgpu/src/quad.rs | 623 ++++++++++----- wgpu/src/shader/gradient.wgsl | 88 -- wgpu/src/shader/quad.wgsl | 292 ++++++- wgpu/src/shader/triangle/gradient.wgsl | 139 ++++ wgpu/src/shader/{ => triangle}/solid.wgsl | 10 +- wgpu/src/triangle.rs | 302 +++---- 72 files changed, 4117 insertions(+), 2314 deletions(-) create mode 100644 core/src/angle.rs create mode 100644 core/src/gradient.rs create mode 100644 examples/benches/gradient/Cargo.toml create mode 100644 examples/benches/gradient/src/main.rs create mode 100644 examples/benches/gradient_triangle/Cargo.toml create mode 100644 examples/benches/gradient_triangle/src/main.rs rename examples/{modern_art => benches/solids}/Cargo.toml (63%) create mode 100644 examples/benches/solids/src/main.rs create mode 100644 examples/benches/solids_triangle/Cargo.toml create mode 100644 examples/benches/solids_triangle/src/main.rs delete mode 100644 examples/modern_art/src/main.rs delete mode 100644 glow/src/shader/common/gradient.frag delete mode 100644 glow/src/shader/common/gradient.vert delete mode 100644 glow/src/shader/common/solid.vert delete mode 100644 glow/src/shader/compatibility/quad.frag delete mode 100644 glow/src/shader/compatibility/quad.vert delete mode 100644 glow/src/shader/core/quad.frag delete mode 100644 glow/src/shader/core/quad.vert create mode 100644 glow/src/shader/includes/border_radius.vert create mode 100644 glow/src/shader/includes/gradient.frag create mode 100644 glow/src/shader/includes/quad_distance.vert create mode 100644 glow/src/shader/quad/compatibility/gradient.frag create mode 100644 glow/src/shader/quad/compatibility/gradient.vert create mode 100644 glow/src/shader/quad/compatibility/solid.frag create mode 100644 glow/src/shader/quad/compatibility/solid.vert create mode 100644 glow/src/shader/quad/core/gradient.frag create mode 100644 glow/src/shader/quad/core/gradient.vert create mode 100644 glow/src/shader/quad/core/solid.frag create mode 100644 glow/src/shader/quad/core/solid.vert create mode 100644 glow/src/shader/triangle/gradient.frag create mode 100644 glow/src/shader/triangle/gradient.vert rename glow/src/shader/{common => triangle}/image.frag (100%) rename glow/src/shader/{common => triangle}/image.vert (100%) rename glow/src/shader/{common => triangle}/solid.frag (82%) create mode 100644 glow/src/shader/triangle/solid.vert delete mode 100644 graphics/src/gradient/linear.rs delete mode 100644 wgpu/src/buffer/dynamic.rs delete mode 100644 wgpu/src/buffer/static.rs delete mode 100644 wgpu/src/shader/gradient.wgsl create mode 100644 wgpu/src/shader/triangle/gradient.wgsl rename wgpu/src/shader/{ => triangle}/solid.wgsl (67%) diff --git a/Cargo.toml b/Cargo.toml index 9c45b2f588..dd3711767d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -56,7 +56,12 @@ members = [ "wgpu", "winit", "examples/*", + "examples/benches/gradient", + "examples/benches/gradient_triangle", + "examples/benches/solids", + "examples/benches/solids_triangle", ] +exclude = ["examples/benches"] [dependencies] iced_core = { version = "0.6", path = "core" } diff --git a/core/Cargo.toml b/core/Cargo.toml index c401f30a38..5419156b25 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -14,5 +14,8 @@ bitflags = "1.2" version = "0.6" optional = true +[dependencies.thiserror] +version = "1.0.37" + [target.'cfg(target_arch = "wasm32")'.dependencies] wasm-timer = { version = "0.2" } diff --git a/core/src/angle.rs b/core/src/angle.rs new file mode 100644 index 0000000000..167cc5ed3d --- /dev/null +++ b/core/src/angle.rs @@ -0,0 +1,15 @@ +use std::f32::consts::PI; + +#[derive(Debug, Copy, Clone, PartialEq)] +/// Degrees +pub struct Degrees(pub f32); + +#[derive(Debug, Copy, Clone, PartialEq)] +/// Radians +pub struct Radians(pub f32); + +impl From for Radians { + fn from(degrees: Degrees) -> Self { + Radians(degrees.0 * (PI / 180.0)) + } +} diff --git a/core/src/background.rs b/core/src/background.rs index cfb958674a..92107d859a 100644 --- a/core/src/background.rs +++ b/core/src/background.rs @@ -1,11 +1,13 @@ -use crate::Color; +use crate::{Color, Gradient}; /// The background of some element. #[derive(Debug, Clone, Copy, PartialEq)] pub enum Background { - /// A solid color + /// A solid color. Color(Color), - // TODO: Add gradient and image variants + /// Interpolate between several colors. + Gradient(Gradient), + // TODO: Add image variant } impl From for Background { @@ -19,3 +21,9 @@ impl From for Option { Some(Background::from(color)) } } + +impl From for Option { + fn from(gradient: Gradient) -> Self { + Some(Background::Gradient(gradient)) + } +} diff --git a/core/src/gradient.rs b/core/src/gradient.rs new file mode 100644 index 0000000000..f299412903 --- /dev/null +++ b/core/src/gradient.rs @@ -0,0 +1,212 @@ +//! For creating a Gradient. +pub use linear::Linear; + +use crate::{Color, Radians, Rectangle, Vector}; + +#[derive(Debug, Clone, Copy, PartialEq)] +/// A fill which transitions colors progressively along a direction, either linearly, radially (TBD), +/// or conically (TBD). +/// +/// For a gradient which can be used as a fill on a canvas, see [`iced_graphics::Gradient`]. +pub enum Gradient { + /// A linear gradient interpolates colors along a direction at a specific [`Angle`]. + Linear(Linear), +} + +impl Gradient { + /// Creates a new linear [`linear::Builder`]. + /// + /// For use in a [`Background`], this can be defined by an angle + /// (in [`Degrees`] or [`Radians`]) which will use the bounds of the widget as a guide. + pub fn linear(angle: impl Into) -> linear::Builder { + linear::Builder::new(angle.into()) + } + + /// Adjust the opacity of the gradient by a multiplier applied to each color stop. + pub fn adjust_alpha(mut self, alpha_multiplier: f32) -> Self { + match &mut self { + Gradient::Linear(linear) => { + for stop in &mut linear.color_stops { + stop.color.a *= alpha_multiplier; + } + } + } + + self + } + + /// Packs the [`Gradient`] into a buffer for use in shader code. + pub fn flatten(&self, bounds: Rectangle) -> [f32; 44] { + match self { + Gradient::Linear(linear) => { + let mut pack: [f32; 44] = [0.0; 44]; + let mut offsets: [f32; 8] = [2.0; 8]; + + for (index, stop) in + linear.color_stops.iter().enumerate().take(8) + { + let [r, g, b, a] = stop.color.into_linear(); + + pack[(index * 4)] = r; + pack[(index * 4) + 1] = g; + pack[(index * 4) + 2] = b; + pack[(index * 4) + 3] = a; + + offsets[index] = stop.offset; + } + + pack[32] = offsets[0]; + pack[33] = offsets[1]; + pack[34] = offsets[2]; + pack[35] = offsets[3]; + pack[36] = offsets[4]; + pack[37] = offsets[5]; + pack[38] = offsets[6]; + pack[39] = offsets[7]; + + let v1 = Vector::new( + f32::cos(linear.angle.0), + f32::sin(linear.angle.0), + ); + + let distance_to_rect = f32::min( + f32::abs((bounds.y - bounds.center().y) / v1.y), + f32::abs( + ((bounds.x + bounds.width) - bounds.center().x) / v1.x, + ), + ); + + let start = bounds.center() + v1 * distance_to_rect; + let end = bounds.center() - v1 * distance_to_rect; + + pack[40] = start.x; + pack[41] = start.y; + pack[42] = end.x; + pack[43] = end.y; + + pack + } + } + } +} + +#[derive(Debug, Default, Clone, Copy, PartialEq)] +/// A point along the gradient vector where the specified [`color`] is unmixed. +/// +/// [`color`]: Self::color +pub struct ColorStop { + /// Offset along the gradient vector. + pub offset: f32, + + /// The color of the gradient at the specified [`offset`]. + /// + /// [`offset`]: Self::offset + pub color: Color, +} + +pub mod linear { + //! Linear gradient builder & definition. + use crate::gradient::{ColorStop, Gradient}; + use crate::{Color, Radians}; + + /// A linear gradient that can be used as a [`Background`]. + #[derive(Debug, Clone, Copy, PartialEq)] + pub struct Linear { + /// How the [`Gradient`] is angled within its bounds. + pub angle: Radians, + /// [`ColorStop`]s along the linear gradient path. + pub color_stops: [ColorStop; 8], + } + + /// A [`Linear`] builder. + #[derive(Debug)] + pub struct Builder { + angle: Radians, + stops: [ColorStop; 8], + error: Option, + } + + impl Builder { + /// Creates a new [`Builder`]. + pub fn new(angle: Radians) -> Self { + Self { + angle, + stops: std::array::from_fn(|_| ColorStop { + offset: 2.0, //default offset = invalid + color: Default::default(), + }), + error: None, + } + } + + /// Adds a new [`ColorStop`], defined by an offset and a color, to the gradient. + /// + /// `offset` must be between `0.0` and `1.0` or the gradient cannot be built. + /// + /// Any stop added after the 8th will be silently ignored. + pub fn add_stop(mut self, offset: f32, color: Color) -> Self { + if offset.is_finite() && (0.0..=1.0).contains(&offset) { + match self.stops.binary_search_by(|stop| { + stop.offset.partial_cmp(&offset).unwrap() + }) { + Ok(_) => { + self.error = Some(BuilderError::DuplicateOffset(offset)) + } + Err(index) => { + if index < 8 { + self.stops[index] = ColorStop { offset, color }; + } + } + } + } else { + self.error = Some(BuilderError::InvalidOffset(offset)) + }; + + self + } + + /// Adds multiple [`ColorStop`]s to the gradient. + /// + /// Any stop added after the 8th will be silently ignored. + pub fn add_stops( + mut self, + stops: impl IntoIterator, + ) -> Self { + for stop in stops.into_iter() { + self = self.add_stop(stop.offset, stop.color) + } + + self + } + + /// Builds the linear [`Gradient`] of this [`Builder`]. + /// + /// Returns `BuilderError` if gradient in invalid. + pub fn build(self) -> Result { + if self.stops.is_empty() { + Err(BuilderError::MissingColorStop) + } else if let Some(error) = self.error { + Err(error) + } else { + Ok(Gradient::Linear(Linear { + angle: self.angle, + color_stops: self.stops, + })) + } + } + } + + /// An error that happened when building a [`Linear`] gradient. + #[derive(Debug, thiserror::Error)] + pub enum BuilderError { + #[error("Gradients must contain at least one color stop.")] + /// Gradients must contain at least one color stop. + MissingColorStop, + #[error("Offset {0} must be a unique, finite number.")] + /// Offsets in a gradient must all be unique & finite. + DuplicateOffset(f32), + #[error("Offset {0} must be between 0.0..=1.0.")] + /// Offsets in a gradient must be between 0.0..=1.0. + InvalidOffset(f32), + } +} diff --git a/core/src/lib.rs b/core/src/lib.rs index f95d61f642..2f743eef34 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -25,10 +25,12 @@ #![forbid(unsafe_code, rust_2018_idioms)] #![allow(clippy::inherent_to_string, clippy::type_complexity)] pub mod alignment; +pub mod gradient; pub mod keyboard; pub mod mouse; pub mod time; +mod angle; mod background; mod color; mod content_fit; @@ -41,10 +43,12 @@ mod size; mod vector; pub use alignment::Alignment; +pub use angle::{Degrees, Radians}; pub use background::Background; pub use color::Color; pub use content_fit::ContentFit; pub use font::Font; +pub use gradient::Gradient; pub use length::Length; pub use padding::Padding; pub use point::Point; diff --git a/core/src/rectangle.rs b/core/src/rectangle.rs index 4e0820510e..4fe9151957 100644 --- a/core/src/rectangle.rs +++ b/core/src/rectangle.rs @@ -116,8 +116,8 @@ impl std::ops::Mul for Rectangle { fn mul(self, scale: f32) -> Self { Self { - x: self.x as f32 * scale, - y: self.y as f32 * scale, + x: self.x * scale, + y: self.y * scale, width: self.width * scale, height: self.height * scale, } diff --git a/examples/benches/gradient/Cargo.toml b/examples/benches/gradient/Cargo.toml new file mode 100644 index 0000000000..52253a8bd3 --- /dev/null +++ b/examples/benches/gradient/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "gradient_bench" +version = "0.1.0" +edition = "2021" +authors = ["Bingus "] +publish = false + +[dependencies] +iced = { path = "../../..", features = ["canvas", "tokio", "debug"] } +rand = "0.8.5" +env_logger = "0.9" diff --git a/examples/benches/gradient/src/main.rs b/examples/benches/gradient/src/main.rs new file mode 100644 index 0000000000..ebb5cda3cc --- /dev/null +++ b/examples/benches/gradient/src/main.rs @@ -0,0 +1,150 @@ +use iced::theme::Container; +use iced::widget::{column, container, horizontal_space, row}; +use iced::{ + window, Application, Color, Command, Element, Length, Radians, Renderer, + Settings, Theme, +}; +use std::f32::consts::PI; + +fn main() -> iced::Result { + env_logger::builder().format_timestamp(None).init(); + + GradientBench::run(Settings { + window: window::Settings { + size: (2000, 1000), + ..Default::default() + }, + ..Default::default() + }) +} + +#[derive(Debug)] +struct GradientBench; + +struct ContainerStyle([Color; 8], Radians); + +impl container::StyleSheet for ContainerStyle { + type Style = Theme; + + fn appearance(&self, _style: &Self::Style) -> container::Appearance { + container::Appearance { + background: iced::Gradient::linear(self.1) + .add_stop(0.0, self.0[0]) + .add_stop(0.125, self.0[1]) + .add_stop(0.250, self.0[2]) + .add_stop(0.375, self.0[3]) + .add_stop(0.5, self.0[4]) + .add_stop(0.625, self.0[5]) + .add_stop(0.750, self.0[6]) + .add_stop(0.875, self.0[7]) + .build() + .expect("Build gradient") + .into(), + ..Default::default() + } + } +} + +impl Application for GradientBench { + type Executor = iced::executor::Default; + type Message = (); + type Theme = Theme; + type Flags = (); + + fn new(_flags: Self::Flags) -> (Self, Command) { + (GradientBench, Command::none()) + } + + fn title(&self) -> String { + "Gradient Bench".to_string() + } + + fn update(&mut self, _message: Self::Message) -> Command { + Command::none() + } + + fn view(&self) -> Element<'_, Self::Message, Renderer> { + //renders 5151 gradients + + let gradient = |color_mul: f32, angle: Radians| { + Element::from( + container(horizontal_space(Length::Units(20))) + .height(Length::Fill) + .style(Container::Custom(Box::new(ContainerStyle( + [ + Color::from_rgb( + 1.0 * color_mul, + 0.1 * color_mul, + 0.1 * color_mul, + ), + Color::from_rgb( + 0.9 * color_mul, + 0.2 * color_mul, + 0.2 * color_mul, + ), + Color::from_rgb( + 0.8 * color_mul, + 0.3 * color_mul, + 0.3 * color_mul, + ), + Color::from_rgb( + 0.7 * color_mul, + 0.4 * color_mul, + 0.4 * color_mul, + ), + Color::from_rgb( + 0.6 * color_mul, + 0.5 * color_mul, + 0.5 * color_mul, + ), + Color::from_rgb( + 0.5 * color_mul, + 0.6 * color_mul, + 0.6 * color_mul, + ), + Color::from_rgb( + 0.4 * color_mul, + 0.7 * color_mul, + 0.7 * color_mul, + ), + Color::from_rgb( + 0.3 * color_mul, + 0.8 * color_mul, + 0.8 * color_mul, + ), + ], + angle, + )))), + ) + }; + + let mut rows = Vec::>::new(); + + let mut c = 1; + while c < 51 { + let mut r = 1; + let mut gradients = vec![]; + + while r < 101 { + gradients + .push(gradient(r as f32 / 100.0, Radians(PI * (c as f32)))); + r += 1; + } + + rows.push(Element::from( + row(gradients).width(Length::Fill).height(Length::Units(20)), + )); + + c += 1; + } + + let content = column(rows).width(Length::Fill).height(Length::Fill); + + container(content) + .height(Length::Fill) + .width(Length::Fill) + .center_y() + .center_x() + .into() + } +} diff --git a/examples/benches/gradient_triangle/Cargo.toml b/examples/benches/gradient_triangle/Cargo.toml new file mode 100644 index 0000000000..f11a9f6073 --- /dev/null +++ b/examples/benches/gradient_triangle/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "gradient_triangle_bench" +version = "0.1.0" +edition = "2021" +authors = ["Bingus "] +publish = false + +[dependencies] +iced = { path = "../../..", features = ["canvas", "tokio", "debug"] } +rand = "0.8.5" +env_logger = "0.9" diff --git a/examples/benches/gradient_triangle/src/main.rs b/examples/benches/gradient_triangle/src/main.rs new file mode 100644 index 0000000000..1954bc0082 --- /dev/null +++ b/examples/benches/gradient_triangle/src/main.rs @@ -0,0 +1,180 @@ +use crate::canvas::{Canvas, Cursor, Fill, Geometry}; +use iced::widget::canvas::{self, Cache}; +use iced::{ + window, Application, Color, Command, Element, Length, Point, Rectangle, + Renderer, Settings, Size, Theme, +}; + +fn main() -> iced::Result { + env_logger::builder().format_timestamp(None).init(); + + GradientTriangleBench::run(Settings { + window: window::Settings { + size: (2000, 1000), + ..Default::default() + }, + ..Default::default() + }) +} + +#[derive(Debug)] +struct GradientTriangleBench { + cache: Cache, +} + +impl Application for GradientTriangleBench { + type Executor = iced::executor::Default; + type Message = (); + type Theme = Theme; + type Flags = (); + + fn new(_flags: Self::Flags) -> (Self, Command) { + ( + GradientTriangleBench { + cache: Default::default(), + }, + Command::none(), + ) + } + + fn title(&self) -> String { + "Gradient Bench".to_string() + } + + fn update(&mut self, _message: Self::Message) -> Command { + Command::none() + } + + fn view(&self) -> Element<'_, Self::Message, Renderer> { + Canvas::new(self) + .width(Length::Fill) + .height(Length::Fill) + .into() + } +} + +impl canvas::Program for GradientTriangleBench { + type State = (); + + fn draw( + &self, + _state: &Self::State, + _theme: &Theme, + bounds: Rectangle, + _cursor: Cursor, + ) -> Vec { + //renders 5151 gradients + let gradient = |color_mul: f32, bounds: &Rectangle| { + canvas::Gradient::linear( + bounds.position(), + Point::new( + bounds.x + bounds.size().width, + bounds.y + bounds.size().height, + ), + ) + .add_stop( + 0.0, + Color::from_rgb( + 1.0 * color_mul, + 0.1 * color_mul, + 0.1 * color_mul, + ), + ) + .add_stop( + 0.125, + Color::from_rgb( + 0.9 * color_mul, + 0.2 * color_mul, + 0.2 * color_mul, + ), + ) + .add_stop( + 0.250, + Color::from_rgb( + 0.8 * color_mul, + 0.3 * color_mul, + 0.3 * color_mul, + ), + ) + .add_stop( + 0.375, + Color::from_rgb( + 0.7 * color_mul, + 0.4 * color_mul, + 0.4 * color_mul, + ), + ) + .add_stop( + 0.5, + Color::from_rgb( + 0.6 * color_mul, + 0.5 * color_mul, + 0.5 * color_mul, + ), + ) + .add_stop( + 0.625, + Color::from_rgb( + 0.5 * color_mul, + 0.6 * color_mul, + 0.6 * color_mul, + ), + ) + .add_stop( + 0.750, + Color::from_rgb( + 0.4 * color_mul, + 0.7 * color_mul, + 0.7 * color_mul, + ), + ) + .add_stop( + 0.875, + Color::from_rgb( + 0.3 * color_mul, + 0.8 * color_mul, + 0.8 * color_mul, + ), + ) + .build() + .expect("Build Gradient") + }; + + let geometry = self.cache.draw(bounds.size(), |frame| { + let g_size: f32 = 20.0; + + let mut total_count = 0; + let mut vert_offset = 0.0; + + while vert_offset <= bounds.height { + let mut hor_position = Point::new(0.0, vert_offset); + + let mut r = 1; + while hor_position.x <= bounds.width { + let b = Rectangle::new( + Point::new(hor_position.x, vert_offset), + Size::new(g_size, g_size), + ); + + frame.fill_rectangle( + b.position(), + b.size(), + Fill { + style: gradient(r as f32 / 200.0, &b).into(), + ..Default::default() + }, + ); + total_count += 1; + r += 1; + hor_position.x += g_size; + } + + vert_offset += g_size; + } + + println!("Created {} gradients", total_count); + }); + + vec![geometry] + } +} diff --git a/examples/modern_art/Cargo.toml b/examples/benches/solids/Cargo.toml similarity index 63% rename from examples/modern_art/Cargo.toml rename to examples/benches/solids/Cargo.toml index 4242d209d9..a6d522a46b 100644 --- a/examples/modern_art/Cargo.toml +++ b/examples/benches/solids/Cargo.toml @@ -1,11 +1,11 @@ [package] -name = "modern_art" +name = "solid_bench" version = "0.1.0" -authors = ["Bingus "] edition = "2021" +authors = ["Bingus "] publish = false [dependencies] -iced = { path = "../..", features = ["canvas", "tokio", "debug"] } +iced = { path = "../../..", features = ["canvas", "tokio", "debug"] } rand = "0.8.5" env_logger = "0.9" diff --git a/examples/benches/solids/src/main.rs b/examples/benches/solids/src/main.rs new file mode 100644 index 0000000000..04a59e48b9 --- /dev/null +++ b/examples/benches/solids/src/main.rs @@ -0,0 +1,98 @@ +use iced::theme::Container; +use iced::widget::{column, container, horizontal_space, row}; +use iced::{ + window, Application, Color, Command, Element, Length, Renderer, Settings, + Theme, +}; + +fn main() -> iced::Result { + env_logger::builder().format_timestamp(None).init(); + + SolidBench::run(Settings { + try_opengles_first: true, + window: window::Settings { + size: (2000, 1000), + ..Default::default() + }, + ..Default::default() + }) +} + +#[derive(Debug)] +struct SolidBench; + +struct ContainerStyle(Color); + +impl container::StyleSheet for ContainerStyle { + type Style = Theme; + + fn appearance(&self, _style: &Self::Style) -> container::Appearance { + container::Appearance { + background: self.0.into(), + ..Default::default() + } + } +} + +impl Application for SolidBench { + type Executor = iced::executor::Default; + type Message = (); + type Theme = Theme; + type Flags = (); + + fn new(_flags: Self::Flags) -> (Self, Command) { + (SolidBench, Command::none()) + } + + fn title(&self) -> String { + "Solid Bench".to_string() + } + + fn update(&mut self, _message: Self::Message) -> Command { + Command::none() + } + + fn view(&self) -> Element<'_, Self::Message, Renderer> { + let solid = |color_mul: f32| { + Element::from( + container(horizontal_space(Length::Units(20))) + .height(Length::Fill) + .style(Container::Custom(Box::new(ContainerStyle( + Color::from_rgb( + 1.0 * color_mul, + 0.8 * color_mul, + 0.7 * color_mul, + ), + )))), + ) + }; + + let mut rows = Vec::>::new(); + + let mut c = 1; + while c < 51 { + let mut r = 1; + let mut solids = vec![]; + + while r < 101 { + solids.push(solid(r as f32 / 100.0)); + r += 1; + } + + rows.push(Element::from( + row(solids).width(Length::Fill).height(Length::Units(20)), + )); + + c += 1; + } + + let content = column(rows).width(Length::Fill).height(Length::Fill); + + container(content) + .height(Length::Fill) + .width(Length::Fill) + .center_y() + .center_x() + .into() + } +} diff --git a/examples/benches/solids_triangle/Cargo.toml b/examples/benches/solids_triangle/Cargo.toml new file mode 100644 index 0000000000..24b471049c --- /dev/null +++ b/examples/benches/solids_triangle/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "solid_triangle_bench" +version = "0.1.0" +edition = "2021" +authors = ["Bingus "] +publish = false + +[dependencies] +iced = { path = "../../..", features = ["canvas", "tokio", "debug"] } +rand = "0.8.5" +env_logger = "0.9" diff --git a/examples/benches/solids_triangle/src/main.rs b/examples/benches/solids_triangle/src/main.rs new file mode 100644 index 0000000000..da066ba735 --- /dev/null +++ b/examples/benches/solids_triangle/src/main.rs @@ -0,0 +1,112 @@ +use crate::canvas::{Canvas, Cursor, Fill, Geometry}; +use iced::widget::canvas::{self, Cache}; +use iced::{ + window, Application, Color, Command, Element, Length, Point, Rectangle, + Renderer, Settings, Size, Theme, +}; + +fn main() -> iced::Result { + env_logger::builder().format_timestamp(None).init(); + + SolidTriangleBench::run(Settings { + try_opengles_first: true, + window: window::Settings { + size: (2000, 1000), + ..Default::default() + }, + ..Default::default() + }) +} + +#[derive(Debug)] +struct SolidTriangleBench { + cache: Cache, +} + +impl Application for SolidTriangleBench { + type Executor = iced::executor::Default; + type Message = (); + type Theme = Theme; + type Flags = (); + + fn new(_flags: Self::Flags) -> (Self, Command) { + ( + SolidTriangleBench { + cache: Default::default(), + }, + Command::none(), + ) + } + + fn title(&self) -> String { + "Solid Triangle Bench".to_string() + } + + fn update(&mut self, _message: Self::Message) -> Command { + Command::none() + } + + fn view(&self) -> Element<'_, Self::Message, Renderer> { + Canvas::new(self) + .width(Length::Fill) + .height(Length::Fill) + .into() + } +} + +impl canvas::Program for SolidTriangleBench { + type State = (); + + fn draw( + &self, + _state: &Self::State, + _theme: &Theme, + bounds: Rectangle, + _cursor: Cursor, + ) -> Vec { + //renders 5151 solids + let geometry = self.cache.draw(bounds.size(), |frame| { + let solid_size: f32 = 20.0; + + let mut total_count = 0; + let mut vert_offset = 0.0; + + while vert_offset <= bounds.height { + let mut hor_position = Point::new(0.0, vert_offset); + + let mut r = 1; + while hor_position.x <= bounds.width { + let b = Rectangle::new( + Point::new(hor_position.x, vert_offset), + Size::new(solid_size, solid_size), + ); + + let color_mul = r as f32 / 200.0; + + frame.fill_rectangle( + b.position(), + b.size(), + Fill { + style: Color::from_rgb( + 1.0 * color_mul, + 0.8 * color_mul, + 0.9 * color_mul, + ) + .into(), + ..Default::default() + }, + ); + total_count += 1; + r += 1; + hor_position.x += solid_size; + } + + vert_offset += solid_size; + } + + println!("Created {} solids", total_count); + }); + + vec![geometry] + } +} diff --git a/examples/modern_art/src/main.rs b/examples/modern_art/src/main.rs deleted file mode 100644 index 28ed3e215c..0000000000 --- a/examples/modern_art/src/main.rs +++ /dev/null @@ -1,142 +0,0 @@ -use iced::widget::canvas::{ - self, gradient::Location, gradient::Position, Cache, Canvas, Cursor, Frame, - Geometry, Gradient, -}; -use iced::{ - executor, Application, Color, Command, Element, Length, Point, Rectangle, - Renderer, Settings, Size, Theme, -}; -use rand::{thread_rng, Rng}; - -fn main() -> iced::Result { - env_logger::builder().format_timestamp(None).init(); - - ModernArt::run(Settings { - antialiasing: true, - ..Settings::default() - }) -} - -#[derive(Debug, Clone, Copy)] -enum Message {} - -struct ModernArt { - cache: Cache, -} - -impl Application for ModernArt { - type Executor = executor::Default; - type Message = Message; - type Theme = Theme; - type Flags = (); - - fn new(_flags: Self::Flags) -> (Self, Command) { - ( - ModernArt { - cache: Default::default(), - }, - Command::none(), - ) - } - - fn title(&self) -> String { - String::from("Modern Art") - } - - fn update(&mut self, _message: Message) -> Command { - Command::none() - } - - fn view(&self) -> Element<'_, Self::Message, Renderer> { - Canvas::new(self) - .width(Length::Fill) - .height(Length::Fill) - .into() - } -} - -impl canvas::Program for ModernArt { - type State = (); - - fn draw( - &self, - _state: &Self::State, - _theme: &Theme, - bounds: Rectangle, - _cursor: Cursor, - ) -> Vec { - let geometry = self.cache.draw(bounds.size(), |frame| { - let num_squares = thread_rng().gen_range(0..1200); - - let mut i = 0; - while i <= num_squares { - generate_box(frame, bounds.size()); - i += 1; - } - }); - - vec![geometry] - } -} - -fn random_direction() -> Location { - match thread_rng().gen_range(0..8) { - 0 => Location::TopLeft, - 1 => Location::Top, - 2 => Location::TopRight, - 3 => Location::Right, - 4 => Location::BottomRight, - 5 => Location::Bottom, - 6 => Location::BottomLeft, - 7 => Location::Left, - _ => Location::TopLeft, - } -} - -fn generate_box(frame: &mut Frame, bounds: Size) -> bool { - let solid = rand::random::(); - - let random_color = || -> Color { - Color::from_rgb( - thread_rng().gen_range(0.0..1.0), - thread_rng().gen_range(0.0..1.0), - thread_rng().gen_range(0.0..1.0), - ) - }; - - let gradient = |top_left: Point, size: Size| -> Gradient { - let mut builder = Gradient::linear(Position::Relative { - top_left, - size, - start: random_direction(), - end: random_direction(), - }); - let stops = thread_rng().gen_range(1..15u32); - - let mut i = 0; - while i <= stops { - builder = builder.add_stop(i as f32 / stops as f32, random_color()); - i += 1; - } - - builder.build().unwrap() - }; - - let top_left = Point::new( - thread_rng().gen_range(0.0..bounds.width), - thread_rng().gen_range(0.0..bounds.height), - ); - - let size = Size::new( - thread_rng().gen_range(50.0..200.0), - thread_rng().gen_range(50.0..200.0), - ); - - if solid { - frame.fill_rectangle(top_left, size, random_color()); - } else { - frame.fill_rectangle(top_left, size, gradient(top_left, size)); - }; - - solid -} diff --git a/examples/solar_system/src/main.rs b/examples/solar_system/src/main.rs index 9e303576f9..2bd0403bdf 100644 --- a/examples/solar_system/src/main.rs +++ b/examples/solar_system/src/main.rs @@ -11,9 +11,8 @@ use iced::executor; use iced::theme::{self, Theme}; use iced::time; use iced::widget::canvas; -use iced::widget::canvas::gradient::{self, Gradient}; use iced::widget::canvas::stroke::{self, Stroke}; -use iced::widget::canvas::{Cursor, Path}; +use iced::widget::canvas::{Cursor, Gradient, Path}; use iced::window; use iced::{ Application, Color, Command, Element, Length, Point, Rectangle, Settings, @@ -207,15 +206,14 @@ impl canvas::Program for State { let earth = Path::circle(Point::ORIGIN, Self::EARTH_RADIUS); - let earth_fill = - Gradient::linear(gradient::Position::Absolute { - start: Point::new(-Self::EARTH_RADIUS, 0.0), - end: Point::new(Self::EARTH_RADIUS, 0.0), - }) - .add_stop(0.2, Color::from_rgb(0.15, 0.50, 1.0)) - .add_stop(0.8, Color::from_rgb(0.0, 0.20, 0.47)) - .build() - .expect("Build Earth fill gradient"); + let earth_fill = Gradient::linear( + Point::new(-Self::EARTH_RADIUS, 0.0), + Point::new(Self::EARTH_RADIUS, 0.0), + ) + .add_stop(0.2, Color::from_rgb(0.15, 0.50, 1.0)) + .add_stop(0.8, Color::from_rgb(0.0, 0.20, 0.47)) + .build() + .expect("Build Earth fill gradient"); frame.fill(&earth, earth_fill); diff --git a/examples/tour/src/main.rs b/examples/tour/src/main.rs index 378508e1e4..6c1f4c6a94 100644 --- a/examples/tour/src/main.rs +++ b/examples/tour/src/main.rs @@ -1,10 +1,11 @@ -use iced::alignment; use iced::theme; +use iced::theme::Palette; use iced::widget::{ checkbox, column, container, horizontal_space, image, radio, row, scrollable, slider, text, text_input, toggler, vertical_space, }; use iced::widget::{Button, Column, Container, Slider}; +use iced::{alignment, widget, Degrees, Gradient, Radians, Theme}; use iced::{Color, Element, Length, Renderer, Sandbox, Settings}; pub fn main() -> iced::Result { @@ -53,9 +54,9 @@ impl Sandbox for Tour { if steps.has_previous() { controls = controls.push( - button("Back") - .on_press(Message::BackPressed) - .style(theme::Button::Secondary), + button("Back").on_press(Message::BackPressed).style( + theme::Button::Custom(Box::new(CustomButtonStyle::Danger)), + ), ); } @@ -63,9 +64,9 @@ impl Sandbox for Tour { if steps.can_continue() { controls = controls.push( - button("Next") - .on_press(Message::NextPressed) - .style(theme::Button::Primary), + button("Next").on_press(Message::NextPressed).style( + theme::Button::Custom(Box::new(CustomButtonStyle::Success)), + ), ); } @@ -670,3 +671,39 @@ pub enum Layout { Row, Column, } + +enum CustomButtonStyle { + Success, + Danger, +} + +impl widget::button::StyleSheet for CustomButtonStyle { + type Style = Theme; + + fn active(&self, _style: &Self::Style) -> widget::button::Appearance { + match self { + CustomButtonStyle::Success => widget::button::Appearance { + background: Gradient::linear(Degrees(270.0)) + .add_stop(0.0, Palette::LIGHT.primary) + .add_stop(1.0, Color::from_rgb8(54, 80, 168)) + .build() + .expect("Build gradient") + .into(), + text_color: Color::WHITE, + ..Default::default() + }, + CustomButtonStyle::Danger => widget::button::Appearance { + background: Gradient::linear(Radians( + 3.0 * std::f32::consts::PI / 2.0, + )) + .add_stop(0.0, Palette::LIGHT.danger) + .add_stop(1.0, Color::from_rgb8(125, 26, 24)) + .build() + .expect("Build gradient") + .into(), + text_color: Color::WHITE, + ..Default::default() + }, + } + } +} diff --git a/glow/src/image.rs b/glow/src/image.rs index 521a01e7bc..fa939007bc 100644 --- a/glow/src/image.rs +++ b/glow/src/image.rs @@ -2,7 +2,7 @@ mod storage; use storage::Storage; -pub use iced_graphics::triangle::{Mesh2D, Vertex2D}; +pub use iced_graphics::triangle::{GradientVertex2D, Mesh2D}; use crate::program::{self, Shader}; use crate::Transformation; @@ -43,12 +43,14 @@ impl Pipeline { let vertex_shader = Shader::vertex( gl, shader_version, - include_str!("shader/common/image.vert"), + [], + include_str!("shader/triangle/image.vert"), ); let fragment_shader = Shader::fragment( gl, shader_version, - include_str!("shader/common/image.frag"), + [], + include_str!("shader/triangle/image.frag"), ); program::create( diff --git a/glow/src/program.rs b/glow/src/program.rs index 1eb9c53589..7270f7acbf 100644 --- a/glow/src/program.rs +++ b/glow/src/program.rs @@ -85,10 +85,11 @@ impl Shader { pub fn vertex( gl: &glow::Context, version: &Version, + includes: impl IntoIterator, content: &'static str, ) -> Self { - let content = format!("{}\n{}", version.vertex, content); - + log::info!("GLOW - Compiling vertex shader."); + let content = concat_shader(&version.vertex, content, includes); Shader::compile(gl, glow::VERTEX_SHADER, &content) } @@ -96,14 +97,35 @@ impl Shader { pub fn fragment( gl: &glow::Context, version: &Version, + includes: impl IntoIterator, content: &'static str, ) -> Self { - let content = format!("{}\n{}", version.fragment, content); - + log::info!("GLOW - Compiling fragment shader."); + let content = concat_shader(&version.fragment, content, includes); Shader::compile(gl, glow::FRAGMENT_SHADER, &content) } } +fn concat_shader( + version: &String, + content: &'static str, + includes: impl IntoIterator, +) -> String { + let includes = includes + .into_iter() + .fold(String::new(), |acc, i| acc + "\n" + i); + + if includes.is_empty() { + format!("{}\n{}", version, content) + } else { + format!( + "{}\n{}", + version, + content.replace("// #includes", includes.as_str()) + ) + } +} + pub unsafe fn create( gl: &glow::Context, shaders: &[Shader], diff --git a/glow/src/quad.rs b/glow/src/quad.rs index d9f1c6ae70..bb0f003b4f 100644 --- a/glow/src/quad.rs +++ b/glow/src/quad.rs @@ -37,7 +37,7 @@ impl Pipeline { &mut self, gl: &glow::Context, target_height: u32, - instances: &[layer::Quad], + instances: &layer::Quads, transformation: Transformation, scale: f32, bounds: Rectangle, diff --git a/glow/src/quad/compatibility.rs b/glow/src/quad/compatibility.rs index e909162c3b..39a8d1b37b 100644 --- a/glow/src/quad/compatibility.rs +++ b/glow/src/quad/compatibility.rs @@ -1,8 +1,6 @@ -use crate::program::{self, Shader}; -use crate::Transformation; +use crate::program; use glow::HasContext; -use iced_graphics::layer; -use iced_native::Rectangle; +use iced_graphics::{layer, Rectangle, Transformation}; // Only change `MAX_QUADS`, otherwise you could cause problems // by splitting a triangle into different render passes. @@ -12,16 +10,8 @@ const MAX_INDICES: usize = MAX_QUADS * 6; #[derive(Debug)] pub struct Pipeline { - program: ::Program, - vertex_array: ::VertexArray, - vertex_buffer: ::Buffer, - index_buffer: ::Buffer, - transform_location: ::UniformLocation, - scale_location: ::UniformLocation, - screen_height_location: ::UniformLocation, - current_transform: Transformation, - current_scale: f32, - current_target_height: u32, + solid: solid::Program, + gradient: gradient::Program, } impl Pipeline { @@ -29,73 +19,9 @@ impl Pipeline { gl: &glow::Context, shader_version: &program::Version, ) -> Pipeline { - let program = unsafe { - let vertex_shader = Shader::vertex( - gl, - shader_version, - include_str!("../shader/compatibility/quad.vert"), - ); - let fragment_shader = Shader::fragment( - gl, - shader_version, - include_str!("../shader/compatibility/quad.frag"), - ); - - program::create( - gl, - &[vertex_shader, fragment_shader], - &[ - (0, "i_Pos"), - (1, "i_Scale"), - (2, "i_Color"), - (3, "i_BorderColor"), - (4, "i_BorderRadius"), - (5, "i_BorderWidth"), - ], - ) - }; - - let transform_location = - unsafe { gl.get_uniform_location(program, "u_Transform") } - .expect("Get transform location"); - - let scale_location = - unsafe { gl.get_uniform_location(program, "u_Scale") } - .expect("Get scale location"); - - let screen_height_location = - unsafe { gl.get_uniform_location(program, "u_ScreenHeight") } - .expect("Get target height location"); - - unsafe { - gl.use_program(Some(program)); - - gl.uniform_matrix_4_f32_slice( - Some(&transform_location), - false, - Transformation::identity().as_ref(), - ); - - gl.uniform_1_f32(Some(&scale_location), 1.0); - gl.uniform_1_f32(Some(&screen_height_location), 0.0); - - gl.use_program(None); - } - - let (vertex_array, vertex_buffer, index_buffer) = - unsafe { create_buffers(gl, MAX_VERTICES) }; - Pipeline { - program, - vertex_array, - vertex_buffer, - index_buffer, - transform_location, - scale_location, - screen_height_location, - current_transform: Transformation::identity(), - current_scale: 1.0, - current_target_height: 0, + solid: solid::Program::new(gl, shader_version), + gradient: gradient::Program::new(gl, shader_version), } } @@ -103,24 +29,11 @@ impl Pipeline { &mut self, gl: &glow::Context, target_height: u32, - instances: &[layer::Quad], + instances: &layer::Quads, transformation: Transformation, scale: f32, bounds: Rectangle, ) { - // TODO: Remove this allocation (probably by changing the shader and removing the need of two `position`) - let vertices: Vec = - instances.iter().flat_map(Vertex::from_quad).collect(); - - // TODO: Remove this allocation (or allocate only when needed) - let indices: Vec = (0..instances.len().min(MAX_QUADS) as i32) - .flat_map(|i| { - [i * 4, 1 + i * 4, 2 + i * 4, 2 + i * 4, 1 + i * 4, 3 + i * 4] - }) - .cycle() - .take(instances.len() * 6) - .collect(); - unsafe { gl.enable(glow::SCISSOR_TEST); gl.scissor( @@ -129,221 +42,547 @@ impl Pipeline { bounds.width as i32, bounds.height as i32, ); + } + + self.solid.bind(gl); + self.solid.uniforms.update( + gl, + transformation, + scale, + target_height as f32, + ); + draw(gl, instances.solids.as_slice()); + + self.gradient.bind(gl); + self.gradient.uniforms.update( + gl, + transformation, + scale, + target_height as f32, + ); + draw(gl, instances.gradients.as_slice()); - gl.use_program(Some(self.program)); - gl.bind_vertex_array(Some(self.vertex_array)); - gl.bind_buffer(glow::ARRAY_BUFFER, Some(self.vertex_buffer)); - gl.bind_buffer(glow::ELEMENT_ARRAY_BUFFER, Some(self.index_buffer)); + unsafe { + gl.bind_vertex_array(None); + gl.use_program(None); + gl.disable(glow::SCISSOR_TEST); } + } +} - if transformation != self.current_transform { - unsafe { - gl.uniform_matrix_4_f32_slice( - Some(&self.transform_location), - false, - transformation.as_ref(), +mod solid { + use crate::program::{self, Shader}; + use crate::quad; + use crate::quad::compatibility::{CompatVertex, MAX_VERTICES}; + use glow::{Context, HasContext}; + use iced_graphics::layer; + use layer::quad::Solid; + + #[derive(Debug)] + pub struct Program { + program: glow::Program, + pub uniforms: quad::core::Uniforms, + pub vertex_array: ::VertexArray, + pub vertex_buffer: ::Buffer, + pub index_buffer: ::Buffer, + } + + impl Program { + pub fn new(gl: &Context, shader_version: &program::Version) -> Self { + log::info!("GLOW: compiling quad (COMPATIBILITY) solid shaders."); + + let program = unsafe { + let vertex_shader = Shader::vertex( + gl, + shader_version, + [], + include_str!("../shader/quad/compatibility/solid.vert"), + ); + let fragment_shader = Shader::fragment( + gl, + shader_version, + [ + include_str!("../shader/includes/border_radius.vert"), + include_str!("../shader/includes/quad_distance.vert"), + ], + include_str!("../shader/quad/compatibility/solid.frag"), ); - self.current_transform = transformation; - } - } + program::create( + gl, + &[vertex_shader, fragment_shader], + &[ + (0, "i_color"), + (1, "i_position"), + (2, "i_size"), + (3, "i_border_color"), + (4, "i_border_radius"), + (5, "i_border_width"), + (6, "i_quad_position"), + ], + ) + }; + + let (vertex_array, vertex_buffer, index_buffer) = + unsafe { Self::create_buffers(gl, MAX_VERTICES) }; - if scale != self.current_scale { - unsafe { - gl.uniform_1_f32(Some(&self.scale_location), scale); + Self { + program, + uniforms: quad::core::Uniforms::new(gl, program), + vertex_array, + vertex_buffer, + index_buffer, } - - self.current_scale = scale; } - if target_height != self.current_target_height { + pub fn bind(&self, gl: &glow::Context) { unsafe { - gl.uniform_1_f32( - Some(&self.screen_height_location), - target_height as f32, + gl.use_program(Some(self.program)); + gl.bind_vertex_array(Some(self.vertex_array)); + gl.bind_buffer(glow::ARRAY_BUFFER, Some(self.vertex_buffer)); + gl.bind_buffer( + glow::ELEMENT_ARRAY_BUFFER, + Some(self.index_buffer), ); } + } + + unsafe fn create_buffers( + gl: &Context, + size: usize, + ) -> ( + ::VertexArray, + ::Buffer, + ::Buffer, + ) { + let vertex_array = + gl.create_vertex_array().expect("Create solid vertex array"); + let vertex_buffer = + gl.create_buffer().expect("Create solid vertex buffer"); + let index_buffer = + gl.create_buffer().expect("Create solid index buffer"); + + gl.bind_vertex_array(Some(vertex_array)); + + gl.bind_buffer(glow::ELEMENT_ARRAY_BUFFER, Some(index_buffer)); + gl.buffer_data_size( + glow::ELEMENT_ARRAY_BUFFER, + 12 * size as i32, + glow::DYNAMIC_DRAW, + ); + + gl.bind_buffer(glow::ARRAY_BUFFER, Some(vertex_buffer)); + gl.buffer_data_size( + glow::ARRAY_BUFFER, + (size * CompatVertex::::SIZE) as i32, + glow::DYNAMIC_DRAW, + ); - self.current_target_height = target_height; + let stride = CompatVertex::::SIZE as i32; + + // Color + gl.enable_vertex_attrib_array(0); + gl.vertex_attrib_pointer_f32(0, 4, glow::FLOAT, false, stride, 0); + + // Position + gl.enable_vertex_attrib_array(1); + gl.vertex_attrib_pointer_f32( + 1, + 2, + glow::FLOAT, + false, + stride, + 4 * 4, + ); + + // Size + gl.enable_vertex_attrib_array(2); + gl.vertex_attrib_pointer_f32( + 2, + 2, + glow::FLOAT, + false, + stride, + 4 * (4 + 2), + ); + + // Border Color + gl.enable_vertex_attrib_array(3); + gl.vertex_attrib_pointer_f32( + 3, + 4, + glow::FLOAT, + false, + stride, + 4 * (4 + 2 + 2), + ); + + // Border Radii + gl.enable_vertex_attrib_array(4); + gl.vertex_attrib_pointer_f32( + 4, + 4, + glow::FLOAT, + false, + stride, + 4 * (4 + 2 + 2 + 4), + ); + + // Border Width + gl.enable_vertex_attrib_array(5); + gl.vertex_attrib_pointer_f32( + 5, + 1, + glow::FLOAT, + false, + stride, + 4 * (4 + 2 + 2 + 4 + 4), + ); + + // Quad Position + gl.enable_vertex_attrib_array(6); + gl.vertex_attrib_pointer_f32( + 6, + 2, + glow::FLOAT, + false, + stride, + 4 * (4 + 2 + 2 + 4 + 4 + 1), + ); + + gl.bind_vertex_array(None); + gl.bind_buffer(glow::ARRAY_BUFFER, None); + gl.bind_buffer(glow::ELEMENT_ARRAY_BUFFER, None); + + (vertex_array, vertex_buffer, index_buffer) } + } +} - let passes = vertices - .chunks(MAX_VERTICES) - .zip(indices.chunks(MAX_INDICES)); +mod gradient { + use crate::program::{self, Shader}; + use crate::quad; + use crate::quad::compatibility::{CompatVertex, MAX_VERTICES}; + use glow::{Context, HasContext}; + use iced_graphics::layer; + use layer::quad::Gradient; + + #[derive(Debug)] + pub struct Program { + program: glow::Program, + pub uniforms: quad::core::Uniforms, + pub vertex_array: ::VertexArray, + pub vertex_buffer: ::Buffer, + pub index_buffer: ::Buffer, + } - for (vertices, indices) in passes { - unsafe { - gl.buffer_sub_data_u8_slice( - glow::ARRAY_BUFFER, - 0, - bytemuck::cast_slice(vertices), + impl Program { + pub fn new(gl: &Context, shader_version: &program::Version) -> Self { + log::info!( + "GLOW: compiling quad (COMPATIBILITY) gradient shaders." + ); + + let program = unsafe { + let vertex_shader = Shader::vertex( + gl, + shader_version, + [], + include_str!("../shader/quad/compatibility/gradient.vert"), + ); + let fragment_shader = Shader::fragment( + gl, + shader_version, + [ + include_str!("../shader/includes/border_radius.vert"), + include_str!("../shader/includes/quad_distance.vert"), + include_str!("../shader/includes/gradient.frag"), + ], + include_str!("../shader/quad/compatibility/gradient.frag"), ); - gl.buffer_sub_data_u8_slice( + program::create( + gl, + &[vertex_shader, fragment_shader], + &[ + (0, "i_colors_1"), + (1, "i_colors_2"), + (2, "i_colors_3"), + (3, "i_colors_4"), + (4, "i_colors_5"), + (5, "i_colors_6"), + (6, "i_colors_7"), + (7, "i_colors_8"), + (8, "i_offsets_1"), + (9, "i_offsets_2"), + (10, "i_direction"), + (11, "i_position_and_size"), + (12, "i_border_color"), + (13, "i_border_radius"), + (14, "i_border_width"), + (15, "i_quad_position"), + ], + ) + }; + + let (vertex_array, vertex_buffer, index_buffer) = + unsafe { Self::create_buffers(gl, MAX_VERTICES) }; + + Self { + program, + uniforms: quad::core::Uniforms::new(gl, program), + vertex_array, + vertex_buffer, + index_buffer, + } + } + + pub fn bind(&self, gl: &glow::Context) { + unsafe { + gl.use_program(Some(self.program)); + gl.bind_vertex_array(Some(self.vertex_array)); + gl.bind_buffer(glow::ARRAY_BUFFER, Some(self.vertex_buffer)); + gl.bind_buffer( glow::ELEMENT_ARRAY_BUFFER, - 0, - bytemuck::cast_slice(indices), + Some(self.index_buffer), ); + } + } - gl.draw_elements( - glow::TRIANGLES, - indices.len() as i32, - glow::UNSIGNED_INT, - 0, + unsafe fn create_buffers( + gl: &Context, + size: usize, + ) -> ( + ::VertexArray, + ::Buffer, + ::Buffer, + ) { + let vertex_array = gl + .create_vertex_array() + .expect("Create gradient vertex array"); + let vertex_buffer = + gl.create_buffer().expect("Create gradient vertex buffer"); + let index_buffer = + gl.create_buffer().expect("Create gradient index buffer"); + + gl.bind_vertex_array(Some(vertex_array)); + + gl.bind_buffer(glow::ELEMENT_ARRAY_BUFFER, Some(index_buffer)); + gl.buffer_data_size( + glow::ELEMENT_ARRAY_BUFFER, + 12 * size as i32, + glow::DYNAMIC_DRAW, + ); + + gl.bind_buffer(glow::ARRAY_BUFFER, Some(vertex_buffer)); + gl.buffer_data_size( + glow::ARRAY_BUFFER, + (size * CompatVertex::::SIZE) as i32, + glow::DYNAMIC_DRAW, + ); + + let stride = CompatVertex::::SIZE as i32; + + // Colors vec4 array[8] (indices 1-8) + for i in 0..=7 { + gl.enable_vertex_attrib_array(i); + gl.vertex_attrib_pointer_f32( + i, + 4, + glow::FLOAT, + false, + stride, + (4 * i * 4) as i32, ); } - } - unsafe { + // Offsets 1-4 + gl.enable_vertex_attrib_array(8); + gl.vertex_attrib_pointer_f32( + 8, + 4, + glow::FLOAT, + false, + stride, + 4 * 32, + ); + + // Offsets 5-8 + gl.enable_vertex_attrib_array(9); + gl.vertex_attrib_pointer_f32( + 9, + 4, + glow::FLOAT, + false, + stride, + 4 * (32 + 4), + ); + + // Direction + gl.enable_vertex_attrib_array(10); + gl.vertex_attrib_pointer_f32( + 10, + 4, + glow::FLOAT, + false, + stride, + 4 * (32 + 4 + 4), + ); + + // Position & Scale + gl.enable_vertex_attrib_array(11); + gl.vertex_attrib_pointer_f32( + 11, + 4, + glow::FLOAT, + false, + stride, + 4 * (32 + 4 + 4 + 4), + ); + + // Border Color + gl.enable_vertex_attrib_array(12); + gl.vertex_attrib_pointer_f32( + 12, + 4, + glow::FLOAT, + false, + stride, + 4 * (32 + 4 + 4 + 4 + 4), + ); + + // Border Radii + gl.enable_vertex_attrib_array(13); + gl.vertex_attrib_pointer_f32( + 13, + 4, + glow::FLOAT, + false, + stride, + 4 * (32 + 4 + 4 + 4 + 4 + 4), + ); + + // Border Width + gl.enable_vertex_attrib_array(14); + gl.vertex_attrib_pointer_f32( + 14, + 1, + glow::FLOAT, + false, + stride, + 4 * (32 + 4 + 4 + 4 + 4 + 4 + 4), + ); + + // Quad Position + gl.enable_vertex_attrib_array(15); + gl.vertex_attrib_pointer_f32( + 15, + 2, + glow::FLOAT, + false, + stride, + 4 * (32 + 4 + 4 + 4 + 4 + 4 + 4 + 1), + ); + gl.bind_vertex_array(None); - gl.use_program(None); - gl.disable(glow::SCISSOR_TEST); + gl.bind_buffer(glow::ARRAY_BUFFER, None); + gl.bind_buffer(glow::ELEMENT_ARRAY_BUFFER, None); + + (vertex_array, vertex_buffer, index_buffer) } } } -unsafe fn create_buffers( - gl: &glow::Context, - size: usize, -) -> ( - ::VertexArray, - ::Buffer, - ::Buffer, -) { - let vertex_array = gl.create_vertex_array().expect("Create vertex array"); - let vertex_buffer = gl.create_buffer().expect("Create vertex buffer"); - let index_buffer = gl.create_buffer().expect("Create index buffer"); - - gl.bind_vertex_array(Some(vertex_array)); - - gl.bind_buffer(glow::ELEMENT_ARRAY_BUFFER, Some(index_buffer)); - gl.buffer_data_size( - glow::ELEMENT_ARRAY_BUFFER, - 12 * size as i32, - glow::DYNAMIC_DRAW, - ); - - gl.bind_buffer(glow::ARRAY_BUFFER, Some(vertex_buffer)); - gl.buffer_data_size( - glow::ARRAY_BUFFER, - (size * Vertex::SIZE) as i32, - glow::DYNAMIC_DRAW, - ); - - let stride = Vertex::SIZE as i32; - - gl.enable_vertex_attrib_array(0); - gl.vertex_attrib_pointer_f32(0, 2, glow::FLOAT, false, stride, 0); - - gl.enable_vertex_attrib_array(1); - gl.vertex_attrib_pointer_f32(1, 2, glow::FLOAT, false, stride, 4 * 2); - - gl.enable_vertex_attrib_array(2); - gl.vertex_attrib_pointer_f32(2, 4, glow::FLOAT, false, stride, 4 * (2 + 2)); - - gl.enable_vertex_attrib_array(3); - gl.vertex_attrib_pointer_f32( - 3, - 4, - glow::FLOAT, - false, - stride, - 4 * (2 + 2 + 4), - ); - - gl.enable_vertex_attrib_array(4); - gl.vertex_attrib_pointer_f32( - 4, - 4, - glow::FLOAT, - false, - stride, - 4 * (2 + 2 + 4 + 4), - ); - - gl.enable_vertex_attrib_array(5); - gl.vertex_attrib_pointer_f32( - 5, - 1, - glow::FLOAT, - false, - stride, - 4 * (2 + 2 + 4 + 4 + 4), - ); - - gl.enable_vertex_attrib_array(6); - gl.vertex_attrib_pointer_f32( - 6, - 2, - glow::FLOAT, - false, - stride, - 4 * (2 + 2 + 4 + 4 + 4 + 1), - ); - - gl.bind_vertex_array(None); - gl.bind_buffer(glow::ARRAY_BUFFER, None); - gl.bind_buffer(glow::ELEMENT_ARRAY_BUFFER, None); - - (vertex_array, vertex_buffer, index_buffer) -} - /// The vertex of a colored rectangle with a border. /// /// This type can be directly uploaded to GPU memory. -#[derive(Debug, Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)] +//TODO how can we remove this extra wrapper to avoid an additional allocation? +#[derive(Debug, Clone, Copy)] #[repr(C)] -pub struct Vertex { - /// The position of the [`Vertex`]. - pub position: [f32; 2], - - /// The size of the [`Vertex`]. - pub size: [f32; 2], - - /// The color of the [`Vertex`], in __linear RGB__. - pub color: [f32; 4], - - /// The border color of the [`Vertex`], in __linear RGB__. - pub border_color: [f32; 4], - - /// The border radius of the [`Vertex`]. - pub border_radius: [f32; 4], - - /// The border width of the [`Vertex`]. - pub border_width: f32, +struct CompatVertex { + /// THe __quad__ of the [`Vertex`]. + quad: T, /// The __quad__ position of the [`Vertex`]. - pub q_position: [f32; 2], + quad_position: [f32; 2], +} + +unsafe impl bytemuck::Pod + for CompatVertex +{ +} +unsafe impl bytemuck::Zeroable + for CompatVertex +{ } -impl Vertex { +impl CompatVertex { const SIZE: usize = std::mem::size_of::(); - fn from_quad(quad: &layer::Quad) -> [Vertex; 4] { - let base = Vertex { - position: quad.position, - size: quad.size, - color: quad.color, - border_color: quad.color, - border_radius: quad.border_radius, - border_width: quad.border_width, - q_position: [0.0, 0.0], + fn from_quad(quad: &T) -> [Self; 4] { + let base = Self { + quad: *quad, + quad_position: [0.0, 0.0], }; [ base, Self { - q_position: [0.0, 1.0], + quad_position: [0.0, 1.0], ..base }, Self { - q_position: [1.0, 0.0], + quad_position: [1.0, 0.0], ..base }, Self { - q_position: [1.0, 1.0], + quad_position: [1.0, 1.0], ..base }, ] } } + +fn draw( + gl: &glow::Context, + instances: &[T], +) { + // TODO: Remove this allocation (probably by changing the shader and removing the need of two `position`) + let vertices: Vec> = + instances.iter().flat_map(CompatVertex::from_quad).collect(); + + // TODO: Remove this allocation (or allocate only when needed) + let indices: Vec = (0..instances.len().min(MAX_QUADS) as i32) + .flat_map(|i| { + [i * 4, 1 + i * 4, 2 + i * 4, 2 + i * 4, 1 + i * 4, 3 + i * 4] + }) + .cycle() + .take(instances.len() * 6) + .collect(); + + let passes = vertices + .chunks(MAX_VERTICES) + .zip(indices.chunks(MAX_INDICES)); + + for (vertices, indices) in passes { + unsafe { + gl.buffer_sub_data_u8_slice( + glow::ARRAY_BUFFER, + 0, + bytemuck::cast_slice(vertices), + ); + + gl.buffer_sub_data_u8_slice( + glow::ELEMENT_ARRAY_BUFFER, + 0, + bytemuck::cast_slice(indices), + ); + + gl.draw_elements( + glow::TRIANGLES, + indices.len() as i32, + glow::UNSIGNED_INT, + 0, + ); + } + } +} diff --git a/glow/src/quad/core.rs b/glow/src/quad/core.rs index 890365301e..b3649110f8 100644 --- a/glow/src/quad/core.rs +++ b/glow/src/quad/core.rs @@ -1,4 +1,4 @@ -use crate::program::{self, Shader}; +use crate::program; use crate::Transformation; use glow::HasContext; use iced_graphics::layer; @@ -8,58 +8,36 @@ const MAX_INSTANCES: usize = 100_000; #[derive(Debug)] pub struct Pipeline { - program: ::Program, - vertex_array: ::VertexArray, - instances: ::Buffer, + solid: solid::Program, + gradient: gradient::Program, +} + +#[derive(Debug)] +pub struct Uniforms { + transform: Transformation, transform_location: ::UniformLocation, + scale: f32, scale_location: ::UniformLocation, + screen_height: f32, screen_height_location: ::UniformLocation, - current_transform: Transformation, - current_scale: f32, - current_target_height: u32, } -impl Pipeline { - pub fn new( - gl: &glow::Context, - shader_version: &program::Version, - ) -> Pipeline { - let program = unsafe { - let vertex_shader = Shader::vertex( - gl, - shader_version, - include_str!("../shader/core/quad.vert"), - ); - let fragment_shader = Shader::fragment( - gl, - shader_version, - include_str!("../shader/core/quad.frag"), - ); - - program::create( - gl, - &[vertex_shader, fragment_shader], - &[ - (0, "i_Pos"), - (1, "i_Scale"), - (2, "i_Color"), - (3, "i_BorderColor"), - (4, "i_BorderRadius"), - (5, "i_BorderWidth"), - ], - ) - }; +impl Uniforms { + pub fn new(gl: &glow::Context, program: glow::NativeProgram) -> Self { + let transform = Transformation::identity(); + let scale = 1.0; + let screen_height = 0.0; let transform_location = - unsafe { gl.get_uniform_location(program, "u_Transform") } + unsafe { gl.get_uniform_location(program, "u_transform") } .expect("Get transform location"); let scale_location = - unsafe { gl.get_uniform_location(program, "u_Scale") } + unsafe { gl.get_uniform_location(program, "u_scale") } .expect("Get scale location"); let screen_height_location = - unsafe { gl.get_uniform_location(program, "u_ScreenHeight") } + unsafe { gl.get_uniform_location(program, "u_screen_height") } .expect("Get target height location"); unsafe { @@ -68,28 +46,73 @@ impl Pipeline { gl.uniform_matrix_4_f32_slice( Some(&transform_location), false, - Transformation::identity().as_ref(), + transform.as_ref(), ); - gl.uniform_1_f32(Some(&scale_location), 1.0); - gl.uniform_1_f32(Some(&screen_height_location), 0.0); + gl.uniform_1_f32(Some(&scale_location), scale); + gl.uniform_1_f32(Some(&screen_height_location), screen_height); gl.use_program(None); } - let (vertex_array, instances) = - unsafe { create_instance_buffer(gl, MAX_INSTANCES) }; - - Pipeline { - program, - vertex_array, - instances, + Self { + transform, transform_location, + scale, scale_location, + screen_height, screen_height_location, - current_transform: Transformation::identity(), - current_scale: 1.0, - current_target_height: 0, + } + } + + pub fn update( + &mut self, + gl: &glow::Context, + new_transform: Transformation, + new_scale: f32, + new_screen_height: f32, + ) { + if new_transform != self.transform { + unsafe { + gl.uniform_matrix_4_f32_slice( + Some(&self.transform_location), + false, + new_transform.as_ref(), + ); + + self.transform = new_transform; + } + } + + if new_scale != self.scale { + unsafe { + gl.uniform_1_f32(Some(&self.scale_location), new_scale); + } + + self.scale = new_scale; + } + + if new_screen_height != self.screen_height { + unsafe { + gl.uniform_1_f32( + Some(&self.screen_height_location), + new_screen_height, + ); + } + + self.screen_height = new_screen_height; + } + } +} + +impl Pipeline { + pub fn new( + gl: &glow::Context, + shader_version: &program::Version, + ) -> Pipeline { + Pipeline { + solid: solid::Program::new(gl, shader_version), + gradient: gradient::Program::new(gl, shader_version), } } @@ -97,7 +120,7 @@ impl Pipeline { &mut self, gl: &glow::Context, target_height: u32, - instances: &[layer::Quad], + instances: &layer::Quads, transformation: Transformation, scale: f32, bounds: Rectangle, @@ -110,135 +133,427 @@ impl Pipeline { bounds.width as i32, bounds.height as i32, ); + } + + //draw solid instances + self.solid.bind(gl); + self.solid.uniforms.update( + gl, + transformation, + scale, + target_height as f32, + ); + draw_instances(gl, instances.solids.as_slice()); + + //draw gradient instances + self.gradient.bind(gl); + self.gradient.uniforms.update( + gl, + transformation, + scale, + target_height as f32, + ); + draw_instances(gl, instances.gradients.as_slice()); - gl.use_program(Some(self.program)); - gl.bind_vertex_array(Some(self.vertex_array)); - gl.bind_buffer(glow::ARRAY_BUFFER, Some(self.instances)); + unsafe { + gl.bind_vertex_array(None); + gl.use_program(None); + gl.disable(glow::SCISSOR_TEST); } + } +} - if transformation != self.current_transform { - unsafe { - gl.uniform_matrix_4_f32_slice( - Some(&self.transform_location), - false, - transformation.as_ref(), +mod solid { + use crate::program::{self, Shader}; + use crate::quad::core::{Uniforms, MAX_INSTANCES}; + use glow::HasContext; + use iced_graphics::layer::quad; + + #[derive(Debug)] + pub struct Program { + program: glow::Program, + pub uniforms: Uniforms, + pub vertex_array: glow::NativeVertexArray, + pub instances: glow::NativeBuffer, + } + + impl Program { + pub fn new( + gl: &glow::Context, + shader_version: &program::Version, + ) -> Self { + log::info!("GLOW: compiling quad (CORE) solid shaders."); + + let program = unsafe { + let vertex_shader = Shader::vertex( + gl, + shader_version, + [], + include_str!("../shader/quad/core/solid.vert"), + ); + let fragment_shader = Shader::fragment( + gl, + shader_version, + [ + include_str!("../shader/includes/quad_distance.vert"), + include_str!("../shader/includes/border_radius.vert"), + ], + include_str!("../shader/quad/core/solid.frag"), ); - self.current_transform = transformation; + program::create( + gl, + &[vertex_shader, fragment_shader], + &[ + (0, "i_color"), + (1, "i_position"), + (2, "i_size"), + (3, "i_border_color"), + (4, "i_border_radius"), + (5, "i_border_width"), + ], + ) + }; + + let (vertex_array, instances) = + unsafe { Self::create_instance_buffer(gl, MAX_INSTANCES) }; + + Self { + program, + uniforms: Uniforms::new(gl, program), + vertex_array, + instances, } } - if scale != self.current_scale { + pub fn bind(&self, gl: &glow::Context) { unsafe { - gl.uniform_1_f32(Some(&self.scale_location), scale); + gl.use_program(Some(self.program)); + gl.bind_vertex_array(Some(self.vertex_array)); + gl.bind_buffer(glow::ARRAY_BUFFER, Some(self.instances)); } + } + + unsafe fn create_instance_buffer( + gl: &glow::Context, + size: usize, + ) -> ( + ::VertexArray, + ::Buffer, + ) { + let vertex_array = + gl.create_vertex_array().expect("Create vertex array"); + let buffer = gl.create_buffer().expect("Create instance buffer"); + + gl.bind_vertex_array(Some(vertex_array)); + gl.bind_buffer(glow::ARRAY_BUFFER, Some(buffer)); + gl.buffer_data_size( + glow::ARRAY_BUFFER, + (size * std::mem::size_of::()) as i32, + glow::DYNAMIC_DRAW, + ); + + let stride = std::mem::size_of::() as i32; + + // Color + gl.enable_vertex_attrib_array(0); + gl.vertex_attrib_pointer_f32(0, 4, glow::FLOAT, false, stride, 0); + gl.vertex_attrib_divisor(0, 1); + + // Position + gl.enable_vertex_attrib_array(1); + gl.vertex_attrib_pointer_f32( + 1, + 2, + glow::FLOAT, + false, + stride, + 4 * 4, + ); + gl.vertex_attrib_divisor(1, 1); + + // Size + gl.enable_vertex_attrib_array(2); + gl.vertex_attrib_pointer_f32( + 2, + 2, + glow::FLOAT, + false, + stride, + 4 * (4 + 2), + ); + gl.vertex_attrib_divisor(2, 1); + + // Border color + gl.enable_vertex_attrib_array(3); + gl.vertex_attrib_pointer_f32( + 3, + 4, + glow::FLOAT, + false, + stride, + 4 * (4 + 2 + 2), + ); + gl.vertex_attrib_divisor(3, 1); + + // Border radii + gl.enable_vertex_attrib_array(4); + gl.vertex_attrib_pointer_f32( + 4, + 4, + glow::FLOAT, + false, + stride, + 4 * (4 + 2 + 2 + 4), + ); + gl.vertex_attrib_divisor(4, 1); + + // Border width + gl.enable_vertex_attrib_array(5); + gl.vertex_attrib_pointer_f32( + 5, + 1, + glow::FLOAT, + false, + stride, + 4 * (4 + 2 + 2 + 4 + 4), + ); + gl.vertex_attrib_divisor(5, 1); - self.current_scale = scale; + gl.bind_vertex_array(None); + gl.bind_buffer(glow::ARRAY_BUFFER, None); + + (vertex_array, buffer) } + } +} - if target_height != self.current_target_height { - unsafe { - gl.uniform_1_f32( - Some(&self.screen_height_location), - target_height as f32, +mod gradient { + use crate::program::{self, Shader}; + use crate::quad::core::{Uniforms, MAX_INSTANCES}; + use glow::HasContext; + use iced_graphics::layer::quad; + + #[derive(Debug)] + pub struct Program { + program: glow::Program, + pub uniforms: Uniforms, + pub vertex_array: glow::NativeVertexArray, + pub instances: glow::NativeBuffer, + } + + impl Program { + pub fn new( + gl: &glow::Context, + shader_version: &program::Version, + ) -> Self { + log::info!("GLOW: compiling quad gradient shaders."); + + let program = unsafe { + let vertex_shader = Shader::vertex( + gl, + shader_version, + [], + include_str!("../shader/quad/core/gradient.vert"), + ); + let fragment_shader = Shader::fragment( + gl, + shader_version, + [ + include_str!("../shader/includes/gradient.frag"), + include_str!("../shader/includes/quad_distance.vert"), + include_str!("../shader/includes/border_radius.vert"), + ], + include_str!("../shader/quad/core/gradient.frag"), ); - } - self.current_target_height = target_height; + program::create( + gl, + &[vertex_shader, fragment_shader], + &[ + (0, "i_colors_1"), + (1, "i_colors_2"), + (2, "i_colors_3"), + (3, "i_colors_4"), + (4, "i_colors_5"), + (5, "i_colors_6"), + (6, "i_colors_7"), + (7, "i_colors_8"), + (8, "i_offsets_1"), + (9, "i_offsets_2"), + (10, "i_direction"), + (11, "i_position_and_size"), + (12, "i_border_color"), + (13, "i_border_radius"), + (14, "i_border_width"), + ], + ) + }; + + let (vertex_array, instances) = + unsafe { Self::create_instance_buffer(gl, MAX_INSTANCES) }; + + Self { + program, + uniforms: Uniforms::new(gl, program), + vertex_array, + instances, + } } - for instances in instances.chunks(MAX_INSTANCES) { + pub fn bind(&self, gl: &glow::Context) { unsafe { - gl.buffer_sub_data_u8_slice( - glow::ARRAY_BUFFER, - 0, - bytemuck::cast_slice(instances), - ); + gl.use_program(Some(self.program)); + gl.bind_vertex_array(Some(self.vertex_array)); + gl.bind_buffer(glow::ARRAY_BUFFER, Some(self.instances)); + } + } - gl.draw_arrays_instanced( - glow::TRIANGLE_STRIP, - 0, + unsafe fn create_instance_buffer( + gl: &glow::Context, + size: usize, + ) -> ( + ::VertexArray, + ::Buffer, + ) { + let vertex_array = + gl.create_vertex_array().expect("Create vertex array"); + let buffer = gl.create_buffer().expect("Create instance buffer"); + + gl.bind_vertex_array(Some(vertex_array)); + gl.bind_buffer(glow::ARRAY_BUFFER, Some(buffer)); + gl.buffer_data_size( + glow::ARRAY_BUFFER, + (size * std::mem::size_of::()) as i32, + glow::DYNAMIC_DRAW, + ); + + let stride = std::mem::size_of::() as i32; + + // Colors vec4 array[8] (indices 1-8) + for i in 0..=7 { + gl.enable_vertex_attrib_array(i); + gl.vertex_attrib_pointer_f32( + i, 4, - instances.len() as i32, + glow::FLOAT, + false, + stride, + (4 * i * 4) as i32, ); + gl.vertex_attrib_divisor(i, 1); } - } - unsafe { + // Offsets 1-4 + gl.enable_vertex_attrib_array(8); + gl.vertex_attrib_pointer_f32( + 8, + 4, + glow::FLOAT, + false, + stride, + 4 * 32, + ); + gl.vertex_attrib_divisor(8, 1); + + // Offsets 5-8 + gl.enable_vertex_attrib_array(9); + gl.vertex_attrib_pointer_f32( + 9, + 4, + glow::FLOAT, + false, + stride, + 4 * (32 + 4), + ); + gl.vertex_attrib_divisor(9, 1); + + // Direction + gl.enable_vertex_attrib_array(10); + gl.vertex_attrib_pointer_f32( + 10, + 4, + glow::FLOAT, + false, + stride, + 4 * (32 + 4 + 4), + ); + gl.vertex_attrib_divisor(10, 1); + + // Position & Scale + gl.enable_vertex_attrib_array(11); + gl.vertex_attrib_pointer_f32( + 11, + 4, + glow::FLOAT, + false, + stride, + 4 * (32 + 4 + 4 + 4), + ); + gl.vertex_attrib_divisor(11, 1); + + // Border Color + gl.enable_vertex_attrib_array(12); + gl.vertex_attrib_pointer_f32( + 12, + 4, + glow::FLOAT, + false, + stride, + 4 * (32 + 4 + 4 + 4 + 4), + ); + gl.vertex_attrib_divisor(12, 1); + + // Border Radii + gl.enable_vertex_attrib_array(13); + gl.vertex_attrib_pointer_f32( + 13, + 4, + glow::FLOAT, + false, + stride, + 4 * (32 + 4 + 4 + 4 + 4 + 4), + ); + gl.vertex_attrib_divisor(13, 1); + + // Border Width + gl.enable_vertex_attrib_array(14); + gl.vertex_attrib_pointer_f32( + 14, + 1, + glow::FLOAT, + false, + stride, + 4 * (32 + 4 + 4 + 4 + 4 + 4 + 4), + ); + gl.vertex_attrib_divisor(14, 1); + gl.bind_vertex_array(None); - gl.use_program(None); - gl.disable(glow::SCISSOR_TEST); + gl.bind_buffer(glow::ARRAY_BUFFER, None); + + (vertex_array, buffer) } } } -unsafe fn create_instance_buffer( - gl: &glow::Context, - size: usize, -) -> ( - ::VertexArray, - ::Buffer, -) { - let vertex_array = gl.create_vertex_array().expect("Create vertex array"); - let buffer = gl.create_buffer().expect("Create instance buffer"); - - gl.bind_vertex_array(Some(vertex_array)); - gl.bind_buffer(glow::ARRAY_BUFFER, Some(buffer)); - gl.buffer_data_size( - glow::ARRAY_BUFFER, - (size * std::mem::size_of::()) as i32, - glow::DYNAMIC_DRAW, - ); - - let stride = std::mem::size_of::() as i32; - - gl.enable_vertex_attrib_array(0); - gl.vertex_attrib_pointer_f32(0, 2, glow::FLOAT, false, stride, 0); - gl.vertex_attrib_divisor(0, 1); - - gl.enable_vertex_attrib_array(1); - gl.vertex_attrib_pointer_f32(1, 2, glow::FLOAT, false, stride, 4 * 2); - gl.vertex_attrib_divisor(1, 1); - - gl.enable_vertex_attrib_array(2); - gl.vertex_attrib_pointer_f32(2, 4, glow::FLOAT, false, stride, 4 * (2 + 2)); - gl.vertex_attrib_divisor(2, 1); - - gl.enable_vertex_attrib_array(3); - gl.vertex_attrib_pointer_f32( - 3, - 4, - glow::FLOAT, - false, - stride, - 4 * (2 + 2 + 4), - ); - gl.vertex_attrib_divisor(3, 1); - - gl.enable_vertex_attrib_array(4); - gl.vertex_attrib_pointer_f32( - 4, - 4, - glow::FLOAT, - false, - stride, - 4 * (2 + 2 + 4 + 4), - ); - gl.vertex_attrib_divisor(4, 1); - - gl.enable_vertex_attrib_array(5); - gl.vertex_attrib_pointer_f32( - 5, - 1, - glow::FLOAT, - false, - stride, - 4 * (2 + 2 + 4 + 4 + 4), - ); - gl.vertex_attrib_divisor(5, 1); - - gl.bind_vertex_array(None); - gl.bind_buffer(glow::ARRAY_BUFFER, None); - - (vertex_array, buffer) +fn draw_instances(gl: &glow::Context, instances: &[T]) +where + T: bytemuck::Zeroable + bytemuck::Pod, +{ + for instances_chunk in instances.chunks(MAX_INSTANCES) { + unsafe { + gl.buffer_sub_data_u8_slice( + glow::ARRAY_BUFFER, + 0, + bytemuck::cast_slice(instances_chunk), + ); + + gl.draw_arrays_instanced( + glow::TRIANGLE_STRIP, + 0, + 4, + instances_chunk.len() as i32, + ); + } + } } diff --git a/glow/src/shader/common/gradient.frag b/glow/src/shader/common/gradient.frag deleted file mode 100644 index 9af0cb6e36..0000000000 --- a/glow/src/shader/common/gradient.frag +++ /dev/null @@ -1,59 +0,0 @@ -#ifdef GL_ES -#ifdef GL_FRAGMENT_PRECISION_HIGH -precision highp float; -#else -precision mediump float; -#endif -#endif - -#ifdef HIGHER_THAN_300 -layout (location = 0) out vec4 fragColor; -#define gl_FragColor fragColor -#endif - -in vec2 raw_position; - -uniform vec4 gradient_direction; -uniform int color_stops_size; -// GLSL does not support dynamically sized arrays without SSBOs so this is capped to 16 stops -//stored as color(vec4) -> offset(vec4) sequentially; -uniform vec4 color_stops[32]; - -//TODO: rewrite without branching to make ALUs happy -void main() { - vec2 start = gradient_direction.xy; - vec2 end = gradient_direction.zw; - vec2 gradient_vec = vec2(end - start); - vec2 current_vec = vec2(raw_position.xy - start); - vec2 unit = normalize(gradient_vec); - float coord_offset = dot(unit, current_vec) / length(gradient_vec); - //if a gradient has a start/end stop that is identical, the mesh will have a transparent fill - gl_FragColor = vec4(0.0, 0.0, 0.0, 0.0); - - float min_offset = color_stops[1].x; - float max_offset = color_stops[color_stops_size - 1].x; - - for (int i = 0; i < color_stops_size - 2; i += 2) { - float curr_offset = color_stops[i+1].x; - float next_offset = color_stops[i+3].x; - - if (coord_offset <= min_offset) { - //current coordinate is before the first defined offset, set it to the start color - gl_FragColor = color_stops[0]; - } - - if (curr_offset <= coord_offset && coord_offset <= next_offset) { - //current fragment is between the current offset processing & the next one, interpolate colors - gl_FragColor = mix(color_stops[i], color_stops[i+2], smoothstep( - curr_offset, - next_offset, - coord_offset - )); - } - - if (coord_offset >= max_offset) { - //current coordinate is before the last defined offset, set it to the last color - gl_FragColor = color_stops[color_stops_size - 2]; - } - } -} diff --git a/glow/src/shader/common/gradient.vert b/glow/src/shader/common/gradient.vert deleted file mode 100644 index fe505997c6..0000000000 --- a/glow/src/shader/common/gradient.vert +++ /dev/null @@ -1,9 +0,0 @@ -uniform mat4 u_Transform; - -in vec2 i_Position; -out vec2 raw_position; - -void main() { - gl_Position = u_Transform * vec4(i_Position, 0.0, 1.0); - raw_position = i_Position; -} diff --git a/glow/src/shader/common/solid.vert b/glow/src/shader/common/solid.vert deleted file mode 100644 index 59ed88e596..0000000000 --- a/glow/src/shader/common/solid.vert +++ /dev/null @@ -1,11 +0,0 @@ -uniform mat4 u_Transform; - -in vec2 i_Position; -in vec4 i_Color; - -out vec4 v_Color; - -void main() { - gl_Position = u_Transform * vec4(i_Position, 0.0, 1.0); - v_Color = i_Color; -} diff --git a/glow/src/shader/compatibility/quad.frag b/glow/src/shader/compatibility/quad.frag deleted file mode 100644 index bb9d8122d1..0000000000 --- a/glow/src/shader/compatibility/quad.frag +++ /dev/null @@ -1,83 +0,0 @@ -#ifdef GL_ES -#ifdef GL_FRAGMENT_PRECISION_HIGH -precision highp float; -#else -precision mediump float; -#endif -#endif - -uniform float u_ScreenHeight; - -varying vec4 v_Color; -varying vec4 v_BorderColor; -varying vec2 v_Pos; -varying vec2 v_Scale; -varying vec4 v_BorderRadius; -varying float v_BorderWidth; - -float _distance(vec2 frag_coord, vec2 position, vec2 size, float radius) -{ - // TODO: Try SDF approach: https://www.shadertoy.com/view/wd3XRN - 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.0), - max(max(top_left_distance.y, bottom_right_distance.y), 0.0) - ); - - return sqrt(distance.x * distance.x + distance.y * distance.y); -} - -float selectBorderRadius(vec4 radi, vec2 position, vec2 center) -{ - float rx = radi.x; - float ry = radi.y; - rx = position.x > center.x ? radi.y : radi.x; - ry = position.x > center.x ? radi.z : radi.w; - rx = position.y > center.y ? ry : rx; - return rx; -} - -void main() { - vec2 fragCoord = vec2(gl_FragCoord.x, u_ScreenHeight - gl_FragCoord.y); - - float border_radius = selectBorderRadius( - v_BorderRadius, - fragCoord, - (v_Pos + v_Scale * 0.5).xy - ); - - float internal_border = max(border_radius - v_BorderWidth, 0.0); - - float internal_distance = _distance( - fragCoord, - v_Pos + vec2(v_BorderWidth), - v_Scale - vec2(v_BorderWidth * 2.0), - internal_border - ); - - float border_mix = smoothstep( - max(internal_border - 0.5, 0.0), - internal_border + 0.5, - internal_distance - ); - - vec4 mixed_color = mix(v_Color, v_BorderColor, border_mix); - - float d = _distance( - fragCoord, - v_Pos, - v_Scale, - border_radius - ); - - float radius_alpha = - 1.0 - smoothstep(max(border_radius - 0.5, 0.0), border_radius + 0.5, d); - - gl_FragColor = vec4(mixed_color.xyz, mixed_color.w * radius_alpha); -} diff --git a/glow/src/shader/compatibility/quad.vert b/glow/src/shader/compatibility/quad.vert deleted file mode 100644 index 89931f0671..0000000000 --- a/glow/src/shader/compatibility/quad.vert +++ /dev/null @@ -1,46 +0,0 @@ -uniform mat4 u_Transform; -uniform float u_Scale; - -attribute vec2 i_Pos; -attribute vec2 i_Scale; -attribute vec4 i_Color; -attribute vec4 i_BorderColor; -attribute vec4 i_BorderRadius; -attribute float i_BorderWidth; -attribute vec2 q_Pos; - -varying vec4 v_Color; -varying vec4 v_BorderColor; -varying vec2 v_Pos; -varying vec2 v_Scale; -varying vec4 v_BorderRadius; -varying float v_BorderWidth; - - -void main() { - vec2 p_Pos = i_Pos * u_Scale; - vec2 p_Scale = i_Scale * u_Scale; - - vec4 i_BorderRadius = vec4( - min(i_BorderRadius.x, min(i_Scale.x, i_Scale.y) / 2.0), - min(i_BorderRadius.y, min(i_Scale.x, i_Scale.y) / 2.0), - min(i_BorderRadius.z, min(i_Scale.x, i_Scale.y) / 2.0), - min(i_BorderRadius.w, min(i_Scale.x, i_Scale.y) / 2.0) - ); - - mat4 i_Transform = mat4( - vec4(p_Scale.x + 1.0, 0.0, 0.0, 0.0), - vec4(0.0, p_Scale.y + 1.0, 0.0, 0.0), - vec4(0.0, 0.0, 1.0, 0.0), - vec4(p_Pos - vec2(0.5, 0.5), 0.0, 1.0) - ); - - v_Color = i_Color; - v_BorderColor = i_BorderColor; - v_Pos = p_Pos; - v_Scale = p_Scale; - v_BorderRadius = i_BorderRadius * u_Scale; - v_BorderWidth = i_BorderWidth * u_Scale; - - gl_Position = u_Transform * i_Transform * vec4(q_Pos, 0.0, 1.0); -} diff --git a/glow/src/shader/core/quad.frag b/glow/src/shader/core/quad.frag deleted file mode 100644 index 71147aa51d..0000000000 --- a/glow/src/shader/core/quad.frag +++ /dev/null @@ -1,95 +0,0 @@ -#ifdef GL_ES -#ifdef GL_FRAGMENT_PRECISION_HIGH -precision highp float; -#else -precision mediump float; -#endif -#endif - -#ifdef HIGHER_THAN_300 -out vec4 fragColor; -#define gl_FragColor fragColor -#endif - -uniform float u_ScreenHeight; - -in vec4 v_Color; -in vec4 v_BorderColor; -in vec2 v_Pos; -in vec2 v_Scale; -in vec4 v_BorderRadius; -in float v_BorderWidth; - -float fDistance(vec2 frag_coord, vec2 position, vec2 size, float radius) -{ - // TODO: Try SDF approach: https://www.shadertoy.com/view/wd3XRN - 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.0), - max(max(top_left_distance.y, bottom_right_distance.y), 0.0) - ); - - return sqrt(distance.x * distance.x + distance.y * distance.y); -} - -float selectBorderRadius(vec4 radi, vec2 position, vec2 center) -{ - float rx = radi.x; - float ry = radi.y; - rx = position.x > center.x ? radi.y : radi.x; - ry = position.x > center.x ? radi.z : radi.w; - rx = position.y > center.y ? ry : rx; - return rx; -} - -void main() { - vec4 mixed_color; - - vec2 fragCoord = vec2(gl_FragCoord.x, u_ScreenHeight - gl_FragCoord.y); - - float border_radius = selectBorderRadius( - v_BorderRadius, - fragCoord, - (v_Pos + v_Scale * 0.5).xy - ); - - // TODO: Remove branching (?) - if(v_BorderWidth > 0.0) { - float internal_border = max(border_radius - v_BorderWidth, 0.0); - - float internal_distance = fDistance( - fragCoord, - v_Pos + vec2(v_BorderWidth), - v_Scale - vec2(v_BorderWidth * 2.0), - internal_border - ); - - float border_mix = smoothstep( - max(internal_border - 0.5, 0.0), - internal_border + 0.5, - internal_distance - ); - - mixed_color = mix(v_Color, v_BorderColor, border_mix); - } else { - mixed_color = v_Color; - } - - float d = fDistance( - fragCoord, - v_Pos, - v_Scale, - border_radius - ); - - float radius_alpha = - 1.0 - smoothstep(max(border_radius - 0.5, 0.0), border_radius + 0.5, d); - - gl_FragColor = vec4(mixed_color.xyz, mixed_color.w * radius_alpha); -} diff --git a/glow/src/shader/core/quad.vert b/glow/src/shader/core/quad.vert deleted file mode 100644 index 17c3e64191..0000000000 --- a/glow/src/shader/core/quad.vert +++ /dev/null @@ -1,52 +0,0 @@ -uniform mat4 u_Transform; -uniform float u_Scale; - -in vec2 i_Pos; -in vec2 i_Scale; -in vec4 i_Color; -in vec4 i_BorderColor; -in vec4 i_BorderRadius; -in float i_BorderWidth; - -out vec4 v_Color; -out vec4 v_BorderColor; -out vec2 v_Pos; -out vec2 v_Scale; -out vec4 v_BorderRadius; -out float v_BorderWidth; - -vec2 positions[4] = vec2[]( - vec2(0.0, 0.0), - vec2(0.0, 1.0), - vec2(1.0, 0.0), - vec2(1.0, 1.0) -); - -void main() { - vec2 q_Pos = positions[gl_VertexID]; - vec2 p_Pos = i_Pos * u_Scale; - vec2 p_Scale = i_Scale * u_Scale; - - vec4 i_BorderRadius = vec4( - min(i_BorderRadius.x, min(i_Scale.x, i_Scale.y) / 2.0), - min(i_BorderRadius.y, min(i_Scale.x, i_Scale.y) / 2.0), - min(i_BorderRadius.z, min(i_Scale.x, i_Scale.y) / 2.0), - min(i_BorderRadius.w, min(i_Scale.x, i_Scale.y) / 2.0) - ); - - mat4 i_Transform = mat4( - vec4(p_Scale.x + 1.0, 0.0, 0.0, 0.0), - vec4(0.0, p_Scale.y + 1.0, 0.0, 0.0), - vec4(0.0, 0.0, 1.0, 0.0), - vec4(p_Pos - vec2(0.5, 0.5), 0.0, 1.0) - ); - - v_Color = i_Color; - v_BorderColor = i_BorderColor; - v_Pos = p_Pos; - v_Scale = p_Scale; - v_BorderRadius = i_BorderRadius * u_Scale; - v_BorderWidth = i_BorderWidth * u_Scale; - - gl_Position = u_Transform * i_Transform * vec4(q_Pos, 0.0, 1.0); -} diff --git a/glow/src/shader/includes/border_radius.vert b/glow/src/shader/includes/border_radius.vert new file mode 100644 index 0000000000..891c65e220 --- /dev/null +++ b/glow/src/shader/includes/border_radius.vert @@ -0,0 +1,9 @@ +float selectBorderRadius(vec4 radi, vec2 position, vec2 center) +{ + float rx = radi.x; + float ry = radi.y; + rx = position.x > center.x ? radi.y : radi.x; + ry = position.x > center.x ? radi.z : radi.w; + rx = position.y > center.y ? ry : rx; + return rx; +} diff --git a/glow/src/shader/includes/gradient.frag b/glow/src/shader/includes/gradient.frag new file mode 100644 index 0000000000..ff16ecbff6 --- /dev/null +++ b/glow/src/shader/includes/gradient.frag @@ -0,0 +1,61 @@ +vec4 gradient( + vec4 direction, + vec2 raw_position, + vec4 offsets[2], + vec4 colors[8] +) { + vec2 start = direction.xy; + vec2 end = direction.zw; + + vec2 v1 = vec2(end - start); + vec2 v2 = vec2(raw_position - start); + vec2 unit = normalize(v1); + float coord_offset = dot(unit, v2) / length(v1); + + //if a gradient has a start/end stop that is identical, the mesh will have a transparent fill + vec4 color; + + float offsets_arr[8]; + offsets_arr[0] = offsets[0].x; + offsets_arr[1] = offsets[0].y; + offsets_arr[2] = offsets[0].z; + offsets_arr[3] = offsets[0].w; + offsets_arr[4] = offsets[1].x; + offsets_arr[5] = offsets[1].y; + offsets_arr[6] = offsets[1].z; + offsets_arr[7] = offsets[1].w; + + int last_index = 7; + for (int i = 0; i <= 7; i++) { + if (offsets_arr[i] > 1.0) { + last_index = i - 1; + break; + } + } + + for (int i = 0; i < last_index; i++) { + float curr_offset = offsets_arr[i]; + float next_offset = offsets_arr[i+1]; + + if (coord_offset <= offsets_arr[0]) { + //current coordinate is before the first defined offset, set it to the start color + color = colors[0]; + } + + if (curr_offset <= coord_offset && coord_offset <= next_offset) { + //current fragment is between the current offset & the next one, interpolate colors + color = mix(colors[i], colors[i+1], smoothstep( + curr_offset, + next_offset, + coord_offset + )); + } + + if (coord_offset >= offsets_arr[last_index]) { + //current coordinate is after the last defined offset, set it to the last color + color = colors[last_index]; + } + } + + return color; +} diff --git a/glow/src/shader/includes/quad_distance.vert b/glow/src/shader/includes/quad_distance.vert new file mode 100644 index 0000000000..54e648148f --- /dev/null +++ b/glow/src/shader/includes/quad_distance.vert @@ -0,0 +1,17 @@ +float fDistance(vec2 frag_coord, vec2 position, vec2 size, float radius) +{ + // TODO: Try SDF approach: https://www.shadertoy.com/view/wd3XRN + 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.0), + max(max(top_left_distance.y, bottom_right_distance.y), 0.0) + ); + + return sqrt(distance.x * distance.x + distance.y * distance.y); +} diff --git a/glow/src/shader/quad/compatibility/gradient.frag b/glow/src/shader/quad/compatibility/gradient.frag new file mode 100644 index 0000000000..1f79b1cd75 --- /dev/null +++ b/glow/src/shader/quad/compatibility/gradient.frag @@ -0,0 +1,64 @@ +#ifdef GL_ES +#ifdef GL_FRAGMENT_PRECISION_HIGH +precision highp float; +#else +precision mediump float; +#endif +#endif + +// #includes + +uniform float u_screen_height; + +varying vec4 v_colors[8]; +varying vec4 v_offsets[2]; +varying vec4 v_direction; +varying vec4 v_position_and_size; +varying vec4 v_border_color; +varying vec4 v_border_radius; +varying float v_border_width; + +void main() { + vec2 fragCoord = vec2(gl_FragCoord.x, u_screen_height - gl_FragCoord.y); + + vec2 v_position = v_position_and_size.xy; + vec2 v_size = v_position_and_size.zw; + + float border_radius = selectBorderRadius( + v_border_radius, + fragCoord, + (v_position + v_size * 0.5).xy + ); + + float internal_border = max(border_radius - v_border_width, 0.0); + + float internal_distance = fDistance( + fragCoord, + v_position + vec2(v_border_width), + v_size - vec2(v_border_width * 2.0), + internal_border + ); + + float border_mix = smoothstep( + max(internal_border - 0.5, 0.0), + internal_border + 0.5, + internal_distance + ); + + vec4 mixed_color = mix( + gradient(v_direction, fragCoord, v_offsets, v_colors), + v_border_color, + border_mix + ); + + float d = fDistance( + fragCoord, + v_position, + v_size, + border_radius + ); + + float radius_alpha = 1.0 - smoothstep(max(border_radius - 0.5, 0.0), border_radius + 0.5, d); + + gl_FragColor = vec4(mixed_color.xyz, mixed_color.w * radius_alpha); +} diff --git a/glow/src/shader/quad/compatibility/gradient.vert b/glow/src/shader/quad/compatibility/gradient.vert new file mode 100644 index 0000000000..49696be2a7 --- /dev/null +++ b/glow/src/shader/quad/compatibility/gradient.vert @@ -0,0 +1,72 @@ +uniform mat4 u_transform; +uniform float u_scale; + +//gradient +attribute vec4 i_colors_1; +attribute vec4 i_colors_2; +attribute vec4 i_colors_3; +attribute vec4 i_colors_4; +attribute vec4 i_colors_5; +attribute vec4 i_colors_6; +attribute vec4 i_colors_7; +attribute vec4 i_colors_8; +attribute vec4 i_offsets_1; +attribute vec4 i_offsets_2; +attribute vec4 i_direction; +//quad properties +attribute vec4 i_position_and_size; +attribute vec4 i_border_color; +attribute vec4 i_border_radius; +attribute float i_border_width; +attribute vec2 i_quad_position; + +varying vec4 v_colors[8]; +varying vec4 v_offsets[2]; +varying vec4 v_direction; +varying vec4 v_position_and_size; +varying vec4 v_border_color; +varying vec4 v_border_radius; +varying float v_border_width; + +void main() { + vec2 i_position = i_position_and_size.xy; + vec2 i_size = i_position_and_size.zw; + + vec2 p_position = i_position * u_scale; + vec2 p_size = i_size * u_scale; + + vec4 i_border_radius = vec4( + min(i_border_radius.x, min(i_size.x, i_size.y) / 2.0), + min(i_border_radius.y, min(i_size.x, i_size.y) / 2.0), + min(i_border_radius.z, min(i_size.x, i_size.y) / 2.0), + min(i_border_radius.w, min(i_size.x, i_size.y) / 2.0) + ); + + mat4 i_transform = mat4( + vec4(p_size.x + 1.0, 0.0, 0.0, 0.0), + vec4(0.0, p_size.y + 1.0, 0.0, 0.0), + vec4(0.0, 0.0, 1.0, 0.0), + vec4(p_position - vec2(0.5, 0.5), 0.0, 1.0) + ); + + // array initializers are not supported in GLSL 1.0 (ES 2.0) + v_colors[0] = i_colors_1; + v_colors[1] = i_colors_2; + v_colors[2] = i_colors_3; + v_colors[3] = i_colors_4; + v_colors[4] = i_colors_5; + v_colors[5] = i_colors_6; + v_colors[6] = i_colors_7; + v_colors[7] = i_colors_8; + + v_offsets[0] = i_offsets_1; + v_offsets[1] = i_offsets_2; + + v_direction = i_direction * u_scale; + v_position_and_size = vec4(p_position, p_size); + v_border_color = i_border_color; + v_border_radius = i_border_radius * u_scale; + v_border_width = i_border_width * u_scale; + + gl_Position = u_transform * i_transform * vec4(i_quad_position, 0.0, 1.0); +} diff --git a/glow/src/shader/quad/compatibility/solid.frag b/glow/src/shader/quad/compatibility/solid.frag new file mode 100644 index 0000000000..202ec9c620 --- /dev/null +++ b/glow/src/shader/quad/compatibility/solid.frag @@ -0,0 +1,56 @@ +#ifdef GL_ES +#ifdef GL_FRAGMENT_PRECISION_HIGH +precision highp float; +#else +precision mediump float; +#endif +#endif + +// #includes + +uniform float u_screen_height; + +varying vec4 v_color; +varying vec4 v_border_color; +varying vec2 v_position; +varying vec2 v_size; +varying vec4 v_border_radius; +varying float v_border_width; + +void main() { + vec2 fragCoord = vec2(gl_FragCoord.x, u_screen_height - gl_FragCoord.y); + + float border_radius = selectBorderRadius( + v_border_radius, + fragCoord, + (v_position + v_size * 0.5).xy + ); + + float internal_border = max(border_radius - v_border_width, 0.0); + + float internal_distance = fDistance( + fragCoord, + v_position + vec2(v_border_width), + v_size - vec2(v_border_width * 2.0), + internal_border + ); + + float border_mix = smoothstep( + max(internal_border - 0.5, 0.0), + internal_border + 0.5, + internal_distance + ); + + vec4 mixed_color = mix(v_color, v_border_color, border_mix); + + float d = fDistance( + fragCoord, + v_position, + v_size, + border_radius + ); + + float radius_alpha = 1.0 - smoothstep(max(border_radius - 0.5, 0.0), border_radius + 0.5, d); + + gl_FragColor = vec4(mixed_color.xyz, mixed_color.w * radius_alpha); +} diff --git a/glow/src/shader/quad/compatibility/solid.vert b/glow/src/shader/quad/compatibility/solid.vert new file mode 100644 index 0000000000..bbac512eb6 --- /dev/null +++ b/glow/src/shader/quad/compatibility/solid.vert @@ -0,0 +1,45 @@ +uniform mat4 u_transform; +uniform float u_scale; + +attribute vec4 i_color; +attribute vec2 i_position; +attribute vec2 i_size; +attribute vec4 i_border_color; +attribute vec4 i_border_radius; +attribute float i_border_width; +attribute vec2 i_quad_position; + +varying vec4 v_color; +varying vec2 v_position; +varying vec2 v_size; +varying vec4 v_border_color; +varying vec4 v_border_radius; +varying float v_border_width; + +void main() { + vec2 p_position = i_position * u_scale; + vec2 p_size = i_size * u_scale; + + vec4 i_border_radius = vec4( + min(i_border_radius.x, min(i_size.x, i_size.y) / 2.0), + min(i_border_radius.y, min(i_size.x, i_size.y) / 2.0), + min(i_border_radius.z, min(i_size.x, i_size.y) / 2.0), + min(i_border_radius.w, min(i_size.x, i_size.y) / 2.0) + ); + + mat4 i_transform = mat4( + vec4(p_size.x + 1.0, 0.0, 0.0, 0.0), + vec4(0.0, p_size.y + 1.0, 0.0, 0.0), + vec4(0.0, 0.0, 1.0, 0.0), + vec4(p_position - vec2(0.5, 0.5), 0.0, 1.0) + ); + + v_color = i_color; + v_border_color = i_border_color; + v_position = p_position; + v_size = p_size; + v_border_radius = i_border_radius * u_scale; + v_border_width = i_border_width * u_scale; + + gl_Position = u_transform * i_transform * vec4(i_quad_position, 0.0, 1.0); +} diff --git a/glow/src/shader/quad/core/gradient.frag b/glow/src/shader/quad/core/gradient.frag new file mode 100644 index 0000000000..26d85ee0b8 --- /dev/null +++ b/glow/src/shader/quad/core/gradient.frag @@ -0,0 +1,69 @@ +#ifdef GL_ES +#ifdef GL_FRAGMENT_PRECISION_HIGH +precision highp float; +#else +precision mediump float; +#endif +#endif + +#ifdef HIGHER_THAN_300 +out vec4 fragColor; +#define gl_FragColor fragColor +#endif + +// #includes + +uniform float u_screen_height; + +in vec4 v_colors[8]; +in vec4 v_offsets[2]; +in vec4 v_direction; +in vec4 v_position_and_size; +in vec4 v_border_color; +in vec4 v_border_radius; +in float v_border_width; + +void main() { + vec2 v_position = v_position_and_size.xy; + vec2 v_size = v_position_and_size.zw; + + vec2 fragCoord = vec2(gl_FragCoord.x, u_screen_height - gl_FragCoord.y); + + vec4 mixed_color = gradient(v_direction, fragCoord, v_offsets, v_colors); + + float border_radius = selectBorderRadius( + v_border_radius, + fragCoord, + (v_position + v_size * 0.5).xy + ); + + if (v_border_width > 0.0) { + float internal_border = max(border_radius - v_border_width, 0.0); + + float internal_distance = fDistance( + fragCoord, + v_position + vec2(v_border_width), + v_size - vec2(v_border_width * 2.0), + internal_border + ); + + float border_mix = smoothstep( + max(internal_border - 0.5, 0.0), + internal_border + 0.5, + internal_distance + ); + + mixed_color = mix(mixed_color, v_border_color, border_mix); + } + + float d = fDistance( + fragCoord, + v_position, + v_size, + border_radius + ); + + float radius_alpha = 1.0 - smoothstep(max(border_radius - 0.5, 0.0), border_radius + 0.5, d); + + gl_FragColor = vec4(mixed_color.xyz, mixed_color.w * radius_alpha); +} diff --git a/glow/src/shader/quad/core/gradient.vert b/glow/src/shader/quad/core/gradient.vert new file mode 100644 index 0000000000..c003889370 --- /dev/null +++ b/glow/src/shader/quad/core/gradient.vert @@ -0,0 +1,80 @@ +uniform mat4 u_transform; +uniform float u_scale; + +in vec4 i_colors_1; +in vec4 i_colors_2; +in vec4 i_colors_3; +in vec4 i_colors_4; +in vec4 i_colors_5; +in vec4 i_colors_6; +in vec4 i_colors_7; +in vec4 i_colors_8; +in vec4 i_offsets_1; +in vec4 i_offsets_2; +in vec4 i_direction; +in vec4 i_position_and_size; +in vec4 i_border_color; +in vec4 i_border_radius; +in float i_border_width; + +out vec4 v_colors[8]; +out vec4 v_offsets[2]; +out vec4 v_direction; +out vec4 v_position_and_size; +out vec4 v_border_color; +out vec4 v_border_radius; +out float v_border_width; + +vec2 positions[4] = vec2[]( + vec2(0.0, 0.0), + vec2(0.0, 1.0), + vec2(1.0, 0.0), + vec2(1.0, 1.0) +); + +void main() { + vec2 i_position = i_position_and_size.xy; + vec2 i_size = i_position_and_size.zw; + + vec2 q_position = positions[gl_VertexID]; + vec2 p_position = i_position * u_scale; + vec2 p_size = i_size * u_scale; + + vec4 i_border_radius = vec4( + min(i_border_radius.x, min(i_size.x, i_size.y) / 2.0), + min(i_border_radius.y, min(i_size.x, i_size.y) / 2.0), + min(i_border_radius.z, min(i_size.x, i_size.y) / 2.0), + min(i_border_radius.w, min(i_size.x, i_size.y) / 2.0) + ); + + mat4 i_transform = mat4( + vec4(p_size.x + 1.0, 0.0, 0.0, 0.0), + vec4(0.0, p_size.y + 1.0, 0.0, 0.0), + vec4(0.0, 0.0, 1.0, 0.0), + vec4(p_position - vec2(0.5, 0.5), 0.0, 1.0) + ); + + v_colors = vec4[]( + i_colors_1, + i_colors_2, + i_colors_3, + i_colors_4, + i_colors_5, + i_colors_6, + i_colors_7, + i_colors_8 + ); + + v_offsets = vec4[]( + i_offsets_1, + i_offsets_2 + ); + + v_direction = i_direction * u_scale; + v_position_and_size = vec4(p_position, p_size); + v_border_color = i_border_color; + v_border_radius = i_border_radius * u_scale; + v_border_width = i_border_width * u_scale; + + gl_Position = u_transform * i_transform * vec4(q_position, 0.0, 1.0); +} diff --git a/glow/src/shader/quad/core/solid.frag b/glow/src/shader/quad/core/solid.frag new file mode 100644 index 0000000000..1377e42bd7 --- /dev/null +++ b/glow/src/shader/quad/core/solid.frag @@ -0,0 +1,67 @@ +#ifdef GL_ES +#ifdef GL_FRAGMENT_PRECISION_HIGH +precision highp float; +#else +precision mediump float; +#endif +#endif + +#ifdef HIGHER_THAN_300 +out vec4 fragColor; +#define gl_FragColor fragColor +#endif + +// #includes + +uniform float u_screen_height; + +in vec4 v_color; +in vec4 v_border_color; +in vec2 v_position; +in vec2 v_scale; +in vec4 v_border_radius; +in float v_border_width; + +void main() { + vec4 mixed_color; + + vec2 fragCoord = vec2(gl_FragCoord.x, u_screen_height - gl_FragCoord.y); + + float border_radius = selectBorderRadius( + v_border_radius, + fragCoord, + (v_position + v_scale * 0.5).xy + ); + + if (v_border_width > 0.0) { + float internal_border = max(border_radius - v_border_width, 0.0); + + float internal_distance = fDistance( + fragCoord, + v_position + vec2(v_border_width), + v_scale - vec2(v_border_width * 2.0), + internal_border + ); + + float border_mix = smoothstep( + max(internal_border - 0.5, 0.0), + internal_border + 0.5, + internal_distance + ); + + mixed_color = mix(v_color, v_border_color, border_mix); + } else { + mixed_color = v_color; + } + + float d = fDistance( + fragCoord, + v_position, + v_scale, + border_radius + ); + + float radius_alpha = 1.0 - smoothstep(max(border_radius - 0.5, 0.0), border_radius + 0.5, d); + + gl_FragColor = vec4(mixed_color.xyz, mixed_color.w * radius_alpha); +} diff --git a/glow/src/shader/quad/core/solid.vert b/glow/src/shader/quad/core/solid.vert new file mode 100644 index 0000000000..2e5f1dd207 --- /dev/null +++ b/glow/src/shader/quad/core/solid.vert @@ -0,0 +1,52 @@ +uniform mat4 u_transform; +uniform float u_scale; + +in vec4 i_color; +in vec2 i_position; +in vec2 i_size; +in vec4 i_border_color; +in vec4 i_border_radius; +in float i_border_width; + +out vec4 v_color; +out vec4 v_border_color; +out vec2 v_position; +out vec2 v_scale; +out vec4 v_border_radius; +out float v_border_width; + +vec2 positions[4] = vec2[]( + vec2(0.0, 0.0), + vec2(0.0, 1.0), + vec2(1.0, 0.0), + vec2(1.0, 1.0) +); + +void main() { + vec2 q_position = positions[gl_VertexID]; + vec2 p_position = i_position * u_scale; + vec2 p_scale = i_size * u_scale; + + vec4 i_BorderRadius = vec4( + min(i_border_radius.x, min(i_size.x, i_size.y) / 2.0), + min(i_border_radius.y, min(i_size.x, i_size.y) / 2.0), + min(i_border_radius.z, min(i_size.x, i_size.y) / 2.0), + min(i_border_radius.w, min(i_size.x, i_size.y) / 2.0) + ); + + mat4 i_transform = mat4( + vec4(p_scale.x + 1.0, 0.0, 0.0, 0.0), + vec4(0.0, p_scale.y + 1.0, 0.0, 0.0), + vec4(0.0, 0.0, 1.0, 0.0), + vec4(p_position - vec2(0.5, 0.5), 0.0, 1.0) + ); + + v_color = i_color; + v_border_color = i_border_color; + v_position = p_position; + v_scale = p_scale; + v_border_radius = i_border_radius * u_scale; + v_border_width = i_border_width * u_scale; + + gl_Position = u_transform * i_transform * vec4(q_position, 0.0, 1.0); +} diff --git a/glow/src/shader/triangle/gradient.frag b/glow/src/shader/triangle/gradient.frag new file mode 100644 index 0000000000..9e56854098 --- /dev/null +++ b/glow/src/shader/triangle/gradient.frag @@ -0,0 +1,23 @@ +#ifdef GL_ES +#ifdef GL_FRAGMENT_PRECISION_HIGH +precision highp float; +#else +precision mediump float; +#endif +#endif + +#ifdef HIGHER_THAN_300 +out vec4 fragColor; +#define gl_FragColor fragColor +#endif + +// #includes + +in vec2 v_raw_position; +in vec4 v_colors[8]; +in vec4 v_offsets[2]; +in vec4 v_direction; + +void main() { + gl_FragColor = gradient(v_direction, v_raw_position, v_offsets, v_colors); +} diff --git a/glow/src/shader/triangle/gradient.vert b/glow/src/shader/triangle/gradient.vert new file mode 100644 index 0000000000..ab8c59f330 --- /dev/null +++ b/glow/src/shader/triangle/gradient.vert @@ -0,0 +1,42 @@ +uniform mat4 u_transform; + +in vec2 i_position; +in vec4 i_colors_1; +in vec4 i_colors_2; +in vec4 i_colors_3; +in vec4 i_colors_4; +in vec4 i_colors_5; +in vec4 i_colors_6; +in vec4 i_colors_7; +in vec4 i_colors_8; +in vec4 i_offsets_1; +in vec4 i_offsets_2; +in vec4 i_direction; + +out vec2 v_raw_position; +out vec4 v_colors[8]; +out vec4 v_offsets[2]; +out vec4 v_direction; + +void main() { + gl_Position = u_transform * vec4(i_position, 0.0, 1.0); + + v_raw_position = i_position; + + v_colors[0] = i_colors_1; + + // array initializers are not supported in GLSL 1.0 (ES 2.0) + v_colors[0] = i_colors_1; + v_colors[1] = i_colors_2; + v_colors[2] = i_colors_3; + v_colors[3] = i_colors_4; + v_colors[4] = i_colors_5; + v_colors[5] = i_colors_6; + v_colors[6] = i_colors_7; + v_colors[7] = i_colors_8; + + v_offsets[0] = i_offsets_1; + v_offsets[1] = i_offsets_2; + + v_direction = i_direction; +} diff --git a/glow/src/shader/common/image.frag b/glow/src/shader/triangle/image.frag similarity index 100% rename from glow/src/shader/common/image.frag rename to glow/src/shader/triangle/image.frag diff --git a/glow/src/shader/common/image.vert b/glow/src/shader/triangle/image.vert similarity index 100% rename from glow/src/shader/common/image.vert rename to glow/src/shader/triangle/image.vert diff --git a/glow/src/shader/common/solid.frag b/glow/src/shader/triangle/solid.frag similarity index 82% rename from glow/src/shader/common/solid.frag rename to glow/src/shader/triangle/solid.frag index 174ffdd3e0..ea8b8a6f87 100644 --- a/glow/src/shader/common/solid.frag +++ b/glow/src/shader/triangle/solid.frag @@ -11,8 +11,8 @@ out vec4 fragColor; #define gl_FragColor fragColor #endif -in vec4 v_Color; +in vec4 v_color; void main() { - gl_FragColor = v_Color; + gl_FragColor = v_color; } diff --git a/glow/src/shader/triangle/solid.vert b/glow/src/shader/triangle/solid.vert new file mode 100644 index 0000000000..d0e6d780cb --- /dev/null +++ b/glow/src/shader/triangle/solid.vert @@ -0,0 +1,11 @@ +uniform mat4 u_transform; + +in vec2 i_position; +in vec4 i_color; + +out vec4 v_color; + +void main() { + gl_Position = u_transform * vec4(i_position, 0.0, 1.0); + v_color = i_color; +} diff --git a/glow/src/triangle.rs b/glow/src/triangle.rs index d0205e0818..64055da93a 100644 --- a/glow/src/triangle.rs +++ b/glow/src/triangle.rs @@ -2,11 +2,10 @@ use crate::program; use crate::Transformation; -use iced_graphics::gradient::Gradient; use iced_graphics::layer::mesh::{self, Mesh}; -use iced_graphics::triangle::{ColoredVertex2D, Vertex2D}; +use iced_graphics::triangle::{ColoredVertex2D, GradientVertex2D}; -use glow::HasContext; +use glow::{Context, HasContext, NativeProgram}; use std::marker::PhantomData; const DEFAULT_VERTICES: usize = 1_000; @@ -114,7 +113,7 @@ impl Pipeline { gl.buffer_sub_data_u8_slice( glow::ARRAY_BUFFER, (gradient_vertex_offset - * std::mem::size_of::()) + * std::mem::size_of::()) as i32, bytemuck::cast_slice(&buffers.vertices), ); @@ -174,15 +173,13 @@ impl Pipeline { last_solid_vertex += buffers.vertices.len(); }, - Mesh::Gradient { - buffers, gradient, .. - } => unsafe { + Mesh::Gradient { buffers, .. } => unsafe { gl.use_program(Some(self.gradient.program)); gl.bind_vertex_array(Some(self.gradient.vertex_array)); if transform != self.gradient.uniforms.transform { gl.uniform_matrix_4_f32_slice( - Some(&self.gradient.uniforms.locations.transform), + Some(&self.gradient.uniforms.transform_location), false, transform.as_ref(), ); @@ -190,70 +187,6 @@ impl Pipeline { self.gradient.uniforms.transform = transform; } - if &self.gradient.uniforms.gradient != *gradient { - match gradient { - Gradient::Linear(linear) => { - gl.uniform_4_f32( - Some( - &self - .gradient - .uniforms - .locations - .gradient_direction, - ), - linear.start.x, - linear.start.y, - linear.end.x, - linear.end.y, - ); - - gl.uniform_1_i32( - Some( - &self - .gradient - .uniforms - .locations - .color_stops_size, - ), - (linear.color_stops.len() * 2) as i32, - ); - - let mut stops = [0.0; 128]; - - for (index, stop) in linear - .color_stops - .iter() - .enumerate() - .take(16) - { - let [r, g, b, a] = stop.color.into_linear(); - - stops[index * 8] = r; - stops[(index * 8) + 1] = g; - stops[(index * 8) + 2] = b; - stops[(index * 8) + 3] = a; - stops[(index * 8) + 4] = stop.offset; - stops[(index * 8) + 5] = 0.; - stops[(index * 8) + 6] = 0.; - stops[(index * 8) + 7] = 0.; - } - - gl.uniform_4_f32_slice( - Some( - &self - .gradient - .uniforms - .locations - .color_stops, - ), - &stops, - ); - } - } - - self.gradient.uniforms.gradient = (*gradient).clone(); - } - gl.draw_elements_base_vertex( glow::TRIANGLES, indices.len() as i32, @@ -323,40 +256,75 @@ impl Buffer { } } +#[derive(Debug)] +pub struct Uniforms { + pub transform: Transformation, + pub transform_location: ::UniformLocation, +} + +impl Uniforms { + fn new(gl: &Context, program: NativeProgram) -> Self { + let transform = Transformation::identity(); + let transform_location = + unsafe { gl.get_uniform_location(program, "u_transform") } + .expect("Get u_transform."); + + unsafe { + gl.use_program(Some(program)); + + gl.uniform_matrix_4_f32_slice( + Some(&transform_location), + false, + transform.as_ref(), + ); + + gl.use_program(None); + } + + Self { + transform, + transform_location, + } + } +} + mod solid { use crate::program; - use crate::triangle; - use glow::{Context, HasContext, NativeProgram}; + use crate::triangle::{self, Uniforms}; + use glow::{Context, HasContext}; use iced_graphics::triangle::ColoredVertex2D; - use iced_graphics::Transformation; #[derive(Debug)] pub struct Program { pub program: ::Program, - pub vertex_array: ::VertexArray, + pub vertex_array: ::VertexArray, pub vertices: triangle::Buffer, pub uniforms: Uniforms, } impl Program { pub fn new(gl: &Context, shader_version: &program::Version) -> Self { + log::info!("GLOW: compiling triangle solid shaders."); + let program = unsafe { let vertex_shader = program::Shader::vertex( gl, shader_version, - include_str!("shader/common/solid.vert"), + [], + include_str!("shader/triangle/solid.vert"), ); let fragment_shader = program::Shader::fragment( gl, shader_version, - include_str!("shader/common/solid.frag"), + [], + include_str!("shader/triangle/solid.frag"), ); program::create( gl, &[vertex_shader, fragment_shader], - &[(0, "i_Position"), (1, "i_Color")], + &[(0, "i_position"), (1, "i_color")], ) }; @@ -378,6 +346,7 @@ mod solid { let stride = std::mem::size_of::() as i32; + // Position gl.enable_vertex_attrib_array(0); gl.vertex_attrib_pointer_f32( 0, @@ -388,6 +357,7 @@ mod solid { 0, ); + // Color gl.enable_vertex_attrib_array(1); gl.vertex_attrib_pointer_f32( 1, @@ -409,75 +379,57 @@ mod solid { } } } - - #[derive(Debug)] - pub struct Uniforms { - pub transform: Transformation, - pub transform_location: ::UniformLocation, - } - - impl Uniforms { - fn new(gl: &Context, program: NativeProgram) -> Self { - let transform = Transformation::identity(); - let transform_location = - unsafe { gl.get_uniform_location(program, "u_Transform") } - .expect("Solid - Get u_Transform."); - - unsafe { - gl.use_program(Some(program)); - - gl.uniform_matrix_4_f32_slice( - Some(&transform_location), - false, - transform.as_ref(), - ); - - gl.use_program(None); - } - - Self { - transform, - transform_location, - } - } - } } mod gradient { use crate::program; - use crate::triangle; - use glow::{Context, HasContext, NativeProgram}; - use iced_graphics::gradient::{self, Gradient}; - use iced_graphics::triangle::Vertex2D; - use iced_graphics::Transformation; + use crate::triangle::{self, Uniforms}; + use glow::{Context, HasContext}; + use iced_graphics::triangle::GradientVertex2D; #[derive(Debug)] pub struct Program { pub program: ::Program, pub vertex_array: ::VertexArray, - pub vertices: triangle::Buffer, + pub vertices: triangle::Buffer, pub uniforms: Uniforms, } impl Program { pub fn new(gl: &Context, shader_version: &program::Version) -> Self { + log::info!("GLOW: compiling triangle gradient shaders."); let program = unsafe { let vertex_shader = program::Shader::vertex( gl, shader_version, - include_str!("shader/common/gradient.vert"), + [], + include_str!("shader/triangle/gradient.vert"), ); let fragment_shader = program::Shader::fragment( gl, shader_version, - include_str!("shader/common/gradient.frag"), + [include_str!("shader/includes/gradient.frag")], + include_str!("shader/triangle/gradient.frag"), ); program::create( gl, &[vertex_shader, fragment_shader], - &[(0, "i_Position")], + &[ + (0, "i_position"), + (1, "i_colors_1"), + (2, "i_colors_2"), + (3, "i_colors_3"), + (4, "i_colors_4"), + (5, "i_colors_5"), + (6, "i_colors_6"), + (7, "i_colors_7"), + (8, "i_colors_8"), + (9, "i_offsets_1"), + (10, "i_offsets_2"), + (11, "i_direction"), + ], ) }; @@ -497,8 +449,9 @@ mod gradient { unsafe { gl.bind_vertex_array(Some(vertex_array)); - let stride = std::mem::size_of::() as i32; + let stride = std::mem::size_of::() as i32; + // Position gl.enable_vertex_attrib_array(0); gl.vertex_attrib_pointer_f32( 0, @@ -509,80 +462,60 @@ mod gradient { 0, ); - gl.bind_vertex_array(None); - }; - - Self { - program, - vertex_array, - vertices, - uniforms: Uniforms::new(gl, program), - } - } - } - - #[derive(Debug)] - pub struct Uniforms { - pub gradient: Gradient, - pub transform: Transformation, - pub locations: Locations, - } - - #[derive(Debug)] - pub struct Locations { - pub gradient_direction: ::UniformLocation, - pub color_stops_size: ::UniformLocation, - //currently the maximum number of stops is 16 due to lack of SSBO in GL2.1 - pub color_stops: ::UniformLocation, - pub transform: ::UniformLocation, - } - - impl Uniforms { - fn new(gl: &Context, program: NativeProgram) -> Self { - let gradient_direction = unsafe { - gl.get_uniform_location(program, "gradient_direction") - } - .expect("Gradient - Get gradient_direction."); - - let color_stops_size = - unsafe { gl.get_uniform_location(program, "color_stops_size") } - .expect("Gradient - Get color_stops_size."); - - let color_stops = unsafe { - gl.get_uniform_location(program, "color_stops") - .expect("Gradient - Get color_stops.") - }; + // Colors vec4 array[8] (indices 1-8) + for i in 0..=7 { + gl.enable_vertex_attrib_array(i + 1); + gl.vertex_attrib_pointer_f32( + i + 1, + 4, + glow::FLOAT, + false, + stride, + (4 * (2 + (i * 4))) as i32, + ) + } - let transform = Transformation::identity(); - let transform_location = - unsafe { gl.get_uniform_location(program, "u_Transform") } - .expect("Solid - Get u_Transform."); + // Offsets 1-4 + gl.enable_vertex_attrib_array(9); + gl.vertex_attrib_pointer_f32( + 9, + 4, + glow::FLOAT, + false, + stride, + 4 * 34, + ); - unsafe { - gl.use_program(Some(program)); + // Offsets 5-8 + gl.enable_vertex_attrib_array(10); + gl.vertex_attrib_pointer_f32( + 10, + 4, + glow::FLOAT, + false, + stride, + 4 * (34 + 4), + ); - gl.uniform_matrix_4_f32_slice( - Some(&transform_location), + // Direction + gl.enable_vertex_attrib_array(11); + gl.vertex_attrib_pointer_f32( + 11, + 4, + glow::FLOAT, false, - transform.as_ref(), + stride, + 4 * (34 + 4 + 4), ); - gl.use_program(None); - } + gl.bind_vertex_array(None); + }; Self { - gradient: Gradient::Linear(gradient::Linear { - start: Default::default(), - end: Default::default(), - color_stops: vec![], - }), - transform: Transformation::identity(), - locations: Locations { - gradient_direction, - color_stops_size, - color_stops, - transform: transform_location, - }, + program, + vertex_array, + vertices, + uniforms: Uniforms::new(gl, program), } } } diff --git a/glow/src/window/compositor.rs b/glow/src/window/compositor.rs index 2075603271..3188b1e575 100644 --- a/glow/src/window/compositor.rs +++ b/glow/src/window/compositor.rs @@ -27,9 +27,9 @@ impl iced_graphics::window::GLCompositor for Compositor { let version = gl.version(); log::info!( - "OpenGL version: {:?} (Embedded: {})", + "OpenGL (Embedded: {}) version: {:?}", + version.is_embedded, version, - version.is_embedded ); let renderer = gl.get_parameter_string(glow::RENDERER); diff --git a/graphics/src/gradient.rs b/graphics/src/gradient.rs index 83f25238b2..d7000da3e5 100644 --- a/graphics/src/gradient.rs +++ b/graphics/src/gradient.rs @@ -1,13 +1,13 @@ -//! For creating a Gradient. -pub mod linear; - +//! For creating a Gradient that can be used as a [`Fill`] for a mesh. pub use linear::Linear; -use crate::{Color, Point, Size}; +use crate::Point; #[derive(Debug, Clone, PartialEq)] /// A fill which transitions colors progressively along a direction, either linearly, radially (TBD), /// or conically (TBD). +/// +/// For a gradient which can be used as a fill for a background of a widget, see [`iced_native::Gradient`]. pub enum Gradient { /// A linear gradient interpolates colors along a direction from its `start` to its `end` /// point. @@ -16,101 +16,151 @@ pub enum Gradient { impl Gradient { /// Creates a new linear [`linear::Builder`]. - pub fn linear(position: impl Into) -> linear::Builder { - linear::Builder::new(position.into()) + /// + /// The `start` and `end` [`Point`]s define the absolute position of the [`Gradient`]. + pub fn linear(start: Point, end: Point) -> linear::Builder { + linear::Builder::new(start, end) + } + + /// Packs the [`Gradient`] into a buffer for use in shader code. + pub fn flatten(&self) -> [f32; 44] { + match self { + Gradient::Linear(linear) => { + let mut pack: [f32; 44] = [0.0; 44]; + let mut offsets: [f32; 8] = [2.0; 8]; + + for (index, stop) in + linear.color_stops.iter().enumerate().take(8) + { + let [r, g, b, a] = stop.color.into_linear(); + + pack[(index * 4)] = r; + pack[(index * 4) + 1] = g; + pack[(index * 4) + 2] = b; + pack[(index * 4) + 3] = a; + + offsets[index] = stop.offset; + } + + pack[32] = offsets[0]; + pack[33] = offsets[1]; + pack[34] = offsets[2]; + pack[35] = offsets[3]; + pack[36] = offsets[4]; + pack[37] = offsets[5]; + pack[38] = offsets[6]; + pack[39] = offsets[7]; + + pack[40] = linear.start.x; + pack[41] = linear.start.y; + pack[42] = linear.end.x; + pack[43] = linear.end.y; + + pack + } + } } } -#[derive(Debug, Clone, Copy, PartialEq)] -/// A point along the gradient vector where the specified [`color`] is unmixed. -/// -/// [`color`]: Self::color -pub struct ColorStop { - /// Offset along the gradient vector. - pub offset: f32, +pub mod linear { + //! Linear gradient builder & definition. + use crate::{Color, Gradient, Point}; + use iced_native::gradient::linear::BuilderError; + use iced_native::gradient::ColorStop; - /// The color of the gradient at the specified [`offset`]. + /// A linear gradient that can be used in the style of [`Fill`] or [`Stroke`]. /// - /// [`offset`]: Self::offset - pub color: Color, -} + /// [`Fill`]: crate::widget::canvas::Fill + /// [`Stroke`]: crate::widget::canvas::Stroke + #[derive(Debug, Clone, Copy, PartialEq)] + pub struct Linear { + /// The absolute starting position of the gradient. + pub start: Point, -#[derive(Debug)] -/// The position of the gradient within its bounds. -pub enum Position { - /// The gradient will be positioned with respect to two points. - Absolute { - /// The starting point of the gradient. - start: Point, - /// The ending point of the gradient. - end: Point, - }, - /// The gradient will be positioned relative to the provided bounds. - Relative { - /// The top left position of the bounds. - top_left: Point, - /// The width & height of the bounds. - size: Size, - /// The start [Location] of the gradient. - start: Location, - /// The end [Location] of the gradient. - end: Location, - }, -} + /// The absolute ending position of the gradient. + pub end: Point, -impl From<(Point, Point)> for Position { - fn from((start, end): (Point, Point)) -> Self { - Self::Absolute { start, end } + /// [`ColorStop`]s along the linear gradient path. + pub color_stops: [ColorStop; 8], } -} -#[derive(Debug)] -/// The location of a relatively-positioned gradient. -pub enum Location { - /// Top left. - TopLeft, - /// Top. - Top, - /// Top right. - TopRight, - /// Right. - Right, - /// Bottom right. - BottomRight, - /// Bottom. - Bottom, - /// Bottom left. - BottomLeft, - /// Left. - Left, -} + /// A [`Linear`] builder. + #[derive(Debug)] + pub struct Builder { + start: Point, + end: Point, + stops: [ColorStop; 8], + error: Option, + } -impl Location { - fn to_absolute(&self, top_left: Point, size: Size) -> Point { - match self { - Location::TopLeft => top_left, - Location::Top => { - Point::new(top_left.x + size.width / 2.0, top_left.y) - } - Location::TopRight => { - Point::new(top_left.x + size.width, top_left.y) - } - Location::Right => Point::new( - top_left.x + size.width, - top_left.y + size.height / 2.0, - ), - Location::BottomRight => { - Point::new(top_left.x + size.width, top_left.y + size.height) + impl Builder { + /// Creates a new [`Builder`]. + pub fn new(start: Point, end: Point) -> Self { + Self { + start, + end, + stops: std::array::from_fn(|_| ColorStop { + offset: 2.0, //default offset = invalid + color: Default::default(), + }), + error: None, } - Location::Bottom => Point::new( - top_left.x + size.width / 2.0, - top_left.y + size.height, - ), - Location::BottomLeft => { - Point::new(top_left.x, top_left.y + size.height) + } + + /// Adds a new [`ColorStop`], defined by an offset and a color, to the gradient. + /// + /// `offset` must be between `0.0` and `1.0` or the gradient cannot be built. + /// + /// Any stop added after the 8th will be silently ignored. + pub fn add_stop(mut self, offset: f32, color: Color) -> Self { + if offset.is_finite() && (0.0..=1.0).contains(&offset) { + match self.stops.binary_search_by(|stop| { + stop.offset.partial_cmp(&offset).unwrap() + }) { + Ok(_) => { + self.error = Some(BuilderError::DuplicateOffset(offset)) + } + Err(index) => { + if index < 8 { + self.stops[index] = ColorStop { offset, color }; + } + } + } + } else { + self.error = Some(BuilderError::InvalidOffset(offset)) + }; + + self + } + + /// Adds multiple [`ColorStop`]s to the gradient. + /// + /// Any stop added after the 8th will be silently ignored. + pub fn add_stops( + mut self, + stops: impl IntoIterator, + ) -> Self { + for stop in stops.into_iter() { + self = self.add_stop(stop.offset, stop.color) } - Location::Left => { - Point::new(top_left.x, top_left.y + size.height / 2.0) + + self + } + + /// Builds the linear [`Gradient`] of this [`Builder`]. + /// + /// Returns `BuilderError` if gradient in invalid. + pub fn build(self) -> Result { + if self.stops.is_empty() { + Err(BuilderError::MissingColorStop) + } else if let Some(error) = self.error { + Err(error) + } else { + Ok(Gradient::Linear(Linear { + start: self.start, + end: self.end, + color_stops: self.stops, + })) } } } diff --git a/graphics/src/gradient/linear.rs b/graphics/src/gradient/linear.rs deleted file mode 100644 index c886db479d..0000000000 --- a/graphics/src/gradient/linear.rs +++ /dev/null @@ -1,112 +0,0 @@ -//! Linear gradient builder & definition. -use crate::gradient::{ColorStop, Gradient, Position}; -use crate::{Color, Point}; - -/// A linear gradient that can be used in the style of [`Fill`] or [`Stroke`]. -/// -/// [`Fill`]: crate::widget::canvas::Fill -/// [`Stroke`]: crate::widget::canvas::Stroke -#[derive(Debug, Clone, PartialEq)] -pub struct Linear { - /// The point where the linear gradient begins. - pub start: Point, - /// The point where the linear gradient ends. - pub end: Point, - /// [`ColorStop`]s along the linear gradient path. - pub color_stops: Vec, -} - -/// A [`Linear`] builder. -#[derive(Debug)] -pub struct Builder { - start: Point, - end: Point, - stops: Vec, - error: Option, -} - -impl Builder { - /// Creates a new [`Builder`]. - pub fn new(position: Position) -> Self { - let (start, end) = match position { - Position::Absolute { start, end } => (start, end), - Position::Relative { - top_left, - size, - start, - end, - } => ( - start.to_absolute(top_left, size), - end.to_absolute(top_left, size), - ), - }; - - Self { - start, - end, - stops: vec![], - error: None, - } - } - - /// Adds a new stop, defined by an offset and a color, to the gradient. - /// - /// `offset` must be between `0.0` and `1.0` or the gradient cannot be built. - /// - /// Note: when using the [`glow`] backend, any color stop added after the 16th - /// will not be displayed. - /// - /// On the [`wgpu`] backend this limitation does not exist (technical limit is 524,288 stops). - /// - /// [`glow`]: https://docs.rs/iced_glow - /// [`wgpu`]: https://docs.rs/iced_wgpu - pub fn add_stop(mut self, offset: f32, color: Color) -> Self { - if offset.is_finite() && (0.0..=1.0).contains(&offset) { - match self.stops.binary_search_by(|stop| { - stop.offset.partial_cmp(&offset).unwrap() - }) { - Ok(_) => { - self.error = Some(BuilderError::DuplicateOffset(offset)) - } - Err(index) => { - self.stops.insert(index, ColorStop { offset, color }); - } - } - } else { - self.error = Some(BuilderError::InvalidOffset(offset)) - }; - - self - } - - /// Builds the linear [`Gradient`] of this [`Builder`]. - /// - /// Returns `BuilderError` if gradient in invalid. - pub fn build(self) -> Result { - if self.stops.is_empty() { - Err(BuilderError::MissingColorStop) - } else if let Some(error) = self.error { - Err(error) - } else { - Ok(Gradient::Linear(Linear { - start: self.start, - end: self.end, - color_stops: self.stops, - })) - } - } -} - -/// An error that happened when building a [`Linear`] gradient. -#[derive(Debug, thiserror::Error)] -pub enum BuilderError { - #[error("Gradients must contain at least one color stop.")] - /// Gradients must contain at least one color stop. - MissingColorStop, - #[error("Offset {0} must be a unique, finite number.")] - /// Offsets in a gradient must all be unique & finite. - DuplicateOffset(f32), - #[error("Offset {0} must be between 0.0..=1.0.")] - /// Offsets in a gradient must be between 0.0..=1.0. - InvalidOffset(f32), -} diff --git a/graphics/src/layer.rs b/graphics/src/layer.rs index 1d453caa39..6430268e34 100644 --- a/graphics/src/layer.rs +++ b/graphics/src/layer.rs @@ -1,16 +1,16 @@ //! Organize rendering primitives into a flattened list of layers. mod image; -mod quad; mod text; pub mod mesh; +pub mod quad; pub use image::Image; pub use mesh::Mesh; -pub use quad::Quad; pub use text::Text; use crate::alignment; +use crate::layer::quad::Properties; use crate::{ Background, Font, Point, Primitive, Rectangle, Size, Vector, Viewport, }; @@ -22,7 +22,7 @@ pub struct Layer<'a> { pub bounds: Rectangle, /// The quads of the [`Layer`]. - pub quads: Vec, + pub quads: Quads, /// The triangle meshes of the [`Layer`]. pub meshes: Vec>, @@ -34,12 +34,29 @@ pub struct Layer<'a> { pub images: Vec, } +/// The quads of the [`Layer`]. +#[derive(Default, Debug)] +pub struct Quads { + /// The solid quads of the [`Layer`]. + pub solids: Vec, + + /// The gradient quads of the [`Layer`]. + pub gradients: Vec, +} + +impl Quads { + /// Returns whether both solid & gradient quads are empty or not. + pub fn is_empty(&self) -> bool { + self.solids.is_empty() && self.gradients.is_empty() + } +} + impl<'a> Layer<'a> { /// Creates a new [`Layer`] with the given clipping bounds. pub fn new(bounds: Rectangle) -> Self { Self { bounds, - quads: Vec::new(), + quads: Quads::default(), meshes: Vec::new(), text: Vec::new(), images: Vec::new(), @@ -152,19 +169,36 @@ impl<'a> Layer<'a> { let layer = &mut layers[current_layer]; // TODO: Move some of these computations to the GPU (?) - layer.quads.push(Quad { + let properties = Properties { position: [ bounds.x + translation.x, bounds.y + translation.y, ], size: [bounds.width, bounds.height], - color: match background { - Background::Color(color) => color.into_linear(), - }, + border_color: border_color.into_linear(), border_radius: *border_radius, border_width: *border_width, - border_color: border_color.into_linear(), - }); + }; + + match background { + Background::Color(color) => { + layer.quads.solids.push(quad::Solid { + color: color.into_linear(), + properties, + }); + } + Background::Gradient(gradient) => { + let quad = quad::Gradient { + gradient: (*gradient).flatten(Rectangle::new( + properties.position.into(), + properties.size.into(), + )), + properties, + }; + + layer.quads.gradients.push(quad); + } + }; } Primitive::SolidMesh { buffers, size } => { let layer = &mut layers[current_layer]; @@ -183,11 +217,7 @@ impl<'a> Layer<'a> { }); } } - Primitive::GradientMesh { - buffers, - size, - gradient, - } => { + Primitive::GradientMesh { buffers, size } => { let layer = &mut layers[current_layer]; let bounds = Rectangle::new( @@ -201,7 +231,6 @@ impl<'a> Layer<'a> { origin: Point::new(translation.x, translation.y), buffers, clip_bounds, - gradient, }); } } diff --git a/graphics/src/layer/mesh.rs b/graphics/src/layer/mesh.rs index 7661c5c9c3..4a804730c6 100644 --- a/graphics/src/layer/mesh.rs +++ b/graphics/src/layer/mesh.rs @@ -1,6 +1,6 @@ //! A collection of triangle primitives. use crate::triangle; -use crate::{Gradient, Point, Rectangle}; +use crate::{Point, Rectangle}; /// A mesh of triangles. #[derive(Debug, Clone, Copy)] @@ -22,13 +22,10 @@ pub enum Mesh<'a> { origin: Point, /// The vertex and index buffers of the [`Mesh`]. - buffers: &'a triangle::Mesh2D, + buffers: &'a triangle::Mesh2D, /// The clipping bounds of the [`Mesh`]. clip_bounds: Rectangle, - - /// The gradient to apply to the [`Mesh`]. - gradient: &'a Gradient, }, } diff --git a/graphics/src/layer/quad.rs b/graphics/src/layer/quad.rs index 0d8bde9dde..b5f88e6159 100644 --- a/graphics/src/layer/quad.rs +++ b/graphics/src/layer/quad.rs @@ -1,30 +1,51 @@ -/// A colored rectangle with a border. -/// -/// This type can be directly uploaded to GPU memory. -#[derive(Debug, Clone, Copy)] +//! A rectangle with certain styled properties. + +use bytemuck::{Pod, Zeroable}; + +/// The properties of a quad. +#[derive(Clone, Copy, Debug, Pod, Zeroable)] #[repr(C)] -pub struct Quad { - /// The position of the [`Quad`]. +pub struct Properties { + /// The position of the quad. pub position: [f32; 2], - /// The size of the [`Quad`]. + /// The size of the quad. pub size: [f32; 2], - /// The color of the [`Quad`], in __linear RGB__. - pub color: [f32; 4], - - /// The border color of the [`Quad`], in __linear RGB__. + /// The border color of the quad, in __linear RGB__. pub border_color: [f32; 4], - /// The border radius of the [`Quad`]. + /// The border radii of the quad. pub border_radius: [f32; 4], - /// The border width of the [`Quad`]. + /// The border width of the quad. pub border_width: f32, } +/// A quad filled with a solid color. +#[derive(Clone, Copy, Debug, Pod, Zeroable)] +#[repr(C)] +pub struct Solid { + /// The background color data of the quad. + pub color: [f32; 4], + + /// The [`Properties`] of the quad. + pub properties: Properties, +} + +/// A quad filled with interpolated colors. +#[derive(Clone, Copy, Debug)] +#[repr(C)] +pub struct Gradient { + /// The background gradient data of the quad. + pub gradient: [f32; 44], + + /// The [`Properties`] of the quad. + pub properties: Properties, +} + #[allow(unsafe_code)] -unsafe impl bytemuck::Zeroable for Quad {} +unsafe impl Pod for Gradient {} #[allow(unsafe_code)] -unsafe impl bytemuck::Pod for Quad {} +unsafe impl Zeroable for Gradient {} diff --git a/graphics/src/primitive.rs b/graphics/src/primitive.rs index 5a163a2f5f..1c73df340c 100644 --- a/graphics/src/primitive.rs +++ b/graphics/src/primitive.rs @@ -3,7 +3,6 @@ use iced_native::svg; use iced_native::{Background, Color, Font, Rectangle, Size, Vector}; use crate::alignment; -use crate::gradient::Gradient; use crate::triangle; use std::sync::Arc; @@ -41,7 +40,7 @@ pub enum Primitive { bounds: Rectangle, /// The background of the quad background: Background, - /// The border radius of the quad + /// The border radii of the quad border_radius: [f32; 4], /// The border width of the quad border_width: f32, @@ -98,15 +97,12 @@ pub enum Primitive { /// It can be used to render many kinds of geometry freely. GradientMesh { /// The vertices and indices of the mesh. - buffers: triangle::Mesh2D, + buffers: triangle::Mesh2D, /// The size of the drawable region of the mesh. /// /// Any geometry that falls out of this region will be clipped. size: Size, - - /// The [`Gradient`] to apply to the mesh. - gradient: Gradient, }, /// A cached primitive. /// diff --git a/graphics/src/triangle.rs b/graphics/src/triangle.rs index f52b2339b1..e738db4dea 100644 --- a/graphics/src/triangle.rs +++ b/graphics/src/triangle.rs @@ -13,14 +13,23 @@ pub struct Mesh2D { pub indices: Vec, } -/// A two-dimensional vertex. -#[derive(Copy, Clone, Debug, Zeroable, Pod)] +/// A vertex which contains 2D position & flattened gradient data. +#[derive(Copy, Clone, Debug)] #[repr(C)] -pub struct Vertex2D { +pub struct GradientVertex2D { /// The vertex position in 2D space. pub position: [f32; 2], + + /// The flattened vertex data of the gradient. + pub gradient: [f32; 44], } +#[allow(unsafe_code)] +unsafe impl Zeroable for GradientVertex2D {} + +#[allow(unsafe_code)] +unsafe impl Pod for GradientVertex2D {} + /// A two-dimensional vertex with a color. #[derive(Copy, Clone, Debug, Zeroable, Pod)] #[repr(C)] diff --git a/graphics/src/widget/canvas.rs b/graphics/src/widget/canvas.rs index b070d0a676..c0016af2e8 100644 --- a/graphics/src/widget/canvas.rs +++ b/graphics/src/widget/canvas.rs @@ -16,20 +16,20 @@ mod program; mod style; mod text; -pub use crate::gradient::{self, Gradient}; pub use cache::Cache; pub use cursor::Cursor; pub use event::Event; pub use fill::{Fill, FillRule}; pub use frame::Frame; pub use geometry::Geometry; +pub use gradient::Gradient; pub use path::Path; pub use program::Program; pub use stroke::{LineCap, LineDash, LineJoin, Stroke}; pub use style::Style; pub use text::Text; -use crate::{Backend, Primitive, Renderer}; +use crate::{gradient, Backend, Primitive, Renderer}; use iced_native::layout::{self, Layout}; use iced_native::mouse; diff --git a/graphics/src/widget/canvas/frame.rs b/graphics/src/widget/canvas/frame.rs index d68548ae8d..d934439559 100644 --- a/graphics/src/widget/canvas/frame.rs +++ b/graphics/src/widget/canvas/frame.rs @@ -1,6 +1,7 @@ -use crate::gradient::Gradient; use crate::triangle; -use crate::widget::canvas::{path, Fill, Geometry, Path, Stroke, Style, Text}; +use crate::widget::canvas::{ + path, Fill, Geometry, Gradient, Path, Stroke, Style, Text, +}; use crate::Primitive; use iced_native::{Point, Rectangle, Size, Vector}; @@ -24,10 +25,7 @@ pub struct Frame { enum Buffer { Solid(tessellation::VertexBuffers), - Gradient( - tessellation::VertexBuffers, - Gradient, - ), + Gradient(tessellation::VertexBuffers), } struct BufferStack { @@ -49,12 +47,11 @@ impl BufferStack { )); } }, - Style::Gradient(gradient) => match self.stack.last() { - Some(Buffer::Gradient(_, last)) if gradient == last => {} + Style::Gradient(_) => match self.stack.last() { + Some(Buffer::Gradient(_)) => {} _ => { self.stack.push(Buffer::Gradient( tessellation::VertexBuffers::new(), - gradient.clone(), )); } }, @@ -74,9 +71,14 @@ impl BufferStack { TriangleVertex2DBuilder(color.into_linear()), )) } - (Style::Gradient(_), Buffer::Gradient(buffer, _)) => Box::new( - tessellation::BuffersBuilder::new(buffer, Vertex2DBuilder), - ), + (Style::Gradient(gradient), Buffer::Gradient(buffer)) => { + Box::new(tessellation::BuffersBuilder::new( + buffer, + GradientVertex2DBuilder { + gradient: gradient.clone(), + }, + )) + } _ => unreachable!(), } } @@ -92,9 +94,14 @@ impl BufferStack { TriangleVertex2DBuilder(color.into_linear()), )) } - (Style::Gradient(_), Buffer::Gradient(buffer, _)) => Box::new( - tessellation::BuffersBuilder::new(buffer, Vertex2DBuilder), - ), + (Style::Gradient(gradient), Buffer::Gradient(buffer)) => { + Box::new(tessellation::BuffersBuilder::new( + buffer, + GradientVertex2DBuilder { + gradient: gradient.clone(), + }, + )) + } _ => unreachable!(), } } @@ -132,11 +139,13 @@ impl Transform { } fn transform_gradient(&self, mut gradient: Gradient) -> Gradient { - let (start, end) = match &mut gradient { - Gradient::Linear(linear) => (&mut linear.start, &mut linear.end), - }; - self.transform_point(start); - self.transform_point(end); + match &mut gradient { + Gradient::Linear(linear) => { + self.transform_point(&mut linear.start); + self.transform_point(&mut linear.end); + } + } + gradient } } @@ -444,7 +453,7 @@ impl Frame { }) } } - Buffer::Gradient(buffer, gradient) => { + Buffer::Gradient(buffer) => { if !buffer.indices.is_empty() { self.primitives.push(Primitive::GradientMesh { buffers: triangle::Mesh2D { @@ -452,7 +461,6 @@ impl Frame { indices: buffer.indices, }, size: self.size, - gradient, }) } } @@ -463,34 +471,38 @@ impl Frame { } } -struct Vertex2DBuilder; +struct GradientVertex2DBuilder { + gradient: Gradient, +} -impl tessellation::FillVertexConstructor - for Vertex2DBuilder +impl tessellation::FillVertexConstructor + for GradientVertex2DBuilder { fn new_vertex( &mut self, vertex: tessellation::FillVertex<'_>, - ) -> triangle::Vertex2D { + ) -> triangle::GradientVertex2D { let position = vertex.position(); - triangle::Vertex2D { + triangle::GradientVertex2D { position: [position.x, position.y], + gradient: self.gradient.flatten(), } } } -impl tessellation::StrokeVertexConstructor - for Vertex2DBuilder +impl tessellation::StrokeVertexConstructor + for GradientVertex2DBuilder { fn new_vertex( &mut self, vertex: tessellation::StrokeVertex<'_, '_>, - ) -> triangle::Vertex2D { + ) -> triangle::GradientVertex2D { let position = vertex.position(); - triangle::Vertex2D { + triangle::GradientVertex2D { position: [position.x, position.y], + gradient: self.gradient.flatten(), } } } diff --git a/native/src/lib.rs b/native/src/lib.rs index ce7c010d4d..c05b7d0117 100644 --- a/native/src/lib.rs +++ b/native/src/lib.rs @@ -80,8 +80,8 @@ mod debug; pub use iced_core::alignment; pub use iced_core::time; pub use iced_core::{ - color, Alignment, Background, Color, ContentFit, Font, Length, Padding, - Point, Rectangle, Size, Vector, + color, gradient, Alignment, Background, Color, ContentFit, Degrees, Font, + Gradient, Length, Padding, Point, Radians, Rectangle, Size, Vector, }; pub use iced_futures::{executor, futures}; pub use iced_style::application; diff --git a/native/src/renderer.rs b/native/src/renderer.rs index 5e776be6cd..741d1c3203 100644 --- a/native/src/renderer.rs +++ b/native/src/renderer.rs @@ -59,7 +59,7 @@ pub struct Quad { pub border_color: Color, } -/// The border radi for the corners of a graphics primitive in the order: +/// The border radii for the corners of a graphics primitive in the order: /// top-left, top-right, bottom-right, bottom-left. #[derive(Debug, Clone, Copy, PartialEq, Default)] pub struct BorderRadius([f32; 4]); diff --git a/src/lib.rs b/src/lib.rs index 001768272b..fa7285e891 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -212,8 +212,9 @@ pub use theme::Theme; pub use runtime::alignment; pub use runtime::futures; pub use runtime::{ - color, Alignment, Background, Color, Command, ContentFit, Font, Length, - Padding, Point, Rectangle, Size, Vector, + color, gradient, Alignment, Background, Color, Command, ContentFit, + Degrees, Font, Gradient, Length, Padding, Point, Radians, Rectangle, Size, + Vector, }; #[cfg(feature = "system")] diff --git a/style/src/button.rs b/style/src/button.rs index a564a2b7ea..ffe755cb54 100644 --- a/style/src/button.rs +++ b/style/src/button.rs @@ -68,6 +68,9 @@ pub trait StyleSheet { a: color.a * 0.5, ..color }), + Background::Gradient(gradient) => { + Background::Gradient(gradient.adjust_alpha(0.5)) + } }), text_color: Color { a: active.text_color.a * 0.5, diff --git a/style/src/theme.rs b/style/src/theme.rs index 271d9a29f2..63ed9fa050 100644 --- a/style/src/theme.rs +++ b/style/src/theme.rs @@ -217,6 +217,9 @@ impl button::StyleSheet for Theme { a: color.a * 0.5, ..color }), + Background::Gradient(gradient) => { + Background::Gradient(gradient.adjust_alpha(0.5)) + } }), text_color: Color { a: active.text_color.a * 0.5, diff --git a/wgpu/src/buffer.rs b/wgpu/src/buffer.rs index 7c092d0b7a..485a2ed62f 100644 --- a/wgpu/src/buffer.rs +++ b/wgpu/src/buffer.rs @@ -1,3 +1,256 @@ -//! Utilities for buffer operations. -pub mod dynamic; -pub mod r#static; +use bytemuck::{Pod, Zeroable}; +use encase::private::WriteInto; +use encase::ShaderType; +use std::fmt; +use std::fmt::{Debug, Formatter}; +use std::marker::PhantomData; +use std::mem; + +/// A generic buffer struct useful for items which have no alignment requirements +/// (e.g. Vertex, Index buffers) & no dynamic offsets. +#[derive(Debug)] +pub struct Static { + //stored sequentially per mesh iteration; refers to the offset index in the GPU buffer + offsets: Vec, + label: &'static str, + usages: wgpu::BufferUsages, + gpu: wgpu::Buffer, + size: wgpu::BufferAddress, + _data: PhantomData, +} + +impl Static { + /// Initialize a new static buffer. + pub fn new( + device: &wgpu::Device, + label: &'static str, + usages: wgpu::BufferUsages, + count: usize, + ) -> Self { + let size = (mem::size_of::() * count) as u64; + + Self { + offsets: Vec::new(), + label, + usages, + gpu: Self::gpu_buffer(device, label, size, usages), + size, + _data: PhantomData, + } + } + + fn gpu_buffer( + device: &wgpu::Device, + label: &'static str, + size: wgpu::BufferAddress, + usage: wgpu::BufferUsages, + ) -> wgpu::Buffer { + device.create_buffer(&wgpu::BufferDescriptor { + label: Some(label), + size, + usage, + mapped_at_creation: false, + }) + } + + /// Returns whether or not the buffer needs to be recreated. + pub fn resize(&mut self, device: &wgpu::Device, new_count: usize) -> bool { + let size = (mem::size_of::() * new_count) as u64; + + if self.size < size { + self.offsets.clear(); + self.size = size; + self.gpu = Self::gpu_buffer(device, self.label, size, self.usages); + true + } else { + false + } + } + + /// Writes the current vertex data to the gpu buffer with a memcpy & stores its offset. + /// + /// Returns the size of the written bytes. + pub fn write( + &mut self, + device: &wgpu::Device, + staging_belt: &mut wgpu::util::StagingBelt, + encoder: &mut wgpu::CommandEncoder, + offset: u64, + content: &[T], + ) -> u64 { + let bytes = bytemuck::cast_slice(content); + let bytes_size = bytes.len() as u64; + + if let Some(buffer_size) = wgpu::BufferSize::new(bytes_size) { + let mut buffer = staging_belt.write_buffer( + encoder, + &self.gpu, + offset, + buffer_size, + device, + ); + + buffer.copy_from_slice(bytes); + + self.offsets.push(offset); + } + + bytes_size + } + + fn offset_at(&self, index: usize) -> &wgpu::BufferAddress { + self.offsets + .get(index) + .expect("Offset at index does not exist.") + } + + /// Returns the slice calculated from the offset stored at the given index. + /// e.g. to calculate the slice for the 2nd mesh in the layer, this would be the offset at index + /// 1 that we stored earlier when writing. + pub fn slice_from_index(&self, index: usize) -> wgpu::BufferSlice<'_> { + self.gpu.slice(self.offset_at(index)..) + } + + /// Clears any temporary data from the buffer. + pub fn clear(&mut self) { + self.offsets.clear() + } + + /// Returns a reference to the GPU buffer. + pub fn raw(&self) -> &wgpu::Buffer { + &self.gpu + } +} + +/// A dynamic uniform buffer is any type of buffer which does not have a static offset. +pub struct DynamicUniform { + offsets: Vec, + cpu: encase::DynamicUniformBuffer>, + gpu: wgpu::Buffer, + label: &'static str, + size: u64, + _data: PhantomData, +} + +impl Debug for DynamicUniform { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + write!( + f, + "{:?}, {:?}, {:?}, {:?}", + self.offsets, self.gpu, self.label, self.size + ) + } +} + +impl DynamicUniform { + pub fn new(device: &wgpu::Device, label: &'static str) -> Self { + let initial_size = u64::from(T::min_size()); + + Self { + offsets: Vec::new(), + cpu: encase::DynamicUniformBuffer::new(Vec::new()), + gpu: DynamicUniform::::create_gpu_buffer( + device, + label, + wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, + initial_size, + ), + label, + size: initial_size, + _data: Default::default(), + } + } + + fn create_gpu_buffer( + device: &wgpu::Device, + label: &'static str, + usage: wgpu::BufferUsages, + size: u64, + ) -> wgpu::Buffer { + device.create_buffer(&wgpu::BufferDescriptor { + label: Some(label), + size, + usage, + mapped_at_creation: false, + }) + } + + /// Write a new value to the CPU buffer with proper alignment. Stores the returned offset value + /// in the buffer for future use. + pub fn push(&mut self, value: &T) { + //this write operation on the cpu buffer will adjust for uniform alignment requirements + let offset = self + .cpu + .write(value) + .expect("Error when writing to dynamic uniform buffer.") + as wgpu::DynamicOffset; + + self.offsets.push(offset); + } + + /// Resize buffer contents if necessary. This will re-create the GPU buffer if current size is + /// less than the newly computed size from the CPU buffer. + /// + /// If the gpu buffer is resized, its bind group will need to be recreated! + pub fn resize(&mut self, device: &wgpu::Device) -> bool { + let new_size = self.cpu.as_ref().len() as u64; + + if self.size < new_size { + self.gpu = DynamicUniform::::create_gpu_buffer( + device, + self.label, + wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, + new_size, + ); + self.size = new_size; + true + } else { + false + } + } + + /// Write the contents of this dynamic uniform buffer to the GPU via staging belt command. + pub fn write( + &mut self, + device: &wgpu::Device, + staging_belt: &mut wgpu::util::StagingBelt, + encoder: &mut wgpu::CommandEncoder, + ) { + let size = self.cpu.as_ref().len(); + + if let Some(buffer_size) = wgpu::BufferSize::new(size as u64) { + let mut buffer = staging_belt.write_buffer( + encoder, + &self.gpu, + 0, + buffer_size, + device, + ); + + buffer.copy_from_slice(self.cpu.as_ref()); + } + } + + // Gets the aligned offset at the given index from the CPU buffer. + pub fn offset_at_index(&self, index: usize) -> wgpu::DynamicOffset { + let offset = self + .offsets + .get(index) + .copied() + .expect("Index not found in offsets."); + + offset + } + + /// Returns a reference to the GPU buffer. + pub fn raw(&self) -> &wgpu::Buffer { + &self.gpu + } + + /// Reset the buffer. + pub fn clear(&mut self) { + self.offsets.clear(); + self.cpu.as_mut().clear(); + self.cpu.set_offset(0); + } +} diff --git a/wgpu/src/buffer/dynamic.rs b/wgpu/src/buffer/dynamic.rs deleted file mode 100644 index 18be03ddeb..0000000000 --- a/wgpu/src/buffer/dynamic.rs +++ /dev/null @@ -1,219 +0,0 @@ -//! Utilities for uniform buffer operations. -use encase::private::WriteInto; -use encase::ShaderType; - -use std::fmt; -use std::marker::PhantomData; - -/// A dynamic buffer is any type of buffer which does not have a static offset. -#[derive(Debug)] -pub struct Buffer { - offsets: Vec, - cpu: Internal, - gpu: wgpu::Buffer, - label: &'static str, - size: u64, - _data: PhantomData, -} - -impl Buffer { - /// Creates a new dynamic uniform buffer. - pub fn uniform(device: &wgpu::Device, label: &'static str) -> Self { - Buffer::new( - device, - Internal::Uniform(encase::DynamicUniformBuffer::new(Vec::new())), - label, - wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, - ) - } - - #[cfg(not(target_arch = "wasm32"))] - /// Creates a new dynamic storage buffer. - pub fn storage(device: &wgpu::Device, label: &'static str) -> Self { - Buffer::new( - device, - Internal::Storage(encase::DynamicStorageBuffer::new(Vec::new())), - label, - wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST, - ) - } - - fn new( - device: &wgpu::Device, - dynamic_buffer_type: Internal, - label: &'static str, - usage: wgpu::BufferUsages, - ) -> Self { - let initial_size = u64::from(T::min_size()); - - Self { - offsets: Vec::new(), - cpu: dynamic_buffer_type, - gpu: Buffer::::create_gpu_buffer( - device, - label, - usage, - initial_size, - ), - label, - size: initial_size, - _data: Default::default(), - } - } - - fn create_gpu_buffer( - device: &wgpu::Device, - label: &'static str, - usage: wgpu::BufferUsages, - size: u64, - ) -> wgpu::Buffer { - device.create_buffer(&wgpu::BufferDescriptor { - label: Some(label), - size, - usage, - mapped_at_creation: false, - }) - } - - /// Write a new value to the CPU buffer with proper alignment. Stores the returned offset value - /// in the buffer for future use. - pub fn push(&mut self, value: &T) { - //this write operation on the cpu buffer will adjust for uniform alignment requirements - let offset = self.cpu.write(value); - self.offsets.push(offset as u32); - } - - /// Resize buffer contents if necessary. This will re-create the GPU buffer if current size is - /// less than the newly computed size from the CPU buffer. - /// - /// If the gpu buffer is resized, its bind group will need to be recreated! - pub fn resize(&mut self, device: &wgpu::Device) -> bool { - let new_size = self.cpu.get_ref().len() as u64; - - if self.size < new_size { - let usages = match self.cpu { - Internal::Uniform(_) => { - wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST - } - #[cfg(not(target_arch = "wasm32"))] - Internal::Storage(_) => { - wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST - } - }; - - self.gpu = Buffer::::create_gpu_buffer( - device, self.label, usages, new_size, - ); - self.size = new_size; - true - } else { - false - } - } - - /// Write the contents of this dynamic buffer to the GPU via staging belt command. - pub fn write( - &mut self, - device: &wgpu::Device, - staging_belt: &mut wgpu::util::StagingBelt, - encoder: &mut wgpu::CommandEncoder, - ) { - let size = self.cpu.get_ref().len(); - - if let Some(buffer_size) = wgpu::BufferSize::new(size as u64) { - let mut buffer = staging_belt.write_buffer( - encoder, - &self.gpu, - 0, - buffer_size, - device, - ); - - buffer.copy_from_slice(self.cpu.get_ref()); - } - } - - // Gets the aligned offset at the given index from the CPU buffer. - pub fn offset_at_index(&self, index: usize) -> wgpu::DynamicOffset { - let offset = self - .offsets - .get(index) - .copied() - .expect("Index not found in offsets."); - - offset - } - - /// Returns a reference to the GPU buffer. - pub fn raw(&self) -> &wgpu::Buffer { - &self.gpu - } - - /// Reset the buffer. - pub fn clear(&mut self) { - self.offsets.clear(); - self.cpu.clear(); - } -} - -// Currently supported dynamic buffers. -enum Internal { - Uniform(encase::DynamicUniformBuffer>), - #[cfg(not(target_arch = "wasm32"))] - //storage buffers are not supported on wgpu wasm target (yet) - Storage(encase::DynamicStorageBuffer>), -} - -impl Internal { - /// Writes the current value to its CPU buffer with proper alignment. - pub(super) fn write( - &mut self, - value: &T, - ) -> wgpu::DynamicOffset { - match self { - Internal::Uniform(buf) => buf - .write(value) - .expect("Error when writing to dynamic uniform buffer.") - as u32, - #[cfg(not(target_arch = "wasm32"))] - Internal::Storage(buf) => buf - .write(value) - .expect("Error when writing to dynamic storage buffer.") - as u32, - } - } - - /// Returns bytearray of aligned CPU buffer. - pub(super) fn get_ref(&self) -> &Vec { - match self { - Internal::Uniform(buf) => buf.as_ref(), - #[cfg(not(target_arch = "wasm32"))] - Internal::Storage(buf) => buf.as_ref(), - } - } - - /// Resets the CPU buffer. - pub(super) fn clear(&mut self) { - match self { - Internal::Uniform(buf) => { - buf.as_mut().clear(); - buf.set_offset(0); - } - #[cfg(not(target_arch = "wasm32"))] - Internal::Storage(buf) => { - buf.as_mut().clear(); - buf.set_offset(0); - } - } - } -} - -impl fmt::Debug for Internal { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::Uniform(_) => write!(f, "Internal::Uniform(_)"), - #[cfg(not(target_arch = "wasm32"))] - Self::Storage(_) => write!(f, "Internal::Storage(_)"), - } - } -} diff --git a/wgpu/src/buffer/static.rs b/wgpu/src/buffer/static.rs deleted file mode 100644 index ef87422f41..0000000000 --- a/wgpu/src/buffer/static.rs +++ /dev/null @@ -1,117 +0,0 @@ -use bytemuck::{Pod, Zeroable}; -use std::marker::PhantomData; -use std::mem; - -//128 triangles/indices -const DEFAULT_STATIC_BUFFER_COUNT: wgpu::BufferAddress = 128; - -/// A generic buffer struct useful for items which have no alignment requirements -/// (e.g. Vertex, Index buffers) & no dynamic offsets. -#[derive(Debug)] -pub struct Buffer { - //stored sequentially per mesh iteration; refers to the offset index in the GPU buffer - offsets: Vec, - label: &'static str, - usages: wgpu::BufferUsages, - gpu: wgpu::Buffer, - size: wgpu::BufferAddress, - _data: PhantomData, -} - -impl Buffer { - /// Initialize a new static buffer. - pub fn new( - device: &wgpu::Device, - label: &'static str, - usages: wgpu::BufferUsages, - ) -> Self { - let size = (mem::size_of::() as u64) * DEFAULT_STATIC_BUFFER_COUNT; - - Self { - offsets: Vec::new(), - label, - usages, - gpu: Self::gpu_buffer(device, label, size, usages), - size, - _data: PhantomData, - } - } - - fn gpu_buffer( - device: &wgpu::Device, - label: &'static str, - size: wgpu::BufferAddress, - usage: wgpu::BufferUsages, - ) -> wgpu::Buffer { - device.create_buffer(&wgpu::BufferDescriptor { - label: Some(label), - size, - usage, - mapped_at_creation: false, - }) - } - - /// Returns whether or not the buffer needs to be recreated. This can happen whenever mesh data - /// changes & a redraw is requested. - pub fn resize(&mut self, device: &wgpu::Device, new_count: usize) -> bool { - let size = (mem::size_of::() * new_count) as u64; - - if self.size < size { - self.offsets.clear(); - self.size = size; - self.gpu = Self::gpu_buffer(device, self.label, size, self.usages); - true - } else { - false - } - } - - /// Writes the current vertex data to the gpu buffer with a memcpy & stores its offset. - /// - /// Returns the size of the written bytes. - pub fn write( - &mut self, - device: &wgpu::Device, - staging_belt: &mut wgpu::util::StagingBelt, - encoder: &mut wgpu::CommandEncoder, - offset: u64, - content: &[T], - ) -> u64 { - let bytes = bytemuck::cast_slice(content); - let bytes_size = bytes.len() as u64; - - if let Some(buffer_size) = wgpu::BufferSize::new(bytes_size) { - let mut buffer = staging_belt.write_buffer( - encoder, - &self.gpu, - offset, - buffer_size, - device, - ); - - buffer.copy_from_slice(bytes); - - self.offsets.push(offset); - } - - bytes_size - } - - fn offset_at(&self, index: usize) -> &wgpu::BufferAddress { - self.offsets - .get(index) - .expect("Offset at index does not exist.") - } - - /// Returns the slice calculated from the offset stored at the given index. - /// e.g. to calculate the slice for the 2nd mesh in the layer, this would be the offset at index - /// 1 that we stored earlier when writing. - pub fn slice_from_index(&self, index: usize) -> wgpu::BufferSlice<'_> { - self.gpu.slice(self.offset_at(index)..) - } - - /// Clears any temporary data from the buffer. - pub fn clear(&mut self) { - self.offsets.clear() - } -} diff --git a/wgpu/src/quad.rs b/wgpu/src/quad.rs index 027a34bebe..1c66076f45 100644 --- a/wgpu/src/quad.rs +++ b/wgpu/src/quad.rs @@ -1,164 +1,86 @@ -use crate::Transformation; +use crate::{buffer, Transformation}; use iced_graphics::layer; use iced_native::Rectangle; +use wgpu::{ + BindGroupDescriptor, BindGroupEntry, BindGroupLayoutDescriptor, + BindGroupLayoutEntry, BindingType, BufferBindingType, BufferUsages, + ShaderStages, +}; -use bytemuck::{Pod, Zeroable}; -use std::mem; use wgpu::util::DeviceExt; #[derive(Debug)] pub struct Pipeline { - pipeline: wgpu::RenderPipeline, - constants: wgpu::BindGroup, - constants_buffer: wgpu::Buffer, vertices: wgpu::Buffer, indices: wgpu::Buffer, - instances: wgpu::Buffer, + uniforms: buffer::Static, + uniforms_bind_group: wgpu::BindGroup, + solid: solid::Pipeline, + gradient: gradient::Pipeline, } impl Pipeline { pub fn new(device: &wgpu::Device, format: wgpu::TextureFormat) -> Pipeline { - let constant_layout = - device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { - label: Some("iced_wgpu::quad uniforms layout"), - entries: &[wgpu::BindGroupLayoutEntry { - binding: 0, - visibility: wgpu::ShaderStages::VERTEX, - ty: wgpu::BindingType::Buffer { - ty: wgpu::BufferBindingType::Uniform, - has_dynamic_offset: false, - min_binding_size: wgpu::BufferSize::new( - mem::size_of::() as wgpu::BufferAddress, - ), - }, - count: None, - }], - }); - - let constants_buffer = device.create_buffer(&wgpu::BufferDescriptor { - label: Some("iced_wgpu::quad uniforms buffer"), - size: mem::size_of::() as wgpu::BufferAddress, - usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, - mapped_at_creation: false, - }); - - let constants = device.create_bind_group(&wgpu::BindGroupDescriptor { - label: Some("iced_wgpu::quad uniforms bind group"), - layout: &constant_layout, - entries: &[wgpu::BindGroupEntry { - binding: 0, - resource: constants_buffer.as_entire_binding(), - }], - }); - - let layout = - device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { - label: Some("iced_wgpu::quad pipeline layout"), - push_constant_ranges: &[], - bind_group_layouts: &[&constant_layout], - }); - - let shader = - device.create_shader_module(wgpu::ShaderModuleDescriptor { - label: Some("iced_wgpu::quad::shader"), - source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Borrowed( - include_str!("shader/quad.wgsl"), - )), - }); - - let pipeline = - device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { - label: Some("iced_wgpu::quad pipeline"), - layout: Some(&layout), - vertex: wgpu::VertexState { - module: &shader, - entry_point: "vs_main", - buffers: &[ - wgpu::VertexBufferLayout { - array_stride: mem::size_of::() as u64, - step_mode: wgpu::VertexStepMode::Vertex, - attributes: &[wgpu::VertexAttribute { - shader_location: 0, - format: wgpu::VertexFormat::Float32x2, - offset: 0, - }], - }, - wgpu::VertexBufferLayout { - array_stride: mem::size_of::() as u64, - step_mode: wgpu::VertexStepMode::Instance, - attributes: &wgpu::vertex_attr_array!( - 1 => Float32x2, - 2 => Float32x2, - 3 => Float32x4, - 4 => Float32x4, - 5 => Float32x4, - 6 => Float32, - ), - }, - ], - }, - fragment: Some(wgpu::FragmentState { - module: &shader, - entry_point: "fs_main", - targets: &[Some(wgpu::ColorTargetState { - format, - blend: Some(wgpu::BlendState { - color: wgpu::BlendComponent { - src_factor: wgpu::BlendFactor::SrcAlpha, - dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha, - operation: wgpu::BlendOperation::Add, - }, - alpha: wgpu::BlendComponent { - src_factor: wgpu::BlendFactor::One, - dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha, - operation: wgpu::BlendOperation::Add, - }, - }), - write_mask: wgpu::ColorWrites::ALL, - })], - }), - primitive: wgpu::PrimitiveState { - topology: wgpu::PrimitiveTopology::TriangleList, - front_face: wgpu::FrontFace::Cw, - ..Default::default() - }, - depth_stencil: None, - multisample: wgpu::MultisampleState { - count: 1, - mask: !0, - alpha_to_coverage_enabled: false, - }, - multiview: None, - }); - let vertices = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { label: Some("iced_wgpu::quad vertex buffer"), contents: bytemuck::cast_slice(&QUAD_VERTS), - usage: wgpu::BufferUsages::VERTEX, + usage: BufferUsages::VERTEX, }); let indices = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { label: Some("iced_wgpu::quad index buffer"), contents: bytemuck::cast_slice(&QUAD_INDICES), - usage: wgpu::BufferUsages::INDEX, + usage: BufferUsages::INDEX, }); - let instances = device.create_buffer(&wgpu::BufferDescriptor { - label: Some("iced_wgpu::quad instance buffer"), - size: mem::size_of::() as u64 * MAX_INSTANCES as u64, - usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST, - mapped_at_creation: false, - }); + let uniforms = buffer::Static::new( + device, + "wgpu::quad uniforms buffer", + BufferUsages::UNIFORM | BufferUsages::COPY_DST, + 1, + ); + + let uniforms_layout = + device.create_bind_group_layout(&BindGroupLayoutDescriptor { + label: Some("wgpu::quad uniforms bind group layout"), + entries: &[BindGroupLayoutEntry { + binding: 0, + visibility: ShaderStages::VERTEX, + ty: BindingType::Buffer { + ty: BufferBindingType::Uniform, + has_dynamic_offset: false, + min_binding_size: wgpu::BufferSize::new( + std::mem::size_of::() + as wgpu::BufferAddress, + ), + }, + count: None, + }], + }); + + let uniforms_bind_group = + device.create_bind_group(&BindGroupDescriptor { + label: Some("wgpu::quad uniform bind group"), + layout: &uniforms_layout, + entries: &[BindGroupEntry { + binding: 0, + resource: uniforms.raw().as_entire_binding(), + }], + }); + + let solid = solid::Pipeline::new(device, format, &uniforms_layout); + let gradient = + gradient::Pipeline::new(device, format, &uniforms_layout); Pipeline { - pipeline, - constants, - constants_buffer, vertices, indices, - instances, + uniforms, + uniforms_bind_group, + solid, + gradient, } } @@ -167,119 +89,128 @@ impl Pipeline { device: &wgpu::Device, staging_belt: &mut wgpu::util::StagingBelt, encoder: &mut wgpu::CommandEncoder, - instances: &[layer::Quad], + instances: &layer::Quads, transformation: Transformation, scale: f32, bounds: Rectangle, target: &wgpu::TextureView, ) { - let uniforms = Uniforms::new(transformation, scale); + // write uniforms to GPU + let _ = self.uniforms.write( + device, + staging_belt, + encoder, + 0, + &[Uniforms::new(transformation, scale)], + ); + + // resize buffers if necessary + let _ = self.solid.instances.resize(device, instances.solids.len()); + let _ = self + .gradient + .instances + .resize(device, instances.gradients.len()); + + // write instances to solid/gradient pipelines + let _ = self.solid.instances.write( + device, + staging_belt, + encoder, + 0, + instances.solids.as_slice(), + ); + + let _ = self.gradient.instances.write( + device, + staging_belt, + encoder, + 0, + instances.gradients.as_slice(), + ); + + //done writing, begin render pass + let mut render_pass = + encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + label: Some("iced_wgpu::quad render pass"), + color_attachments: &[Some(wgpu::RenderPassColorAttachment { + view: target, + resolve_target: None, + ops: wgpu::Operations { + load: wgpu::LoadOp::Load, + store: true, + }, + })], + depth_stencil_attachment: None, + }); - { - let mut constants_buffer = staging_belt.write_buffer( - encoder, - &self.constants_buffer, + render_pass.set_bind_group(0, &self.uniforms_bind_group, &[]); + render_pass.set_vertex_buffer(0, self.vertices.slice(..)); + render_pass.set_index_buffer( + self.indices.slice(..), + wgpu::IndexFormat::Uint16, + ); + + render_pass.set_scissor_rect( + bounds.x, + bounds.y, + bounds.width, + // TODO: Address anti-aliasing adjustments properly + bounds.height, + ); + + // draw solid quads + if !instances.solids.is_empty() { + render_pass.set_pipeline(&self.solid.pipeline); + render_pass + .set_vertex_buffer(1, self.solid.instances.raw().slice(..)); + render_pass.draw_indexed( + 0..QUAD_INDICES.len() as u32, 0, - wgpu::BufferSize::new(mem::size_of::() as u64) - .unwrap(), - device, + 0..instances.solids.len() as u32, ); - - constants_buffer.copy_from_slice(bytemuck::bytes_of(&uniforms)); } - let mut i = 0; - let total = instances.len(); - - while i < total { - let end = (i + MAX_INSTANCES).min(total); - let amount = end - i; - - let instance_bytes = bytemuck::cast_slice(&instances[i..end]); - - let mut instance_buffer = staging_belt.write_buffer( - encoder, - &self.instances, + //draw gradient quads + if !instances.gradients.is_empty() { + render_pass.set_pipeline(&self.gradient.pipeline); + render_pass + .set_vertex_buffer(1, self.gradient.instances.raw().slice(..)); + render_pass.draw_indexed( + 0..QUAD_INDICES.len() as u32, 0, - wgpu::BufferSize::new(instance_bytes.len() as u64).unwrap(), - device, + 0..instances.gradients.len() as u32, ); - - instance_buffer.copy_from_slice(instance_bytes); - - { - let mut render_pass = - encoder.begin_render_pass(&wgpu::RenderPassDescriptor { - label: Some("iced_wgpu::quad render pass"), - color_attachments: &[Some( - wgpu::RenderPassColorAttachment { - view: target, - resolve_target: None, - ops: wgpu::Operations { - load: wgpu::LoadOp::Load, - store: true, - }, - }, - )], - 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.slice(..), - wgpu::IndexFormat::Uint16, - ); - render_pass.set_vertex_buffer(0, self.vertices.slice(..)); - render_pass.set_vertex_buffer(1, self.instances.slice(..)); - - render_pass.set_scissor_rect( - bounds.x, - bounds.y, - bounds.width, - // TODO: Address anti-aliasing adjustments properly - bounds.height, - ); - - render_pass.draw_indexed( - 0..QUAD_INDICES.len() as u32, - 0, - 0..amount as u32, - ); - } - - i += MAX_INSTANCES; } } } #[repr(C)] -#[derive(Clone, Copy, Zeroable, Pod)] -pub struct Vertex { +#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)] +pub struct QuadVertex { _position: [f32; 2], } const QUAD_INDICES: [u16; 6] = [0, 1, 2, 0, 2, 3]; -const QUAD_VERTS: [Vertex; 4] = [ - Vertex { +const QUAD_VERTS: [QuadVertex; 4] = [ + QuadVertex { _position: [0.0, 0.0], }, - Vertex { + QuadVertex { _position: [1.0, 0.0], }, - Vertex { + QuadVertex { _position: [1.0, 1.0], }, - Vertex { + QuadVertex { _position: [0.0, 1.0], }, ]; const MAX_INSTANCES: usize = 100_000; +#[derive(Debug, Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)] #[repr(C)] -#[derive(Debug, Clone, Copy, Zeroable, Pod)] struct Uniforms { transform: [f32; 16], scale: f32, @@ -307,3 +238,275 @@ impl Default for Uniforms { } } } + +mod solid { + use crate::buffer; + use crate::quad::{QuadVertex, MAX_INSTANCES}; + use iced_graphics::layer::quad; + use std::mem; + + #[derive(Debug)] + pub struct Pipeline { + pub pipeline: wgpu::RenderPipeline, + pub instances: buffer::Static, + } + + impl Pipeline { + pub fn new( + device: &wgpu::Device, + format: wgpu::TextureFormat, + uniforms_layout: &wgpu::BindGroupLayout, + ) -> Self { + let instances = buffer::Static::new( + device, + "iced_wgpu::quad::solid instance buffer", + wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST, + MAX_INSTANCES, + ); + + let layout = device.create_pipeline_layout( + &wgpu::PipelineLayoutDescriptor { + label: Some("iced_wgpu::quad::solid pipeline layout"), + push_constant_ranges: &[], + bind_group_layouts: &[uniforms_layout], + }, + ); + + let shader = + device.create_shader_module(wgpu::ShaderModuleDescriptor { + label: Some("iced_wgpu::quad::solid shader"), + source: wgpu::ShaderSource::Wgsl( + std::borrow::Cow::Borrowed(include_str!( + "shader/quad.wgsl" + )), + ), + }); + + let pipeline = device.create_render_pipeline( + &wgpu::RenderPipelineDescriptor { + label: Some("iced_wgpu::quad::solid pipeline"), + layout: Some(&layout), + vertex: wgpu::VertexState { + module: &shader, + entry_point: "solid_vs_main", + buffers: &[ + wgpu::VertexBufferLayout { + array_stride: mem::size_of::() + as u64, + step_mode: wgpu::VertexStepMode::Vertex, + attributes: &[wgpu::VertexAttribute { + shader_location: 0, + format: wgpu::VertexFormat::Float32x2, + offset: 0, + }], + }, + wgpu::VertexBufferLayout { + array_stride: mem::size_of::() + as u64, + step_mode: wgpu::VertexStepMode::Instance, + attributes: &wgpu::vertex_attr_array!( + // Color + 1 => Float32x4, + // Position + 2 => Float32x2, + // Size + 3 => Float32x2, + // Border color + 4 => Float32x4, + // Border radius + 5 => Float32x4, + // Border width + 6 => Float32, + ), + }, + ], + }, + fragment: Some(wgpu::FragmentState { + module: &shader, + entry_point: "solid_fs_main", + targets: &[Some(wgpu::ColorTargetState { + format, + blend: Some(wgpu::BlendState { + color: wgpu::BlendComponent { + src_factor: wgpu::BlendFactor::SrcAlpha, + dst_factor: + wgpu::BlendFactor::OneMinusSrcAlpha, + operation: wgpu::BlendOperation::Add, + }, + alpha: wgpu::BlendComponent { + src_factor: wgpu::BlendFactor::One, + dst_factor: + wgpu::BlendFactor::OneMinusSrcAlpha, + operation: wgpu::BlendOperation::Add, + }, + }), + write_mask: wgpu::ColorWrites::ALL, + })], + }), + primitive: wgpu::PrimitiveState { + topology: wgpu::PrimitiveTopology::TriangleList, + front_face: wgpu::FrontFace::Cw, + ..Default::default() + }, + depth_stencil: None, + multisample: wgpu::MultisampleState { + count: 1, + mask: !0, + alpha_to_coverage_enabled: false, + }, + multiview: None, + }, + ); + + Self { + pipeline, + instances, + } + } + } +} + +mod gradient { + use crate::buffer; + use crate::quad::{QuadVertex, MAX_INSTANCES}; + use iced_graphics::layer::quad; + use std::mem; + + #[derive(Debug)] + pub struct Pipeline { + pub pipeline: wgpu::RenderPipeline, + pub instances: buffer::Static, + } + + impl Pipeline { + pub fn new( + device: &wgpu::Device, + format: wgpu::TextureFormat, + uniforms_layout: &wgpu::BindGroupLayout, + ) -> Self { + let instances = buffer::Static::new( + device, + "iced_wgpu::quad::gradient instance buffer", + wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST, + MAX_INSTANCES, + ); + + let layout = device.create_pipeline_layout( + &wgpu::PipelineLayoutDescriptor { + label: Some("iced_wgpu::quad::gradient pipeline layout"), + push_constant_ranges: &[], + bind_group_layouts: &[uniforms_layout], + }, + ); + + let shader = + device.create_shader_module(wgpu::ShaderModuleDescriptor { + label: Some("iced_wgpu::quad::gradient shader"), + source: wgpu::ShaderSource::Wgsl( + std::borrow::Cow::Borrowed(include_str!( + "shader/quad.wgsl" + )), + ), + }); + + let pipeline = device.create_render_pipeline( + &wgpu::RenderPipelineDescriptor { + label: Some("iced_wgpu::quad::gradient pipeline"), + layout: Some(&layout), + vertex: wgpu::VertexState { + module: &shader, + entry_point: "gradient_vs_main", + buffers: &[ + wgpu::VertexBufferLayout { + array_stride: mem::size_of::() + as u64, + step_mode: wgpu::VertexStepMode::Vertex, + attributes: &[wgpu::VertexAttribute { + shader_location: 0, + format: wgpu::VertexFormat::Float32x2, + offset: 0, + }], + }, + wgpu::VertexBufferLayout { + array_stride: mem::size_of::() + as u64, + step_mode: wgpu::VertexStepMode::Instance, + attributes: &wgpu::vertex_attr_array!( + // Color 1 + 1 => Float32x4, + // Color 2 + 2 => Float32x4, + // Color 3 + 3 => Float32x4, + // Color 4 + 4 => Float32x4, + // Color 5 + 5 => Float32x4, + // Color 6 + 6 => Float32x4, + // Color 7 + 7 => Float32x4, + // Color 8 + 8 => Float32x4, + // Offsets 1-4 + 9 => Float32x4, + // Offsets 5-8 + 10 => Float32x4, + // Direction + 11 => Float32x4, + // Position & Scale + 12 => Float32x4, + // Border color + 13 => Float32x4, + // Border radius + 14 => Float32x4, + // Border width + 15 => Float32 + ), + }, + ], + }, + fragment: Some(wgpu::FragmentState { + module: &shader, + entry_point: "gradient_fs_main", + targets: &[Some(wgpu::ColorTargetState { + format, + blend: Some(wgpu::BlendState { + color: wgpu::BlendComponent { + src_factor: wgpu::BlendFactor::SrcAlpha, + dst_factor: + wgpu::BlendFactor::OneMinusSrcAlpha, + operation: wgpu::BlendOperation::Add, + }, + alpha: wgpu::BlendComponent { + src_factor: wgpu::BlendFactor::One, + dst_factor: + wgpu::BlendFactor::OneMinusSrcAlpha, + operation: wgpu::BlendOperation::Add, + }, + }), + write_mask: wgpu::ColorWrites::ALL, + })], + }), + primitive: wgpu::PrimitiveState { + topology: wgpu::PrimitiveTopology::TriangleList, + front_face: wgpu::FrontFace::Cw, + ..Default::default() + }, + depth_stencil: None, + multisample: wgpu::MultisampleState { + count: 1, + mask: !0, + alpha_to_coverage_enabled: false, + }, + multiview: None, + }, + ); + + Self { + pipeline, + instances, + } + } + } +} diff --git a/wgpu/src/shader/gradient.wgsl b/wgpu/src/shader/gradient.wgsl deleted file mode 100644 index 63825aec0b..0000000000 --- a/wgpu/src/shader/gradient.wgsl +++ /dev/null @@ -1,88 +0,0 @@ -struct Uniforms { - transform: mat4x4, - //xy = start, wz = end - position: vec4, - //x = start stop, y = end stop, zw = padding - stop_range: vec4, -} - -struct Stop { - color: vec4, - offset: f32, -}; - -@group(0) @binding(0) -var uniforms: Uniforms; - -@group(0) @binding(1) -var color_stops: array; - -struct VertexOutput { - @builtin(position) position: vec4, - @location(0) raw_position: vec2 -} - -@vertex -fn vs_main(@location(0) input: vec2) -> VertexOutput { - var output: VertexOutput; - output.position = uniforms.transform * vec4(input.xy, 0.0, 1.0); - output.raw_position = input; - - return output; -} - -//TODO: rewrite without branching -@fragment -fn fs_main(input: VertexOutput) -> @location(0) vec4 { - let start = uniforms.position.xy; - let end = uniforms.position.zw; - let start_stop = uniforms.stop_range.x; - let end_stop = uniforms.stop_range.y; - - let v1 = end - start; - let v2 = input.raw_position.xy - start; - let unit = normalize(v1); - let offset = dot(unit, v2) / length(v1); - - let min_stop = color_stops[start_stop]; - let max_stop = color_stops[end_stop]; - - var color: vec4; - - if (offset <= min_stop.offset) { - color = min_stop.color; - } else if (offset >= max_stop.offset) { - color = max_stop.color; - } else { - var min = min_stop; - var max = max_stop; - var min_index = start_stop; - var max_index = end_stop; - - loop { - if (min_index >= max_index - 1) { - break; - } - - let index = min_index + (max_index - min_index) / 2; - - let stop = color_stops[index]; - - if (offset <= stop.offset) { - max = stop; - max_index = index; - } else { - min = stop; - min_index = index; - } - } - - color = mix(min.color, max.color, smoothstep( - min.offset, - max.offset, - offset - )); - } - - return color; -} diff --git a/wgpu/src/shader/quad.wgsl b/wgpu/src/shader/quad.wgsl index cf4f7e4d5b..9ef20a66f2 100644 --- a/wgpu/src/shader/quad.wgsl +++ b/wgpu/src/shader/quad.wgsl @@ -5,17 +5,50 @@ struct Globals { @group(0) @binding(0) var globals: Globals; -struct VertexInput { +fn distance_alg( + frag_coord: vec2, + position: vec2, + size: vec2, + radius: f32 +) -> f32 { + var inner_size: vec2 = size - vec2(radius, radius) * 2.0; + var top_left: vec2 = position + vec2(radius, radius); + var bottom_right: vec2 = top_left + inner_size; + + var top_left_distance: vec2 = top_left - frag_coord; + var bottom_right_distance: vec2 = frag_coord - bottom_right; + + var dist: vec2 = vec2( + max(max(top_left_distance.x, bottom_right_distance.x), 0.0), + max(max(top_left_distance.y, bottom_right_distance.y), 0.0) + ); + + return sqrt(dist.x * dist.x + dist.y * dist.y); +} + +// Based on the fragement position and the center of the quad, select one of the 4 radi. +// Order matches CSS border radius attribute: +// radi.x = top-left, radi.y = top-right, radi.z = bottom-right, radi.w = bottom-left +fn select_border_radius(radi: vec4, position: vec2, center: vec2) -> f32 { + var rx = radi.x; + var ry = radi.y; + rx = select(radi.x, radi.y, position.x > center.x); + ry = select(radi.w, radi.z, position.x > center.x); + rx = select(rx, ry, position.y > center.y); + return rx; +} + +struct SolidVertexInput { @location(0) v_pos: vec2, - @location(1) pos: vec2, - @location(2) scale: vec2, - @location(3) color: vec4, + @location(1) color: vec4, + @location(2) pos: vec2, + @location(3) scale: vec2, @location(4) border_color: vec4, @location(5) border_radius: vec4, @location(6) border_width: f32, } -struct VertexOutput { +struct SolidVertexOutput { @builtin(position) position: vec4, @location(0) color: vec4, @location(1) border_color: vec4, @@ -26,8 +59,8 @@ struct VertexOutput { } @vertex -fn vs_main(input: VertexInput) -> VertexOutput { - var out: VertexOutput; +fn solid_vs_main(input: SolidVertexInput) -> SolidVertexOutput { + var out: SolidVertexOutput; var pos: vec2 = input.pos * globals.scale; var scale: vec2 = input.scale * globals.scale; @@ -47,54 +80,20 @@ fn vs_main(input: VertexInput) -> VertexOutput { vec4(pos - vec2(0.5, 0.5), 0.0, 1.0) ); + out.position = globals.transform * transform * vec4(input.v_pos, 0.0, 1.0); out.color = input.color; out.border_color = input.border_color; out.pos = pos; out.scale = scale; out.border_radius = border_radius * globals.scale; out.border_width = input.border_width * globals.scale; - out.position = globals.transform * transform * vec4(input.v_pos, 0.0, 1.0); return out; } -fn distance_alg( - frag_coord: vec2, - position: vec2, - size: vec2, - radius: f32 -) -> f32 { - var inner_size: vec2 = size - vec2(radius, radius) * 2.0; - var top_left: vec2 = position + vec2(radius, radius); - var bottom_right: vec2 = top_left + inner_size; - - var top_left_distance: vec2 = top_left - frag_coord; - var bottom_right_distance: vec2 = frag_coord - bottom_right; - - var dist: vec2 = vec2( - max(max(top_left_distance.x, bottom_right_distance.x), 0.0), - max(max(top_left_distance.y, bottom_right_distance.y), 0.0) - ); - - return sqrt(dist.x * dist.x + dist.y * dist.y); -} - -// Based on the fragement position and the center of the quad, select one of the 4 radi. -// Order matches CSS border radius attribute: -// radi.x = top-left, radi.y = top-right, radi.z = bottom-right, radi.w = bottom-left -fn select_border_radius(radi: vec4, position: vec2, center: vec2) -> f32 { - var rx = radi.x; - var ry = radi.y; - rx = select(radi.x, radi.y, position.x > center.x); - ry = select(radi.w, radi.z, position.x > center.x); - rx = select(rx, ry, position.y > center.y); - return rx; -} - - @fragment -fn fs_main( - input: VertexOutput +fn solid_fs_main( + input: SolidVertexOutput ) -> @location(0) vec4 { var mixed_color: vec4 = input.color; @@ -138,3 +137,208 @@ fn fs_main( return vec4(mixed_color.x, mixed_color.y, mixed_color.z, mixed_color.w * radius_alpha); } + +struct GradientVertexInput { + @location(0) v_pos: vec2, + @location(1) color_1: vec4, + @location(2) color_2: vec4, + @location(3) color_3: vec4, + @location(4) color_4: vec4, + @location(5) color_5: vec4, + @location(6) color_6: vec4, + @location(7) color_7: vec4, + @location(8) color_8: vec4, + @location(9) offsets_1: vec4, + @location(10) offsets_2: vec4, + @location(11) direction: vec4, + @location(12) position_and_scale: vec4, + @location(13) border_color: vec4, + @location(14) border_radius: vec4, + @location(15) border_width: f32 +} + +struct GradientVertexOutput { + @builtin(position) position: vec4, + @location(1) color_1: vec4, + @location(2) color_2: vec4, + @location(3) color_3: vec4, + @location(4) color_4: vec4, + @location(5) color_5: vec4, + @location(6) color_6: vec4, + @location(7) color_7: vec4, + @location(8) color_8: vec4, + @location(9) offsets_1: vec4, + @location(10) offsets_2: vec4, + @location(11) direction: vec4, + @location(12) position_and_scale: vec4, + @location(13) border_color: vec4, + @location(14) border_radius: vec4, + @location(15) border_width: f32 +} + +@vertex +fn gradient_vs_main(input: GradientVertexInput) -> GradientVertexOutput { + var out: GradientVertexOutput; + + var pos: vec2 = input.position_and_scale.xy * globals.scale; + var scale: vec2 = input.position_and_scale.zw * globals.scale; + + var min_border_radius = min(input.position_and_scale.z, input.position_and_scale.w) * 0.5; + var border_radius: vec4 = vec4( + min(input.border_radius.x, min_border_radius), + min(input.border_radius.y, min_border_radius), + min(input.border_radius.z, min_border_radius), + min(input.border_radius.w, min_border_radius) + ); + + var transform: mat4x4 = mat4x4( + vec4(scale.x + 1.0, 0.0, 0.0, 0.0), + vec4(0.0, scale.y + 1.0, 0.0, 0.0), + vec4(0.0, 0.0, 1.0, 0.0), + vec4(pos - vec2(0.5, 0.5), 0.0, 1.0) + ); + + out.position = globals.transform * transform * vec4(input.v_pos, 0.0, 1.0); + out.color_1 = input.color_1; + out.color_2 = input.color_2; + out.color_3 = input.color_3; + out.color_4 = input.color_4; + out.color_5 = input.color_5; + out.color_6 = input.color_6; + out.color_7 = input.color_7; + out.color_8 = input.color_8; + out.offsets_1 = input.offsets_1; + out.offsets_2 = input.offsets_2; + out.direction = input.direction * globals.scale; + out.position_and_scale = vec4(pos, scale); + out.border_color = input.border_color; + out.border_radius = border_radius * globals.scale; + out.border_width = input.border_width * globals.scale; + + return out; +} + +/// Returns the current interpolated color with a max 8-stop gradient +fn gradient( + raw_position: vec2, + direction: vec4, + colors: array, 8>, + offsets: array, + last_index: i32 +) -> vec4 { + let start = direction.xy; + let end = direction.zw; + + let v1 = end - start; + let v2 = raw_position - start; + let unit = normalize(v1); + let coord_offset = dot(unit, v2) / length(v1); + + //need to store these as a var to use dynamic indexing in a loop + //this is already added to wgsl spec but not in wgpu yet + var colors_arr = colors; + var offsets_arr = offsets; + + var color: vec4; + + for (var i: i32 = 0; i < last_index; i++) { + let curr_offset = offsets_arr[i]; + let next_offset = offsets_arr[i+1]; + + if (coord_offset <= offsets_arr[0]) { + color = colors_arr[0]; + } + + if (curr_offset <= coord_offset && coord_offset <= next_offset) { + color = mix(colors_arr[i], colors_arr[i+1], smoothstep( + curr_offset, + next_offset, + coord_offset, + )); + } + + if (coord_offset >= offsets_arr[last_index]) { + color = colors_arr[last_index]; + } + } + + return color; +} + +@fragment +fn gradient_fs_main(input: GradientVertexOutput) -> @location(0) vec4 { + let colors = array, 8>( + input.color_1, + input.color_2, + input.color_3, + input.color_4, + input.color_5, + input.color_6, + input.color_7, + input.color_8, + ); + + var offsets = array( + input.offsets_1.x, + input.offsets_1.y, + input.offsets_1.z, + input.offsets_1.w, + input.offsets_2.x, + input.offsets_2.y, + input.offsets_2.z, + input.offsets_2.w, + ); + + //TODO could just pass this in to the shader but is probably more performant to just check it here + var last_index = 7; + for (var i: i32 = 0; i <= 7; i++) { + if (offsets[i] > 1.0) { + last_index = i - 1; + break; + } + } + + var mixed_color: vec4 = gradient(input.position.xy, input.direction, colors, offsets, last_index); + + let pos = input.position_and_scale.xy; + let scale = input.position_and_scale.zw; + + var border_radius = select_border_radius( + input.border_radius, + input.position.xy, + (pos + scale * 0.5).xy + ); + + if (input.border_width > 0.0) { + var internal_border: f32 = max(border_radius - input.border_width, 0.0); + + var internal_distance: f32 = distance_alg( + input.position.xy, + pos + vec2(input.border_width, input.border_width), + scale - vec2(input.border_width * 2.0, input.border_width * 2.0), + internal_border + ); + + var border_mix: f32 = smoothstep( + max(internal_border - 0.5, 0.0), + internal_border + 0.5, + internal_distance + ); + + mixed_color = mix(mixed_color, input.border_color, vec4(border_mix, border_mix, border_mix, border_mix)); + } + + var dist: f32 = distance_alg( + input.position.xy, + pos, + scale, + border_radius + ); + + var radius_alpha: f32 = 1.0 - smoothstep( + max(border_radius - 0.5, 0.0), + border_radius + 0.5, + dist); + + return vec4(mixed_color.x, mixed_color.y, mixed_color.z, mixed_color.w * radius_alpha); +} diff --git a/wgpu/src/shader/triangle/gradient.wgsl b/wgpu/src/shader/triangle/gradient.wgsl new file mode 100644 index 0000000000..24c330a2e5 --- /dev/null +++ b/wgpu/src/shader/triangle/gradient.wgsl @@ -0,0 +1,139 @@ +struct Globals { + transform: mat4x4, +} + +@group(0) @binding(0) var globals: Globals; + +struct GradientVertexOutput { + @builtin(position) position: vec4, + @location(0) raw_position: vec2, + @location(1) color_1: vec4, + @location(2) color_2: vec4, + @location(3) color_3: vec4, + @location(4) color_4: vec4, + @location(5) color_5: vec4, + @location(6) color_6: vec4, + @location(7) color_7: vec4, + @location(8) color_8: vec4, + @location(9) offsets_1: vec4, + @location(10) offsets_2: vec4, + @location(11) direction: vec4, +} + +@vertex +fn vs_main( + @location(0) input: vec2, + @location(1) color_1: vec4, + @location(2) color_2: vec4, + @location(3) color_3: vec4, + @location(4) color_4: vec4, + @location(5) color_5: vec4, + @location(6) color_6: vec4, + @location(7) color_7: vec4, + @location(8) color_8: vec4, + @location(9) offsets_1: vec4, + @location(10) offsets_2: vec4, + @location(11) direction: vec4, +) -> GradientVertexOutput { + var output: GradientVertexOutput; + + output.position = globals.transform * vec4(input.xy, 0.0, 1.0); + output.raw_position = input; + //pass gradient data to frag shader + output.color_1 = color_1; + output.color_2 = color_2; + output.color_3 = color_3; + output.color_4 = color_4; + output.color_5 = color_5; + output.color_6 = color_6; + output.color_7 = color_7; + output.color_8 = color_8; + output.offsets_1 = offsets_1; + output.offsets_2 = offsets_2; + output.direction = direction; + + return output; +} + +/// Returns the current interpolated color with a max 8-stop gradient +fn gradient( + raw_position: vec2, + direction: vec4, + colors: array, 8>, + offsets: array, + last_index: i32 +) -> vec4 { + let start = direction.xy; + let end = direction.zw; + + let v1 = end - start; + let v2 = raw_position - start; + let unit = normalize(v1); + let coord_offset = dot(unit, v2) / length(v1); + + //need to store these as a var to use dynamic indexing in a loop + //this is already added to wgsl spec but not in wgpu yet + var colors_arr = colors; + var offsets_arr = offsets; + + var color: vec4; + + for (var i: i32 = 0; i < last_index; i++) { + let curr_offset = offsets_arr[i]; + let next_offset = offsets_arr[i+1]; + + if (coord_offset <= offsets_arr[0]) { + color = colors_arr[0]; + } + + if (curr_offset <= coord_offset && coord_offset <= next_offset) { + color = mix(colors_arr[i], colors_arr[i+1], smoothstep( + curr_offset, + next_offset, + coord_offset, + )); + } + + if (coord_offset >= offsets_arr[last_index]) { + color = colors_arr[last_index]; + } + } + + return color; +} + +@fragment +fn fs_main(input: GradientVertexOutput) -> @location(0) vec4 { + let colors = array, 8>( + input.color_1, + input.color_2, + input.color_3, + input.color_4, + input.color_5, + input.color_6, + input.color_7, + input.color_8, + ); + + var offsets = array( + input.offsets_1.x, + input.offsets_1.y, + input.offsets_1.z, + input.offsets_1.w, + input.offsets_2.x, + input.offsets_2.y, + input.offsets_2.z, + input.offsets_2.w, + ); + + //TODO could just pass this in to the shader but is probably more performant to just check it here + var last_index = 7; + for (var i: i32 = 0; i <= 7; i++) { + if (offsets[i] >= 1.0) { + last_index = i; + break; + } + } + + return gradient(input.raw_position, input.direction, colors, offsets, last_index); +} diff --git a/wgpu/src/shader/solid.wgsl b/wgpu/src/shader/triangle/solid.wgsl similarity index 67% rename from wgpu/src/shader/solid.wgsl rename to wgpu/src/shader/triangle/solid.wgsl index b24402f832..808f7eaa60 100644 --- a/wgpu/src/shader/solid.wgsl +++ b/wgpu/src/shader/triangle/solid.wgsl @@ -4,19 +4,19 @@ struct Globals { @group(0) @binding(0) var globals: Globals; -struct VertexInput { +struct SolidVertexInput { @location(0) position: vec2, @location(1) color: vec4, } -struct VertexOutput { +struct SolidVertexOutput { @builtin(position) position: vec4, @location(0) color: vec4, } @vertex -fn vs_main(input: VertexInput) -> VertexOutput { - var out: VertexOutput; +fn vs_main(input: SolidVertexInput) -> SolidVertexOutput { + var out: SolidVertexOutput; out.color = input.color; out.position = globals.transform * vec4(input.position, 0.0, 1.0); @@ -25,6 +25,6 @@ fn vs_main(input: VertexInput) -> VertexOutput { } @fragment -fn fs_main(input: VertexOutput) -> @location(0) vec4 { +fn fs_main(input: SolidVertexOutput) -> @location(0) vec4 { return input.color; } diff --git a/wgpu/src/triangle.rs b/wgpu/src/triangle.rs index b33b488a44..36b5d70ea0 100644 --- a/wgpu/src/triangle.rs +++ b/wgpu/src/triangle.rs @@ -1,23 +1,21 @@ //! Draw meshes of triangles. mod msaa; -use crate::buffer::r#static::Buffer; -use crate::settings; use crate::Transformation; +use crate::{buffer, settings}; use iced_graphics::layer::mesh::{self, Mesh}; use iced_graphics::triangle::ColoredVertex2D; use iced_graphics::Size; +const INITIAL_BUFFER_COUNT: usize = 10_000; + #[derive(Debug)] pub struct Pipeline { blit: Option, - index_buffer: Buffer, + index_buffer: buffer::Static, index_strides: Vec, solid: solid::Pipeline, - - /// Gradients are currently not supported on WASM targets due to their need of storage buffers. - #[cfg(not(target_arch = "wasm32"))] gradient: gradient::Pipeline, } @@ -29,15 +27,14 @@ impl Pipeline { ) -> Pipeline { Pipeline { blit: antialiasing.map(|a| msaa::Blit::new(device, format, a)), - index_buffer: Buffer::new( + index_buffer: buffer::Static::new( device, "iced_wgpu::triangle vertex buffer", wgpu::BufferUsages::INDEX | wgpu::BufferUsages::COPY_DST, + INITIAL_BUFFER_COUNT, ), index_strides: Vec::new(), solid: solid::Pipeline::new(device, format, antialiasing), - - #[cfg(not(target_arch = "wasm32"))] gradient: gradient::Pipeline::new(device, format, antialiasing), } } @@ -62,30 +59,21 @@ impl Pipeline { // the majority of use cases. Therefore we will write GPU data every frame (for now). let _ = self.index_buffer.resize(device, count.indices); let _ = self.solid.vertices.resize(device, count.solid_vertices); - - #[cfg(not(target_arch = "wasm32"))] let _ = self .gradient .vertices .resize(device, count.gradient_vertices); - // Prepare dynamic buffers & data store for writing self.index_strides.clear(); self.solid.vertices.clear(); + self.gradient.vertices.clear(); + // Prepare dynamic buffers & data store for writing self.solid.uniforms.clear(); - - #[cfg(not(target_arch = "wasm32"))] - { - self.gradient.uniforms.clear(); - self.gradient.vertices.clear(); - self.gradient.storage.clear(); - } + self.gradient.uniforms.clear(); let mut solid_vertex_offset = 0; - let mut index_offset = 0; - - #[cfg(not(target_arch = "wasm32"))] let mut gradient_vertex_offset = 0; + let mut index_offset = 0; for mesh in meshes { let origin = mesh.origin(); @@ -108,7 +96,7 @@ impl Pipeline { //push uniform data to CPU buffers match mesh { Mesh::Solid { buffers, .. } => { - self.solid.uniforms.push(&solid::Uniforms::new(transform)); + self.solid.uniforms.push(&Uniforms::new(transform)); let written_bytes = self.solid.vertices.write( device, @@ -120,10 +108,11 @@ impl Pipeline { solid_vertex_offset += written_bytes; } - #[cfg(not(target_arch = "wasm32"))] - Mesh::Gradient { - buffers, gradient, .. - } => { + Mesh::Gradient { buffers, .. } => { + self.gradient.uniforms.push(&Uniforms { + transform: transform.into(), + }); + let written_bytes = self.gradient.vertices.write( device, staging_belt, @@ -133,56 +122,7 @@ impl Pipeline { ); gradient_vertex_offset += written_bytes; - - match gradient { - iced_graphics::Gradient::Linear(linear) => { - use glam::{IVec4, Vec4}; - - let start_offset = self.gradient.color_stop_offset; - let end_offset = (linear.color_stops.len() as i32) - + start_offset - - 1; - - self.gradient.uniforms.push(&gradient::Uniforms { - transform: transform.into(), - direction: Vec4::new( - linear.start.x, - linear.start.y, - linear.end.x, - linear.end.y, - ), - stop_range: IVec4::new( - start_offset, - end_offset, - 0, - 0, - ), - }); - - self.gradient.color_stop_offset = end_offset + 1; - - let stops: Vec = linear - .color_stops - .iter() - .map(|stop| { - let [r, g, b, a] = stop.color.into_linear(); - - gradient::ColorStop { - offset: stop.offset, - color: Vec4::new(r, g, b, a), - } - }) - .collect(); - - self.gradient - .color_stops_pending_write - .color_stops - .extend(stops); - } - } } - #[cfg(target_arch = "wasm32")] - Mesh::Gradient { .. } => {} } } @@ -201,33 +141,20 @@ impl Pipeline { self.solid.uniforms.write(device, staging_belt, encoder); } - #[cfg(not(target_arch = "wasm32"))] if count.gradient_vertices > 0 { - // First write the pending color stops to the CPU buffer - self.gradient - .storage - .push(&self.gradient.color_stops_pending_write); - // Resize buffers if needed let uniforms_resized = self.gradient.uniforms.resize(device); - let storage_resized = self.gradient.storage.resize(device); - if uniforms_resized || storage_resized { + if uniforms_resized { self.gradient.bind_group = gradient::Pipeline::bind_group( device, self.gradient.uniforms.raw(), - self.gradient.storage.raw(), &self.gradient.bind_group_layout, ); } // Write to GPU self.gradient.uniforms.write(device, staging_belt, encoder); - self.gradient.storage.write(device, staging_belt, encoder); - - // Cleanup - self.gradient.color_stop_offset = 0; - self.gradient.color_stops_pending_write.color_stops.clear(); } // Configure render pass @@ -261,7 +188,6 @@ impl Pipeline { }); let mut num_solids = 0; - #[cfg(not(target_arch = "wasm32"))] let mut num_gradients = 0; let mut last_is_solid = None; @@ -296,7 +222,6 @@ impl Pipeline { num_solids += 1; } - #[cfg(not(target_arch = "wasm32"))] Mesh::Gradient { .. } => { if last_is_solid.unwrap_or(true) { render_pass.set_pipeline(&self.gradient.pipeline); @@ -322,8 +247,6 @@ impl Pipeline { num_gradients += 1; } - #[cfg(target_arch = "wasm32")] - Mesh::Gradient { .. } => {} }; render_pass.set_index_buffer( @@ -375,50 +298,49 @@ fn multisample_state( } } +#[derive(Debug, Clone, Copy, encase::ShaderType)] +pub struct Uniforms { + transform: glam::Mat4, +} + +impl Uniforms { + pub fn new(transform: Transformation) -> Self { + Self { + transform: transform.into(), + } + } +} + mod solid { - use crate::buffer::dynamic; - use crate::buffer::r#static::Buffer; - use crate::settings; use crate::triangle; + use crate::triangle::{Uniforms, INITIAL_BUFFER_COUNT}; + use crate::{buffer, settings}; use encase::ShaderType; - use iced_graphics::Transformation; #[derive(Debug)] pub struct Pipeline { pub pipeline: wgpu::RenderPipeline, - pub vertices: Buffer, - pub uniforms: dynamic::Buffer, + pub vertices: buffer::Static, + pub uniforms: buffer::DynamicUniform, pub bind_group_layout: wgpu::BindGroupLayout, pub bind_group: wgpu::BindGroup, } - #[derive(Debug, Clone, Copy, ShaderType)] - pub struct Uniforms { - transform: glam::Mat4, - } - - impl Uniforms { - pub fn new(transform: Transformation) -> Self { - Self { - transform: transform.into(), - } - } - } - impl Pipeline { - /// Creates a new [SolidPipeline] using `solid.wgsl` shader. + /// Creates a new [SolidPipeline] using `triangle/solid.wgsl` shader. pub fn new( device: &wgpu::Device, format: wgpu::TextureFormat, antialiasing: Option, ) -> Self { - let vertices = Buffer::new( + let vertices = buffer::Static::new( device, "iced_wgpu::triangle::solid vertex buffer", wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST, + INITIAL_BUFFER_COUNT, ); - let uniforms = dynamic::Buffer::uniform( + let uniforms = buffer::DynamicUniform::new( device, "iced_wgpu::triangle::solid uniforms", ); @@ -457,7 +379,7 @@ mod solid { ), source: wgpu::ShaderSource::Wgsl( std::borrow::Cow::Borrowed(include_str!( - "shader/solid.wgsl" + "shader/triangle/solid.wgsl" )), ), }); @@ -527,113 +449,63 @@ mod solid { } } -#[cfg(not(target_arch = "wasm32"))] mod gradient { - use crate::buffer::dynamic; - use crate::buffer::r#static::Buffer; - use crate::settings; use crate::triangle; + use crate::{buffer, settings}; + use crate::triangle::{Uniforms, INITIAL_BUFFER_COUNT}; use encase::ShaderType; - use glam::{IVec4, Vec4}; - use iced_graphics::triangle::Vertex2D; + use iced_graphics::triangle::GradientVertex2D; #[derive(Debug)] pub struct Pipeline { pub pipeline: wgpu::RenderPipeline, - pub vertices: Buffer, - pub uniforms: dynamic::Buffer, - pub storage: dynamic::Buffer, - pub color_stop_offset: i32, - //Need to store these and then write them all at once - //or else they will be padded to 256 and cause gaps in the storage buffer - pub color_stops_pending_write: Storage, + pub vertices: buffer::Static, + pub uniforms: buffer::DynamicUniform, pub bind_group_layout: wgpu::BindGroupLayout, pub bind_group: wgpu::BindGroup, } - #[derive(Debug, ShaderType)] - pub struct Uniforms { - pub transform: glam::Mat4, - //xy = start, zw = end - pub direction: Vec4, - //x = start stop, y = end stop, zw = padding - pub stop_range: IVec4, - } - - #[derive(Debug, ShaderType)] - pub struct ColorStop { - pub color: Vec4, - pub offset: f32, - } - - #[derive(Debug, ShaderType)] - pub struct Storage { - #[size(runtime)] - pub color_stops: Vec, - } - impl Pipeline { - /// Creates a new [GradientPipeline] using `gradient.wgsl` shader. + /// Creates a new [GradientPipeline] using `triangle/gradient.wgsl` shader. pub(super) fn new( device: &wgpu::Device, format: wgpu::TextureFormat, antialiasing: Option, ) -> Self { - let vertices = Buffer::new( + let vertices = buffer::Static::new( device, "iced_wgpu::triangle::gradient vertex buffer", wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST, + INITIAL_BUFFER_COUNT, ); - let uniforms = dynamic::Buffer::uniform( + let uniforms = buffer::DynamicUniform::new( device, "iced_wgpu::triangle::gradient uniforms", ); - //Note: with a WASM target storage buffers are not supported. Will need to use UBOs & static - // sized array (eg like the 32-sized array on OpenGL side right now) to make gradients work - let storage = dynamic::Buffer::storage( - device, - "iced_wgpu::triangle::gradient storage", - ); - let bind_group_layout = device.create_bind_group_layout( &wgpu::BindGroupLayoutDescriptor { label: Some( "iced_wgpu::triangle::gradient bind group layout", ), - entries: &[ - wgpu::BindGroupLayoutEntry { - binding: 0, - visibility: wgpu::ShaderStages::VERTEX_FRAGMENT, - ty: wgpu::BindingType::Buffer { - ty: wgpu::BufferBindingType::Uniform, - has_dynamic_offset: true, - min_binding_size: Some(Uniforms::min_size()), - }, - count: None, - }, - wgpu::BindGroupLayoutEntry { - binding: 1, - visibility: wgpu::ShaderStages::FRAGMENT, - ty: wgpu::BindingType::Buffer { - ty: wgpu::BufferBindingType::Storage { - read_only: true, - }, - has_dynamic_offset: false, - min_binding_size: Some(Storage::min_size()), - }, - count: None, + entries: &[wgpu::BindGroupLayoutEntry { + binding: 0, + visibility: wgpu::ShaderStages::VERTEX_FRAGMENT, + ty: wgpu::BindingType::Buffer { + ty: wgpu::BufferBindingType::Uniform, + has_dynamic_offset: true, + min_binding_size: Some(Uniforms::min_size()), }, - ], + count: None, + }], }, ); let bind_group = Pipeline::bind_group( device, uniforms.raw(), - storage.raw(), &bind_group_layout, ); @@ -654,7 +526,7 @@ mod gradient { ), source: wgpu::ShaderSource::Wgsl( std::borrow::Cow::Borrowed(include_str!( - "shader/gradient.wgsl" + "shader/triangle/gradient.wgsl" )), ), }); @@ -667,12 +539,34 @@ mod gradient { module: &shader, entry_point: "vs_main", buffers: &[wgpu::VertexBufferLayout { - array_stride: std::mem::size_of::() - as u64, + array_stride: std::mem::size_of::( + ) as u64, step_mode: wgpu::VertexStepMode::Vertex, attributes: &wgpu::vertex_attr_array!( // Position 0 => Float32x2, + // Color 1 + 1 => Float32x4, + // Color 2 + 2 => Float32x4, + // Color 3 + 3 => Float32x4, + // Color 4 + 4 => Float32x4, + // Color 5 + 5 => Float32x4, + // Color 6 + 6 => Float32x4, + // Color 7 + 7 => Float32x4, + // Color 8 + 8 => Float32x4, + // Offsets 1-4 + 9 => Float32x4, + // Offsets 5-8 + 10 => Float32x4, + // Direction + 11 => Float32x4 ), }], }, @@ -692,11 +586,6 @@ mod gradient { pipeline, vertices, uniforms, - storage, - color_stop_offset: 0, - color_stops_pending_write: Storage { - color_stops: vec![], - }, bind_group_layout, bind_group, } @@ -705,28 +594,21 @@ mod gradient { pub fn bind_group( device: &wgpu::Device, uniform_buffer: &wgpu::Buffer, - storage_buffer: &wgpu::Buffer, layout: &wgpu::BindGroupLayout, ) -> wgpu::BindGroup { device.create_bind_group(&wgpu::BindGroupDescriptor { label: Some("iced_wgpu::triangle::gradient bind group"), layout, - entries: &[ - wgpu::BindGroupEntry { - binding: 0, - resource: wgpu::BindingResource::Buffer( - wgpu::BufferBinding { - buffer: uniform_buffer, - offset: 0, - size: Some(Uniforms::min_size()), - }, - ), - }, - wgpu::BindGroupEntry { - binding: 1, - resource: storage_buffer.as_entire_binding(), - }, - ], + entries: &[wgpu::BindGroupEntry { + binding: 0, + resource: wgpu::BindingResource::Buffer( + wgpu::BufferBinding { + buffer: uniform_buffer, + offset: 0, + size: Some(Uniforms::min_size()), + }, + ), + }], }) } }