summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLibravatar Bingus <shankern@protonmail.com>2023-05-11 09:12:06 -0700
committerLibravatar Bingus <shankern@protonmail.com>2023-05-11 11:13:44 -0700
commit6551a0b2ab6c831dd1d3646ecf55180339275e22 (patch)
treede1e4a85b3176f94fd006fed190ef035d3202e49
parent669f7cc74b2e7918e86a8197916f503f2d3d9b93 (diff)
downloadiced-6551a0b2ab6c831dd1d3646ecf55180339275e22.tar.gz
iced-6551a0b2ab6c831dd1d3646ecf55180339275e22.tar.bz2
iced-6551a0b2ab6c831dd1d3646ecf55180339275e22.zip
Added support for gradients as background variants + other optimizations.
-rw-r--r--core/Cargo.toml1
-rw-r--r--core/src/angle.rs33
-rw-r--r--core/src/background.rs14
-rw-r--r--core/src/gradient.rs176
-rw-r--r--core/src/gradient/linear.rs112
-rw-r--r--core/src/lib.rs2
-rw-r--r--core/src/renderer.rs2
-rw-r--r--examples/modern_art/Cargo.toml11
-rw-r--r--examples/modern_art/src/main.rs143
-rw-r--r--examples/solar_system/Cargo.toml1
-rw-r--r--examples/solar_system/src/main.rs21
-rw-r--r--examples/tour/Cargo.toml2
-rw-r--r--examples/tour/src/main.rs58
-rw-r--r--graphics/src/geometry/fill.rs3
-rw-r--r--graphics/src/geometry/style.rs3
-rw-r--r--graphics/src/gradient.rs119
-rw-r--r--graphics/src/lib.rs2
-rw-r--r--graphics/src/primitive.rs32
-rw-r--r--graphics/src/triangle.rs1
-rw-r--r--src/lib.rs4
-rw-r--r--style/src/button.rs3
-rw-r--r--style/src/theme.rs3
-rw-r--r--tiny_skia/src/backend.rs45
-rw-r--r--tiny_skia/src/geometry.rs44
-rw-r--r--wgpu/Cargo.toml4
-rw-r--r--wgpu/src/buffer.rs43
-rw-r--r--wgpu/src/buffer/dynamic.rs202
-rw-r--r--wgpu/src/buffer/static.rs107
-rw-r--r--wgpu/src/geometry.rs111
-rw-r--r--wgpu/src/image.rs46
-rw-r--r--wgpu/src/layer.rs94
-rw-r--r--wgpu/src/layer/mesh.rs15
-rw-r--r--wgpu/src/layer/quad.rs41
-rw-r--r--wgpu/src/lib.rs2
-rw-r--r--wgpu/src/quad.rs481
-rw-r--r--wgpu/src/shader/gradient.wgsl88
-rw-r--r--wgpu/src/shader/quad.wgsl298
-rw-r--r--wgpu/src/shader/solid.wgsl30
-rw-r--r--wgpu/src/shader/triangle.wgsl168
-rw-r--r--wgpu/src/triangle.rs543
-rw-r--r--widget/src/canvas.rs1
41 files changed, 1639 insertions, 1470 deletions
diff --git a/core/Cargo.toml b/core/Cargo.toml
index 92d9773f..41b0640a 100644
--- a/core/Cargo.toml
+++ b/core/Cargo.toml
@@ -10,6 +10,7 @@ repository = "https://github.com/iced-rs/iced"
[dependencies]
bitflags = "1.2"
thiserror = "1"
+log = "0.4.17"
twox-hash = { version = "1.5", default-features = false }
[dependencies.palette]
diff --git a/core/src/angle.rs b/core/src/angle.rs
new file mode 100644
index 00000000..75a57c76
--- /dev/null
+++ b/core/src/angle.rs
@@ -0,0 +1,33 @@
+use crate::{Point, Rectangle, Vector};
+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<Degrees> for Radians {
+ fn from(degrees: Degrees) -> Self {
+ Radians(degrees.0 * PI / 180.0)
+ }
+}
+
+impl Radians {
+ /// Calculates the line in which the [`Angle`] intercepts the `bounds`.
+ pub fn to_distance(&self, bounds: &Rectangle) -> (Point, Point) {
+ let v1 = Vector::new(f32::cos(self.0), f32::sin(self.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;
+
+ (start, end)
+ }
+}
diff --git a/core/src/background.rs b/core/src/background.rs
index cfb95867..6af33c3e 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
+ /// Linearly interpolate between several colors.
+ Gradient(Gradient),
+ // TODO: Add image variant
}
impl From<Color> for Background {
@@ -19,3 +21,9 @@ impl From<Color> for Option<Background> {
Some(Background::from(color))
}
}
+
+impl From<Gradient> for Option<Background> {
+ fn from(gradient: Gradient) -> Self {
+ Some(Background::Gradient(gradient))
+ }
+}
diff --git a/core/src/gradient.rs b/core/src/gradient.rs
index 61e919d6..54bb86a4 100644
--- a/core/src/gradient.rs
+++ b/core/src/gradient.rs
@@ -1,27 +1,42 @@
//! For creating a Gradient.
-pub mod linear;
-
pub use linear::Linear;
-use crate::{Color, Point, Size};
+use crate::{Color, Radians};
-#[derive(Debug, Clone, PartialEq)]
+#[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 from its `start` to its `end`
- /// point.
+ /// A linear gradient interpolates colors along a direction at a specific [`Angle`].
Linear(Linear),
}
impl Gradient {
/// Creates a new linear [`linear::Builder`].
- pub fn linear(position: impl Into<Position>) -> linear::Builder {
- linear::Builder::new(position.into())
+ ///
+ /// This must 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<Radians>) -> linear::Builder {
+ linear::Builder::new(angle.into())
+ }
+
+ /// Adjust the opacity of the gradient by a multiplier applied to each color stop.
+ pub fn mul_alpha(mut self, alpha_multiplier: f32) -> Self {
+ match &mut self {
+ Gradient::Linear(linear) => {
+ for stop in linear.color_stops.iter_mut().flatten() {
+ stop.color.a *= alpha_multiplier;
+ }
+ }
+ }
+
+ self
}
}
-#[derive(Debug, Clone, Copy, PartialEq)]
+#[derive(Debug, Default, Clone, Copy, PartialEq)]
/// A point along the gradient vector where the specified [`color`] is unmixed.
///
/// [`color`]: Self::color
@@ -35,83 +50,84 @@ pub struct ColorStop {
pub color: Color,
}
-#[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,
- },
-}
+pub mod linear {
+ //! Linear gradient builder & definition.
+ use crate::gradient::{ColorStop, Gradient};
+ use crate::{Color, Radians};
+ use std::cmp::Ordering;
-impl From<(Point, Point)> for Position {
- fn from((start, end): (Point, Point)) -> Self {
- Self::Absolute { start, end }
+ /// 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: [Option<ColorStop>; 8],
}
-}
-#[derive(Debug, Clone, Copy)]
-/// 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 {
+ angle: Radians,
+ stops: [Option<ColorStop>; 8],
+ }
-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)
- }
- 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)
+ impl Builder {
+ /// Creates a new [`Builder`].
+ pub fn new(angle: Radians) -> Self {
+ Self {
+ angle,
+ stops: [None; 8],
}
- Location::Left => {
- Point::new(top_left.x, top_left.y + size.height / 2.0)
+ }
+
+ /// Adds a new [`ColorStop`], defined by an offset and a color, to the gradient.
+ ///
+ /// Any `offset` that is not within `0.0..=1.0` will be silently ignored.
+ ///
+ /// 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) {
+ let (Ok(index) | Err(index)) =
+ self.stops.binary_search_by(|stop| match stop {
+ None => Ordering::Greater,
+ Some(stop) => stop.offset.partial_cmp(&offset).unwrap(),
+ });
+
+ if index < 8 {
+ self.stops[index] = Some(ColorStop { offset, color });
+ }
+ } else {
+ log::warn!(
+ "Gradient color stop must be within 0.0..=1.0 range."
+ );
+ };
+
+ 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<Item = ColorStop>,
+ ) -> 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) -> Gradient {
+ Gradient::Linear(Linear {
+ angle: self.angle,
+ color_stops: self.stops,
+ })
}
}
}
diff --git a/core/src/gradient/linear.rs b/core/src/gradient/linear.rs
deleted file mode 100644
index c886db47..00000000
--- a/core/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<ColorStop>,
-}
-
-/// A [`Linear`] builder.
-#[derive(Debug)]
-pub struct Builder {
- start: Point,
- end: Point,
- stops: Vec<ColorStop>,
- error: Option<BuilderError>,
-}
-
-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<Gradient, BuilderError> {
- 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/core/src/lib.rs b/core/src/lib.rs
index 89dfb828..6de5ada4 100644
--- a/core/src/lib.rs
+++ b/core/src/lib.rs
@@ -42,6 +42,7 @@ pub mod touch;
pub mod widget;
pub mod window;
+mod angle;
mod background;
mod color;
mod content_fit;
@@ -57,6 +58,7 @@ mod size;
mod vector;
pub use alignment::Alignment;
+pub use angle::{Degrees, Radians};
pub use background::Background;
pub use clipboard::Clipboard;
pub use color::Color;
diff --git a/core/src/renderer.rs b/core/src/renderer.rs
index d6247e39..007a0370 100644
--- a/core/src/renderer.rs
+++ b/core/src/renderer.rs
@@ -60,7 +60,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/examples/modern_art/Cargo.toml b/examples/modern_art/Cargo.toml
deleted file mode 100644
index 4242d209..00000000
--- a/examples/modern_art/Cargo.toml
+++ /dev/null
@@ -1,11 +0,0 @@
-[package]
-name = "modern_art"
-version = "0.1.0"
-authors = ["Bingus <shankern@protonmail.com>"]
-edition = "2021"
-publish = false
-
-[dependencies]
-iced = { path = "../..", features = ["canvas", "tokio", "debug"] }
-rand = "0.8.5"
-env_logger = "0.9"
diff --git a/examples/modern_art/src/main.rs b/examples/modern_art/src/main.rs
deleted file mode 100644
index a43a2b2b..00000000
--- a/examples/modern_art/src/main.rs
+++ /dev/null
@@ -1,143 +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<Self::Message>) {
- (
- ModernArt {
- cache: Default::default(),
- },
- Command::none(),
- )
- }
-
- fn title(&self) -> String {
- String::from("Modern Art")
- }
-
- fn update(&mut self, _message: Message) -> Command<Message> {
- Command::none()
- }
-
- fn view(&self) -> Element<'_, Self::Message, Renderer<Self::Theme>> {
- Canvas::new(self)
- .width(Length::Fill)
- .height(Length::Fill)
- .into()
- }
-}
-
-impl<Message> canvas::Program<Message, Renderer> for ModernArt {
- type State = ();
-
- fn draw(
- &self,
- _state: &Self::State,
- renderer: &Renderer,
- _theme: &Theme,
- bounds: Rectangle,
- _cursor: Cursor,
- ) -> Vec<Geometry> {
- let geometry = self.cache.draw(renderer, 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::<bool>();
-
- 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/Cargo.toml b/examples/solar_system/Cargo.toml
index 835396b0..1a98a87e 100644
--- a/examples/solar_system/Cargo.toml
+++ b/examples/solar_system/Cargo.toml
@@ -7,4 +7,5 @@ publish = false
[dependencies]
iced = { path = "../..", features = ["canvas", "tokio", "debug"] }
+env_logger = "0.10.0"
rand = "0.8.3"
diff --git a/examples/solar_system/src/main.rs b/examples/solar_system/src/main.rs
index f2606feb..42606e3f 100644
--- a/examples/solar_system/src/main.rs
+++ b/examples/solar_system/src/main.rs
@@ -10,9 +10,8 @@ use iced::application;
use iced::executor;
use iced::theme::{self, Theme};
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, Renderer,
@@ -22,6 +21,8 @@ use iced::{
use std::time::Instant;
pub fn main() -> iced::Result {
+ env_logger::builder().format_timestamp(None).init();
+
SolarSystem::run(Settings {
antialiasing: true,
..Settings::default()
@@ -208,15 +209,13 @@ impl<Message> canvas::Program<Message> 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();
frame.fill(&earth, earth_fill);
diff --git a/examples/tour/Cargo.toml b/examples/tour/Cargo.toml
index 39e83671..48471f2d 100644
--- a/examples/tour/Cargo.toml
+++ b/examples/tour/Cargo.toml
@@ -7,4 +7,4 @@ publish = false
[dependencies]
iced = { path = "../..", features = ["image", "debug"] }
-env_logger = "0.8"
+env_logger = "0.10.0"
diff --git a/examples/tour/src/main.rs b/examples/tour/src/main.rs
index 9c38ad0e..630b6359 100644
--- a/examples/tour/src/main.rs
+++ b/examples/tour/src/main.rs
@@ -1,11 +1,15 @@
-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::{Color, Element, Font, Length, Renderer, Sandbox, Settings};
+use iced::{alignment, widget, Theme};
+use iced::{
+ Color, Degrees, Element, Font, Gradient, Length, Radians, Renderer,
+ Sandbox, Settings,
+};
pub fn main() -> iced::Result {
env_logger::init();
@@ -53,9 +57,11 @@ 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::Secondary,
+ )),
+ ),
);
}
@@ -63,9 +69,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::Primary)),
+ ),
);
}
@@ -716,3 +722,39 @@ pub enum Layout {
Row,
Column,
}
+
+enum CustomButtonStyle {
+ Primary,
+ Secondary,
+}
+
+impl widget::button::StyleSheet for CustomButtonStyle {
+ type Style = Theme;
+
+ fn active(&self, _style: &Self::Style) -> widget::button::Appearance {
+ match self {
+ CustomButtonStyle::Primary => 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()
+ .into(),
+ text_color: Color::WHITE,
+ border_radius: 5.0,
+ ..Default::default()
+ },
+ CustomButtonStyle::Secondary => widget::button::Appearance {
+ background: Gradient::linear(Radians(
+ 3.0 * std::f32::consts::PI / 2.0,
+ ))
+ .add_stop(0.0, Color::from_rgb8(194, 194, 194))
+ .add_stop(1.0, Color::from_rgb8(126, 126, 126))
+ .build()
+ .into(),
+ text_color: Color::WHITE,
+ border_radius: 5.0,
+ ..Default::default()
+ },
+ }
+ }
+}
diff --git a/graphics/src/geometry/fill.rs b/graphics/src/geometry/fill.rs
index 2e8c1669..6aa1f28b 100644
--- a/graphics/src/geometry/fill.rs
+++ b/graphics/src/geometry/fill.rs
@@ -1,7 +1,8 @@
//! Fill [crate::widget::canvas::Geometry] with a certain style.
-use iced_core::{Color, Gradient};
+use iced_core::Color;
pub use crate::geometry::Style;
+use crate::Gradient;
/// The style used to fill geometry.
#[derive(Debug, Clone)]
diff --git a/graphics/src/geometry/style.rs b/graphics/src/geometry/style.rs
index be9ee376..ece6b32a 100644
--- a/graphics/src/geometry/style.rs
+++ b/graphics/src/geometry/style.rs
@@ -1,4 +1,5 @@
-use iced_core::{Color, Gradient};
+use crate::Gradient;
+use iced_core::Color;
/// The coloring style of some drawing.
#[derive(Debug, Clone, PartialEq)]
diff --git a/graphics/src/gradient.rs b/graphics/src/gradient.rs
new file mode 100644
index 00000000..21bcd2c6
--- /dev/null
+++ b/graphics/src/gradient.rs
@@ -0,0 +1,119 @@
+//! A gradient that can be used as a [`Fill`] for a mesh.
+//!
+//! For a gradient that you can use as a background variant for a widget, see [`Gradient`].
+//!
+//! [`Gradient`]: crate::core::Gradient;
+use crate::core::Point;
+pub use linear::Linear;
+
+#[derive(Debug, Clone, PartialEq)]
+/// A fill which linearly interpolates colors along a direction.
+///
+/// For a gradient which can be used as a fill for a background of a widget, see [`crate::core::Gradient`].
+pub enum Gradient {
+ /// A linear gradient interpolates colors along a direction from its `start` to its `end`
+ /// point.
+ Linear(Linear),
+}
+
+impl Gradient {
+ /// Creates a new linear [`linear::Builder`].
+ ///
+ /// 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)
+ }
+}
+
+pub mod linear {
+ //! Linear gradient builder & definition.
+ use crate::Gradient;
+ use iced_core::gradient::ColorStop;
+ use iced_core::{Color, Point};
+ use std::cmp::Ordering;
+
+ /// A linear gradient that can be used in the style of [`Fill`] or [`Stroke`].
+ ///
+ /// [`Fill`]: crate::geometry::Fill;
+ /// [`Stroke`]: crate::geometry::Stroke;
+ #[derive(Debug, Clone, Copy, PartialEq)]
+ pub struct Linear {
+ /// The absolute starting position of the gradient.
+ pub start: Point,
+
+ /// The absolute ending position of the gradient.
+ pub end: Point,
+
+ /// [`ColorStop`]s along the linear gradient direction.
+ pub color_stops: [Option<ColorStop>; 8],
+ }
+
+ /// A [`Linear`] builder.
+ #[derive(Debug)]
+ pub struct Builder {
+ start: Point,
+ end: Point,
+ stops: [Option<ColorStop>; 8],
+ }
+
+ impl Builder {
+ /// Creates a new [`Builder`].
+ pub fn new(start: Point, end: Point) -> Self {
+ Self {
+ start,
+ end,
+ stops: [None; 8],
+ }
+ }
+
+ /// Adds a new [`ColorStop`], defined by an offset and a color, to the gradient.
+ ///
+ /// Any `offset` that is not within `0.0..=1.0` will be silently ignored.
+ ///
+ /// 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) {
+ let (Ok(index) | Err(index)) =
+ self.stops.binary_search_by(|stop| match stop {
+ None => Ordering::Greater,
+ Some(stop) => stop.offset.partial_cmp(&offset).unwrap(),
+ });
+
+ if index < 8 {
+ self.stops[index] = Some(ColorStop { offset, color });
+ }
+ } else {
+ log::warn!(
+ "Gradient: ColorStop must be within 0.0..=1.0 range."
+ );
+ };
+
+ 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<Item = ColorStop>,
+ ) -> 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) -> Gradient {
+ Gradient::Linear(Linear {
+ start: self.start,
+ end: self.end,
+ color_stops: self.stops,
+ })
+ }
+ }
+}
diff --git a/graphics/src/lib.rs b/graphics/src/lib.rs
index 91f50282..ae338936 100644
--- a/graphics/src/lib.rs
+++ b/graphics/src/lib.rs
@@ -23,6 +23,7 @@
#![cfg_attr(docsrs, feature(doc_cfg))]
mod antialiasing;
mod error;
+mod gradient;
mod transformation;
mod viewport;
@@ -42,6 +43,7 @@ pub use antialiasing::Antialiasing;
pub use backend::Backend;
pub use compositor::Compositor;
pub use error::Error;
+pub use gradient::Gradient;
pub use primitive::Primitive;
pub use renderer::Renderer;
pub use transformation::Transformation;
diff --git a/graphics/src/primitive.rs b/graphics/src/primitive.rs
index d4446c87..9728db39 100644
--- a/graphics/src/primitive.rs
+++ b/graphics/src/primitive.rs
@@ -3,7 +3,7 @@ use crate::core::alignment;
use crate::core::image;
use crate::core::svg;
use crate::core::text;
-use crate::core::{Background, Color, Font, Gradient, Rectangle, Size, Vector};
+use crate::core::{Background, Color, Font, Rectangle, Size, Vector};
use bytemuck::{Pod, Zeroable};
use std::sync::Arc;
@@ -39,7 +39,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,
@@ -81,15 +81,12 @@ pub enum Primitive {
/// It can be used to render many kinds of geometry freely.
GradientMesh {
/// The vertices and indices of the mesh.
- buffers: Mesh2D<Vertex2D>,
+ buffers: Mesh2D<GradientVertex2D>,
/// 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 [`tiny_skia`] path filled with some paint.
#[cfg(feature = "tiny-skia")]
@@ -242,25 +239,34 @@ pub struct Mesh2D<T> {
pub indices: Vec<u32>,
}
-/// A two-dimensional vertex.
+/// A two-dimensional vertex with a color.
#[derive(Copy, Clone, Debug, PartialEq, Zeroable, Pod)]
#[repr(C)]
-pub struct Vertex2D {
+pub struct ColoredVertex2D {
/// The vertex position in 2D space.
pub position: [f32; 2],
+
+ /// The color of the vertex in __linear__ RGBA.
+ pub color: [f32; 4],
}
-/// A two-dimensional vertex with a color.
-#[derive(Copy, Clone, Debug, PartialEq, Zeroable, Pod)]
+/// A vertex which contains 2D position & packed gradient data.
+#[derive(Copy, Clone, Debug, PartialEq)]
#[repr(C)]
-pub struct ColoredVertex2D {
+pub struct GradientVertex2D {
/// The vertex position in 2D space.
pub position: [f32; 2],
- /// The color of the vertex in __linear__ RGBA.
- pub color: [f32; 4],
+ /// The packed 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 {}
+
impl From<()> for Primitive {
fn from(_: ()) -> Self {
Self::Group { primitives: vec![] }
diff --git a/graphics/src/triangle.rs b/graphics/src/triangle.rs
deleted file mode 100644
index 09b61767..00000000
--- a/graphics/src/triangle.rs
+++ /dev/null
@@ -1 +0,0 @@
-//! Draw geometry using meshes of triangles.
diff --git a/src/lib.rs b/src/lib.rs
index c73cc48d..b81da394 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -189,8 +189,8 @@ pub use style::theme;
pub use crate::core::alignment;
pub use crate::core::event;
pub use crate::core::{
- color, Alignment, Background, Color, ContentFit, Length, Padding, Pixels,
- Point, Rectangle, Size, Vector,
+ color, Alignment, Background, Color, ContentFit, Degrees, Gradient, Length,
+ Padding, Pixels, Point, Radians, Rectangle, Size, Vector,
};
pub use crate::runtime::Command;
diff --git a/style/src/button.rs b/style/src/button.rs
index a564a2b7..32ec28b7 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.mul_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 d9893bcf..1b47e2f9 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.mul_alpha(0.5))
+ }
}),
text_color: Color {
a: active.text_color.a * 0.5,
diff --git a/tiny_skia/src/backend.rs b/tiny_skia/src/backend.rs
index d481bacd..dd1adbd8 100644
--- a/tiny_skia/src/backend.rs
+++ b/tiny_skia/src/backend.rs
@@ -1,4 +1,5 @@
use crate::core::text;
+use crate::core::Gradient;
use crate::core::{Background, Color, Font, Point, Rectangle, Size, Vector};
use crate::graphics::backend;
use crate::graphics::{Primitive, Viewport};
@@ -183,6 +184,9 @@ impl Backend {
*color,
))
}
+ Background::Gradient(gradient) => {
+ into_gradient(*gradient, *bounds)
+ }
},
anti_alias: true,
..tiny_skia::Paint::default()
@@ -452,6 +456,47 @@ fn into_color(color: Color) -> tiny_skia::Color {
.expect("Convert color from iced to tiny_skia")
}
+fn into_gradient<'a>(
+ gradient: Gradient,
+ bounds: Rectangle,
+) -> tiny_skia::Shader<'a> {
+ let Gradient::Linear(linear) = gradient;
+ let (start, end) = linear.angle.to_distance(&bounds);
+ let stops: Vec<tiny_skia::GradientStop> = linear
+ .color_stops
+ .into_iter()
+ .flatten()
+ .map(|stop| {
+ tiny_skia::GradientStop::new(
+ stop.offset,
+ tiny_skia::Color::from_rgba(
+ stop.color.b,
+ stop.color.g,
+ stop.color.r,
+ stop.color.a,
+ )
+ .expect("Create color"),
+ )
+ })
+ .collect();
+
+ tiny_skia::LinearGradient::new(
+ tiny_skia::Point {
+ x: start.x,
+ y: start.y,
+ },
+ tiny_skia::Point { x: end.x, y: end.y },
+ if stops.is_empty() {
+ vec![tiny_skia::GradientStop::new(0.0, tiny_skia::Color::BLACK)]
+ } else {
+ stops
+ },
+ tiny_skia::SpreadMode::Pad,
+ tiny_skia::Transform::identity(),
+ )
+ .expect("Create linear gradient")
+}
+
fn rounded_rectangle(
bounds: Rectangle,
border_radius: [f32; 4],
diff --git a/tiny_skia/src/geometry.rs b/tiny_skia/src/geometry.rs
index a1fd7b60..100db0b0 100644
--- a/tiny_skia/src/geometry.rs
+++ b/tiny_skia/src/geometry.rs
@@ -1,8 +1,8 @@
-use crate::core::Gradient;
use crate::core::{Point, Rectangle, Size, Vector};
use crate::graphics::geometry::fill::{self, Fill};
use crate::graphics::geometry::stroke::{self, Stroke};
use crate::graphics::geometry::{Path, Style, Text};
+use crate::graphics::Gradient;
use crate::graphics::Primitive;
pub struct Frame {
@@ -231,18 +231,11 @@ pub fn into_paint(style: Style) -> tiny_skia::Paint<'static> {
.expect("Create color"),
),
Style::Gradient(gradient) => match gradient {
- Gradient::Linear(linear) => tiny_skia::LinearGradient::new(
- tiny_skia::Point {
- x: linear.start.x,
- y: linear.start.y,
- },
- tiny_skia::Point {
- x: linear.end.x,
- y: linear.end.y,
- },
- linear
+ Gradient::Linear(linear) => {
+ let stops: Vec<tiny_skia::GradientStop> = linear
.color_stops
.into_iter()
+ .flatten()
.map(|stop| {
tiny_skia::GradientStop::new(
stop.offset,
@@ -255,11 +248,30 @@ pub fn into_paint(style: Style) -> tiny_skia::Paint<'static> {
.expect("Create color"),
)
})
- .collect(),
- tiny_skia::SpreadMode::Pad,
- tiny_skia::Transform::identity(),
- )
- .expect("Create linear gradient"),
+ .collect();
+
+ tiny_skia::LinearGradient::new(
+ tiny_skia::Point {
+ x: linear.start.x,
+ y: linear.start.y,
+ },
+ tiny_skia::Point {
+ x: linear.end.x,
+ y: linear.end.y,
+ },
+ if stops.is_empty() {
+ vec![tiny_skia::GradientStop::new(
+ 0.0,
+ tiny_skia::Color::BLACK,
+ )]
+ } else {
+ stops
+ },
+ tiny_skia::SpreadMode::Pad,
+ tiny_skia::Transform::identity(),
+ )
+ .expect("Create linear gradient")
+ }
},
},
anti_alias: true,
diff --git a/wgpu/Cargo.toml b/wgpu/Cargo.toml
index 41eb4c23..1b71d6ec 100644
--- a/wgpu/Cargo.toml
+++ b/wgpu/Cargo.toml
@@ -46,10 +46,6 @@ version = "0.2"
git = "https://github.com/hecrj/glyphon.git"
rev = "f145067d292082abdd1f2b2481812d4a52c394ec"
-[dependencies.encase]
-version = "0.3.0"
-features = ["glam"]
-
[dependencies.glam]
version = "0.21.3"
diff --git a/wgpu/src/buffer.rs b/wgpu/src/buffer.rs
index c210dd4e..94122187 100644
--- a/wgpu/src/buffer.rs
+++ b/wgpu/src/buffer.rs
@@ -1,7 +1,3 @@
-//! Utilities for buffer operations.
-pub mod dynamic;
-pub mod r#static;
-
use std::marker::PhantomData;
use std::ops::RangeBounds;
@@ -10,7 +6,8 @@ pub struct Buffer<T> {
label: &'static str,
size: u64,
usage: wgpu::BufferUsages,
- raw: wgpu::Buffer,
+ pub(crate) raw: wgpu::Buffer,
+ offsets: Vec<wgpu::BufferAddress>,
type_: PhantomData<T>,
}
@@ -35,6 +32,7 @@ impl<T: bytemuck::Pod> Buffer<T> {
size,
usage,
raw,
+ offsets: Vec::new(),
type_: PhantomData,
}
}
@@ -43,6 +41,8 @@ impl<T: bytemuck::Pod> Buffer<T> {
let new_size = (std::mem::size_of::<T>() * new_count) as u64;
if self.size < new_size {
+ self.offsets.clear();
+
self.raw = device.create_buffer(&wgpu::BufferDescriptor {
label: Some(self.label),
size: new_size,
@@ -58,17 +58,19 @@ impl<T: bytemuck::Pod> Buffer<T> {
}
}
+ /// Returns the size of the written bytes.
pub fn write(
- &self,
+ &mut self,
queue: &wgpu::Queue,
- offset_count: usize,
+ offset: usize,
contents: &[T],
- ) {
- queue.write_buffer(
- &self.raw,
- (std::mem::size_of::<T>() * offset_count) as u64,
- bytemuck::cast_slice(contents),
- );
+ ) -> usize {
+ let bytes: &[u8] = bytemuck::cast_slice(contents);
+ queue.write_buffer(&self.raw, offset as u64, bytes);
+
+ self.offsets.push(offset as u64);
+
+ bytes.len()
}
pub fn slice(
@@ -77,6 +79,21 @@ impl<T: bytemuck::Pod> Buffer<T> {
) -> wgpu::BufferSlice<'_> {
self.raw.slice(bounds)
}
+
+ /// Returns the slice calculated from the offset stored at the given index.
+ pub fn slice_from_index(&self, index: usize) -> wgpu::BufferSlice<'_> {
+ self.raw.slice(self.offset_at(index)..)
+ }
+
+ /// Clears any temporary data (i.e. offsets) from the buffer.
+ pub fn clear(&mut self) {
+ self.offsets.clear()
+ }
+
+ /// Returns the offset at `index`, if it exists.
+ fn offset_at(&self, index: usize) -> &wgpu::BufferAddress {
+ self.offsets.get(index).expect("No offset at index.")
+ }
}
fn next_copy_size<T>(amount: usize) -> u64 {
diff --git a/wgpu/src/buffer/dynamic.rs b/wgpu/src/buffer/dynamic.rs
deleted file mode 100644
index 43fc47ac..00000000
--- a/wgpu/src/buffer/dynamic.rs
+++ /dev/null
@@ -1,202 +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<T: ShaderType> {
- offsets: Vec<wgpu::DynamicOffset>,
- cpu: Internal,
- gpu: wgpu::Buffer,
- label: &'static str,
- size: u64,
- _data: PhantomData<T>,
-}
-
-impl<T: ShaderType + WriteInto> Buffer<T> {
- /// 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::<T>::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);
- }
-
- /// 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::<T>::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, queue: &wgpu::Queue) {
- queue.write_buffer(&self.gpu, 0, 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<Vec<u8>>),
- #[cfg(not(target_arch = "wasm32"))]
- //storage buffers are not supported on wgpu wasm target (yet)
- Storage(encase::DynamicStorageBuffer<Vec<u8>>),
-}
-
-impl Internal {
- /// Writes the current value to its CPU buffer with proper alignment.
- pub(super) fn write<T: ShaderType + WriteInto>(
- &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) -> &[u8] {
- 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 d8ae116e..00000000
--- a/wgpu/src/buffer/static.rs
+++ /dev/null
@@ -1,107 +0,0 @@
-use bytemuck::{Pod, Zeroable};
-use std::marker::PhantomData;
-use std::mem;
-
-const DEFAULT_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<T> {
- //stored sequentially per mesh iteration; refers to the offset index in the GPU buffer
- offsets: Vec<wgpu::BufferAddress>,
- label: &'static str,
- usages: wgpu::BufferUsages,
- gpu: wgpu::Buffer,
- size: wgpu::BufferAddress,
- _data: PhantomData<T>,
-}
-
-impl<T: Pod + Zeroable> Buffer<T> {
- /// Initialize a new static buffer.
- pub fn new(
- device: &wgpu::Device,
- label: &'static str,
- usages: wgpu::BufferUsages,
- ) -> Self {
- let size = (mem::size_of::<T>() as u64) * DEFAULT_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::<T>() * new_count) as u64;
-
- if self.size < size {
- self.size =
- (mem::size_of::<T>() * (new_count + new_count / 2)) as u64;
-
- self.gpu =
- Self::gpu_buffer(device, self.label, self.size, self.usages);
-
- self.offsets.clear();
- 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,
- queue: &wgpu::Queue,
- offset: u64,
- content: &[T],
- ) -> u64 {
- let bytes = bytemuck::cast_slice(content);
- let bytes_size = bytes.len() as u64;
-
- queue.write_buffer(&self.gpu, offset, 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/geometry.rs b/wgpu/src/geometry.rs
index 7e17a7ad..e26d9278 100644
--- a/wgpu/src/geometry.rs
+++ b/wgpu/src/geometry.rs
@@ -1,10 +1,11 @@
//! Build and draw geometry.
-use crate::core::{Gradient, Point, Rectangle, Size, Vector};
+use crate::core::{Point, Rectangle, Size, Vector};
use crate::graphics::geometry::fill::{self, Fill};
use crate::graphics::geometry::{
LineCap, LineDash, LineJoin, Path, Stroke, Style, Text,
};
use crate::graphics::primitive::{self, Primitive};
+use crate::graphics::Gradient;
use lyon::geom::euclid;
use lyon::tessellation;
@@ -23,10 +24,7 @@ pub struct Frame {
enum Buffer {
Solid(tessellation::VertexBuffers<primitive::ColoredVertex2D, u32>),
- Gradient(
- tessellation::VertexBuffers<primitive::Vertex2D, u32>,
- Gradient,
- ),
+ Gradient(tessellation::VertexBuffers<primitive::GradientVertex2D, u32>),
}
struct BufferStack {
@@ -48,12 +46,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(),
));
}
},
@@ -73,9 +70,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!(),
}
}
@@ -91,9 +93,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!(),
}
}
@@ -131,11 +138,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
}
}
@@ -462,7 +471,7 @@ impl Frame {
})
}
}
- Buffer::Gradient(buffer, gradient) => {
+ Buffer::Gradient(buffer) => {
if !buffer.indices.is_empty() {
self.primitives.push(Primitive::GradientMesh {
buffers: primitive::Mesh2D {
@@ -470,7 +479,6 @@ impl Frame {
indices: buffer.indices,
},
size: self.size,
- gradient,
})
}
}
@@ -481,34 +489,38 @@ impl Frame {
}
}
-struct Vertex2DBuilder;
+struct GradientVertex2DBuilder {
+ gradient: Gradient,
+}
-impl tessellation::FillVertexConstructor<primitive::Vertex2D>
- for Vertex2DBuilder
+impl tessellation::FillVertexConstructor<primitive::GradientVertex2D>
+ for GradientVertex2DBuilder
{
fn new_vertex(
&mut self,
vertex: tessellation::FillVertex<'_>,
- ) -> primitive::Vertex2D {
+ ) -> primitive::GradientVertex2D {
let position = vertex.position();
- primitive::Vertex2D {
+ primitive::GradientVertex2D {
position: [position.x, position.y],
+ gradient: pack_gradient(&self.gradient),
}
}
}
-impl tessellation::StrokeVertexConstructor<primitive::Vertex2D>
- for Vertex2DBuilder
+impl tessellation::StrokeVertexConstructor<primitive::GradientVertex2D>
+ for GradientVertex2DBuilder
{
fn new_vertex(
&mut self,
vertex: tessellation::StrokeVertex<'_, '_>,
- ) -> primitive::Vertex2D {
+ ) -> primitive::GradientVertex2D {
let position = vertex.position();
- primitive::Vertex2D {
+ primitive::GradientVertex2D {
position: [position.x, position.y],
+ gradient: pack_gradient(&self.gradient),
}
}
}
@@ -611,3 +623,42 @@ pub(super) fn dashed(path: &Path, line_dash: LineDash<'_>) -> Path {
);
})
}
+
+/// Packs the [`Gradient`] for use in shader code.
+fn pack_gradient(gradient: &Gradient) -> [f32; 44] {
+ match gradient {
+ 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() {
+ let [r, g, b, a] = stop
+ .map_or(crate::core::Color::default(), |s| s.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.map_or(2.0, |s| s.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
+ }
+ }
+}
diff --git a/wgpu/src/image.rs b/wgpu/src/image.rs
index 263bcfa2..6fe02b91 100644
--- a/wgpu/src/image.rs
+++ b/wgpu/src/image.rs
@@ -8,10 +8,10 @@ mod vector;
use atlas::Atlas;
+use crate::buffer::Buffer;
use crate::core::{Rectangle, Size};
use crate::graphics::Transformation;
-use crate::layer;
-use crate::Buffer;
+use crate::{layer, quad};
use std::cell::RefCell;
use std::mem;
@@ -121,7 +121,7 @@ impl Layer {
);
let _ = self.instances.resize(device, instances.len());
- self.instances.write(queue, 0, instances);
+ let _ = self.instances.write(queue, 0, instances);
self.instance_count = instances.len();
}
@@ -131,7 +131,7 @@ impl Layer {
render_pass.set_vertex_buffer(1, self.instances.slice(..));
render_pass.draw_indexed(
- 0..QUAD_INDICES.len() as u32,
+ 0..quad::INDICES.len() as u32,
0,
0..self.instance_count as u32,
);
@@ -244,22 +244,7 @@ impl Pipeline {
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,
- })],
+ targets: &quad::color_target_state(format),
}),
primitive: wgpu::PrimitiveState {
topology: wgpu::PrimitiveTopology::TriangleList,
@@ -278,14 +263,14 @@ impl Pipeline {
let vertices =
device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("iced_wgpu::image vertex buffer"),
- contents: bytemuck::cast_slice(&QUAD_VERTS),
+ contents: bytemuck::cast_slice(&quad::VERTICES),
usage: wgpu::BufferUsages::VERTEX,
});
let indices =
device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("iced_wgpu::image index buffer"),
- contents: bytemuck::cast_slice(&QUAD_INDICES),
+ contents: bytemuck::cast_slice(&quad::INDICES),
usage: wgpu::BufferUsages::INDEX,
});
@@ -498,23 +483,6 @@ pub struct Vertex {
_position: [f32; 2],
}
-const QUAD_INDICES: [u16; 6] = [0, 1, 2, 0, 2, 3];
-
-const QUAD_VERTS: [Vertex; 4] = [
- Vertex {
- _position: [0.0, 0.0],
- },
- Vertex {
- _position: [1.0, 0.0],
- },
- Vertex {
- _position: [1.0, 1.0],
- },
- Vertex {
- _position: [0.0, 1.0],
- },
-];
-
#[repr(C)]
#[derive(Debug, Clone, Copy, Zeroable, Pod)]
struct Instance {
diff --git a/wgpu/src/layer.rs b/wgpu/src/layer.rs
index 8af72b9d..b3ee4739 100644
--- a/wgpu/src/layer.rs
+++ b/wgpu/src/layer.rs
@@ -1,13 +1,13 @@
//! 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;
+use quad::Quad;
pub use text::Text;
use crate::core;
@@ -22,7 +22,7 @@ pub struct Layer<'a> {
pub bounds: Rectangle,
/// The quads of the [`Layer`].
- pub quads: Vec<Quad>,
+ pub quads: Quads,
/// The triangle meshes of the [`Layer`].
pub meshes: Vec<Mesh<'a>>,
@@ -34,12 +34,29 @@ pub struct Layer<'a> {
pub images: Vec<Image>,
}
+/// The quads of the [`Layer`].
+#[derive(Default, Debug)]
+pub struct Quads {
+ /// The solid quads of the [`Layer`].
+ pub solids: Vec<quad::Solid>,
+
+ /// The gradient quads of the [`Layer`].
+ pub gradients: Vec<quad::Gradient>,
+}
+
+impl Quads {
+ /// Returns true if there are no quads of any type in [`Quads`].
+ 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(),
@@ -145,20 +162,39 @@ impl<'a> Layer<'a> {
} => {
let layer = &mut layers[current_layer];
- // TODO: Move some of these computations to the GPU (?)
- layer.quads.push(Quad {
+ let quad = Quad {
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(),
+ quad,
+ });
+ }
+ Background::Gradient(gradient) => {
+ let quad = quad::Gradient {
+ gradient: pack_gradient(
+ gradient,
+ Rectangle::new(
+ quad.position.into(),
+ quad.size.into(),
+ ),
+ ),
+ quad,
+ };
+
+ layer.quads.gradients.push(quad);
+ }
+ };
}
Primitive::Image { handle, bounds } => {
let layer = &mut layers[current_layer];
@@ -198,11 +234,7 @@ impl<'a> Layer<'a> {
});
}
}
- Primitive::GradientMesh {
- buffers,
- size,
- gradient,
- } => {
+ Primitive::GradientMesh { buffers, size } => {
let layer = &mut layers[current_layer];
let bounds = Rectangle::new(
@@ -216,7 +248,6 @@ impl<'a> Layer<'a> {
origin: Point::new(translation.x, translation.y),
buffers,
clip_bounds,
- gradient,
});
}
}
@@ -279,3 +310,32 @@ impl<'a> Layer<'a> {
}
}
}
+
+/// Packs the [`Gradient`] for use in shader code.
+fn pack_gradient(gradient: &core::Gradient, bounds: Rectangle) -> [f32; 44] {
+ match gradient {
+ core::Gradient::Linear(linear) => {
+ let mut pack: [f32; 44] = [0.0; 44];
+
+ for (index, stop) in linear.color_stops.iter().enumerate() {
+ let [r, g, b, a] =
+ stop.map_or(Color::default(), |s| s.color).into_linear();
+
+ pack[index * 4] = r;
+ pack[(index * 4) + 1] = g;
+ pack[(index * 4) + 2] = b;
+ pack[(index * 4) + 3] = a;
+ pack[32 + index] = stop.map_or(2.0, |s| s.offset);
+ }
+
+ let (start, end) = linear.angle.to_distance(&bounds);
+
+ pack[40] = start.x;
+ pack[41] = start.y;
+ pack[42] = end.x;
+ pack[43] = end.y;
+
+ pack
+ }
+ }
+}
diff --git a/wgpu/src/layer/mesh.rs b/wgpu/src/layer/mesh.rs
index 9dd14391..b7dd9a0b 100644
--- a/wgpu/src/layer/mesh.rs
+++ b/wgpu/src/layer/mesh.rs
@@ -1,5 +1,5 @@
//! A collection of triangle primitives.
-use crate::core::{Gradient, Point, Rectangle};
+use crate::core::{Point, Rectangle};
use crate::graphics::primitive;
/// A mesh of triangles.
@@ -22,13 +22,10 @@ pub enum Mesh<'a> {
origin: Point,
/// The vertex and index buffers of the [`Mesh`].
- buffers: &'a primitive::Mesh2D<primitive::Vertex2D>,
+ buffers: &'a primitive::Mesh2D<primitive::GradientVertex2D>,
/// The clipping bounds of the [`Mesh`].
clip_bounds: Rectangle<f32>,
-
- /// The gradient to apply to the [`Mesh`].
- gradient: &'a Gradient,
},
}
@@ -65,9 +62,15 @@ pub struct AttributeCount {
/// The total amount of solid vertices.
pub solid_vertices: usize,
+ /// The total amount of solid meshes.
+ pub solids: usize,
+
/// The total amount of gradient vertices.
pub gradient_vertices: usize,
+ /// The total amount of gradient meshes.
+ pub gradients: usize,
+
/// The total amount of indices.
pub indices: usize,
}
@@ -79,10 +82,12 @@ pub fn attribute_count_of<'a>(meshes: &'a [Mesh<'a>]) -> AttributeCount {
.fold(AttributeCount::default(), |mut count, mesh| {
match mesh {
Mesh::Solid { buffers, .. } => {
+ count.solids += 1;
count.solid_vertices += buffers.vertices.len();
count.indices += buffers.indices.len();
}
Mesh::Gradient { buffers, .. } => {
+ count.gradients += 1;
count.gradient_vertices += buffers.vertices.len();
count.indices += buffers.indices.len();
}
diff --git a/wgpu/src/layer/quad.rs b/wgpu/src/layer/quad.rs
index 0d8bde9d..9913cfe0 100644
--- a/wgpu/src/layer/quad.rs
+++ b/wgpu/src/layer/quad.rs
@@ -1,7 +1,9 @@
-/// 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`].
@@ -10,21 +12,40 @@ pub struct 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__.
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`].
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 [`Quad`] data of the [`Solid`].
+ pub quad: Quad,
+}
+
+/// 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 [`Quad`] data of the [`Gradient`].
+ pub quad: Quad,
+}
+
#[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/wgpu/src/lib.rs b/wgpu/src/lib.rs
index 4a92c345..c05280a6 100644
--- a/wgpu/src/lib.rs
+++ b/wgpu/src/lib.rs
@@ -59,8 +59,6 @@ pub use backend::Backend;
pub use layer::Layer;
pub use settings::Settings;
-use buffer::Buffer;
-
#[cfg(any(feature = "image", feature = "svg"))]
mod image;
diff --git a/wgpu/src/quad.rs b/wgpu/src/quad.rs
index 8fa7359e..31bf2b85 100644
--- a/wgpu/src/quad.rs
+++ b/wgpu/src/quad.rs
@@ -1,18 +1,19 @@
use crate::core::Rectangle;
use crate::graphics::Transformation;
use crate::layer;
-use crate::Buffer;
-use bytemuck::{Pod, Zeroable};
use std::mem;
use wgpu::util::DeviceExt;
#[cfg(feature = "tracing")]
use tracing::info_span;
+const INITIAL_INSTANCES: usize = 10_000;
+
#[derive(Debug)]
pub struct Pipeline {
- pipeline: wgpu::RenderPipeline,
+ solid: solid::Pipeline,
+ gradient: gradient::Pipeline,
constant_layout: wgpu::BindGroupLayout,
vertices: wgpu::Buffer,
indices: wgpu::Buffer,
@@ -39,107 +40,28 @@ impl Pipeline {
}],
});
- 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::<Vertex>() 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::<layer::Quad>() 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),
+ contents: bytemuck::cast_slice(&VERTICES),
usage: wgpu::BufferUsages::VERTEX,
});
let indices =
device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("iced_wgpu::quad index buffer"),
- contents: bytemuck::cast_slice(&QUAD_INDICES),
+ contents: bytemuck::cast_slice(&INDICES),
usage: wgpu::BufferUsages::INDEX,
});
- Pipeline {
- pipeline,
- constant_layout,
+ Self {
vertices,
indices,
+ solid: solid::Pipeline::new(device, format, &constant_layout),
+ gradient: gradient::Pipeline::new(device, format, &constant_layout),
layers: Vec::new(),
prepare_layer: 0,
+ constant_layout,
}
}
@@ -147,7 +69,7 @@ impl Pipeline {
&mut self,
device: &wgpu::Device,
queue: &wgpu::Queue,
- instances: &[layer::Quad],
+ instances: &layer::Quads,
transformation: Transformation,
scale: f32,
) {
@@ -168,22 +90,27 @@ impl Pipeline {
render_pass: &mut wgpu::RenderPass<'a>,
) {
if let Some(layer) = self.layers.get(layer) {
- render_pass.set_pipeline(&self.pipeline);
-
render_pass.set_scissor_rect(
bounds.x,
bounds.y,
bounds.width,
bounds.height,
);
-
render_pass.set_index_buffer(
self.indices.slice(..),
wgpu::IndexFormat::Uint16,
);
render_pass.set_vertex_buffer(0, self.vertices.slice(..));
- layer.draw(render_pass);
+ if layer.solid.instance_count > 0 {
+ render_pass.set_pipeline(&self.solid.pipeline);
+ layer.solid.draw(&layer.constants, render_pass);
+ }
+
+ if layer.gradient.instance_count > 0 {
+ render_pass.set_pipeline(&self.gradient.pipeline);
+ layer.gradient.draw(&layer.constants, render_pass);
+ }
}
}
@@ -196,8 +123,8 @@ impl Pipeline {
struct Layer {
constants: wgpu::BindGroup,
constants_buffer: wgpu::Buffer,
- instances: Buffer<layer::Quad>,
- instance_count: usize,
+ solid: solid::Layer,
+ gradient: gradient::Layer,
}
impl Layer {
@@ -221,18 +148,11 @@ impl Layer {
}],
});
- let instances = Buffer::new(
- device,
- "iced_wgpu::quad instance buffer",
- INITIAL_INSTANCES,
- wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
- );
-
Self {
constants,
constants_buffer,
- instances,
- instance_count: 0,
+ solid: solid::Layer::new(device),
+ gradient: gradient::Layer::new(device),
}
}
@@ -240,7 +160,7 @@ impl Layer {
&mut self,
device: &wgpu::Device,
queue: &wgpu::Queue,
- instances: &[layer::Quad],
+ instances: &layer::Quads,
transformation: Transformation,
scale: f32,
) {
@@ -255,35 +175,350 @@ impl Layer {
bytemuck::bytes_of(&uniforms),
);
- let _ = self.instances.resize(device, instances.len());
- self.instances.write(queue, 0, instances);
- self.instance_count = instances.len();
+ let _ = self.solid.instances.resize(device, instances.solids.len());
+ let _ = self
+ .gradient
+ .instances
+ .resize(device, instances.gradients.len());
+ let _ =
+ self.solid
+ .instances
+ .write(queue, 0, instances.solids.as_slice());
+ self.solid.instance_count = instances.solids.len();
+ let _ = self.gradient.instances.write(
+ queue,
+ 0,
+ instances.gradients.as_slice(),
+ );
+ self.gradient.instance_count = instances.gradients.len();
}
+}
- pub fn draw<'a>(&'a self, render_pass: &mut wgpu::RenderPass<'a>) {
- #[cfg(feature = "tracing")]
- let _ = info_span!("Wgpu::Quad", "DRAW").entered();
+mod solid {
+ use crate::buffer::Buffer;
+ use crate::layer::quad;
+ use crate::quad::{color_target_state, Vertex, INDICES, INITIAL_INSTANCES};
- render_pass.set_bind_group(0, &self.constants, &[]);
- render_pass.set_vertex_buffer(1, self.instances.slice(..));
+ #[derive(Debug)]
+ pub struct Pipeline {
+ pub pipeline: wgpu::RenderPipeline,
+ }
- render_pass.draw_indexed(
- 0..QUAD_INDICES.len() as u32,
- 0,
- 0..self.instance_count as u32,
- );
+ #[derive(Debug)]
+ pub struct Layer {
+ pub instances: Buffer<quad::Solid>,
+ pub instance_count: usize,
+ }
+
+ impl Layer {
+ pub fn new(device: &wgpu::Device) -> Self {
+ let instances = Buffer::new(
+ device,
+ "iced_wgpu.quad.solid.buffer",
+ INITIAL_INSTANCES,
+ wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
+ );
+
+ Self {
+ instances,
+ instance_count: 0,
+ }
+ }
+
+ pub fn draw<'a>(
+ &'a self,
+ constants: &'a wgpu::BindGroup,
+ render_pass: &mut wgpu::RenderPass<'a>,
+ ) {
+ #[cfg(feature = "tracing")]
+ let _ = tracing::info_span!("Wgpu::Quad::Solid", "DRAW").entered();
+
+ render_pass.set_bind_group(0, constants, &[]);
+ render_pass.set_vertex_buffer(1, self.instances.slice(..));
+
+ render_pass.draw_indexed(
+ 0..INDICES.len() as u32,
+ 0,
+ 0..self.instance_count as u32,
+ );
+ }
+ }
+
+ impl Pipeline {
+ pub fn new(
+ device: &wgpu::Device,
+ format: wgpu::TextureFormat,
+ constants_layout: &wgpu::BindGroupLayout,
+ ) -> Self {
+ let layout = device.create_pipeline_layout(
+ &wgpu::PipelineLayoutDescriptor {
+ label: Some("iced_wgpu.quad.solid.pipeline"),
+ push_constant_ranges: &[],
+ bind_group_layouts: &[constants_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: &[
+ Vertex::buffer_layout(),
+ wgpu::VertexBufferLayout {
+ array_stride: std::mem::size_of::<quad::Solid>()
+ 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: &color_target_state(format),
+ }),
+ 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 }
+ }
}
}
+mod gradient {
+ use crate::buffer::Buffer;
+ use crate::layer::quad;
+ use crate::quad::{color_target_state, Vertex, INDICES, INITIAL_INSTANCES};
+
+ #[derive(Debug)]
+ pub struct Pipeline {
+ pub pipeline: wgpu::RenderPipeline,
+ }
+
+ #[derive(Debug)]
+ pub struct Layer {
+ pub instances: Buffer<quad::Gradient>,
+ pub instance_count: usize,
+ }
+
+ impl Layer {
+ pub fn new(device: &wgpu::Device) -> Self {
+ let instances = Buffer::new(
+ device,
+ "iced_wgpu.quad.gradient.buffer",
+ INITIAL_INSTANCES,
+ wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
+ );
+
+ Self {
+ instances,
+ instance_count: 0,
+ }
+ }
+
+ pub fn draw<'a>(
+ &'a self,
+ constants: &'a wgpu::BindGroup,
+ render_pass: &mut wgpu::RenderPass<'a>,
+ ) {
+ #[cfg(feature = "tracing")]
+ let _ =
+ tracing::info_span!("Wgpu::Quad::Gradient", "DRAW").entered();
+
+ render_pass.set_bind_group(0, constants, &[]);
+ render_pass.set_vertex_buffer(1, self.instances.slice(..));
+
+ render_pass.draw_indexed(
+ 0..INDICES.len() as u32,
+ 0,
+ 0..self.instance_count as u32,
+ );
+ }
+ }
+
+ impl Pipeline {
+ pub fn new(
+ device: &wgpu::Device,
+ format: wgpu::TextureFormat,
+ constants_layout: &wgpu::BindGroupLayout,
+ ) -> Self {
+ let layout = device.create_pipeline_layout(
+ &wgpu::PipelineLayoutDescriptor {
+ label: Some("iced_wgpu.quad.gradient.pipeline"),
+ push_constant_ranges: &[],
+ bind_group_layouts: &[constants_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: &[
+ Vertex::buffer_layout(),
+ wgpu::VertexBufferLayout {
+ array_stride: std::mem::size_of::<
+ quad::Gradient,
+ >(
+ )
+ 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: &color_target_state(format),
+ }),
+ 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 }
+ }
+ }
+}
+
+pub(crate) fn color_target_state(
+ format: wgpu::TextureFormat,
+) -> [Option<wgpu::ColorTargetState>; 1] {
+ [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,
+ })]
+}
+
#[repr(C)]
-#[derive(Clone, Copy, Zeroable, Pod)]
+#[derive(Clone, Copy, bytemuck::Zeroable, bytemuck::Pod)]
pub struct Vertex {
_position: [f32; 2],
}
-const QUAD_INDICES: [u16; 6] = [0, 1, 2, 0, 2, 3];
+impl Vertex {
+ fn buffer_layout<'a>() -> wgpu::VertexBufferLayout<'a> {
+ wgpu::VertexBufferLayout {
+ array_stride: mem::size_of::<Self>() as u64,
+ step_mode: wgpu::VertexStepMode::Vertex,
+ attributes: &[wgpu::VertexAttribute {
+ shader_location: 0,
+ format: wgpu::VertexFormat::Float32x2,
+ offset: 0,
+ }],
+ }
+ }
+}
+
+pub(crate) const INDICES: [u16; 6] = [0, 1, 2, 0, 2, 3];
-const QUAD_VERTS: [Vertex; 4] = [
+pub(crate) const VERTICES: [Vertex; 4] = [
Vertex {
_position: [0.0, 0.0],
},
@@ -298,10 +533,8 @@ const QUAD_VERTS: [Vertex; 4] = [
},
];
-const INITIAL_INSTANCES: usize = 10_000;
-
#[repr(C)]
-#[derive(Debug, Clone, Copy, Zeroable, Pod)]
+#[derive(Debug, Clone, Copy, bytemuck::Zeroable, bytemuck::Pod)]
struct Uniforms {
transform: [f32; 16],
scale: f32,
diff --git a/wgpu/src/shader/gradient.wgsl b/wgpu/src/shader/gradient.wgsl
deleted file mode 100644
index 63825aec..00000000
--- a/wgpu/src/shader/gradient.wgsl
+++ /dev/null
@@ -1,88 +0,0 @@
-struct Uniforms {
- transform: mat4x4<f32>,
- //xy = start, wz = end
- position: vec4<f32>,
- //x = start stop, y = end stop, zw = padding
- stop_range: vec4<i32>,
-}
-
-struct Stop {
- color: vec4<f32>,
- offset: f32,
-};
-
-@group(0) @binding(0)
-var<uniform> uniforms: Uniforms;
-
-@group(0) @binding(1)
-var<storage, read> color_stops: array<Stop>;
-
-struct VertexOutput {
- @builtin(position) position: vec4<f32>,
- @location(0) raw_position: vec2<f32>
-}
-
-@vertex
-fn vs_main(@location(0) input: vec2<f32>) -> VertexOutput {
- var output: VertexOutput;
- output.position = uniforms.transform * vec4<f32>(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<f32> {
- 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<f32>;
-
- 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 cf4f7e4d..3232bdbe 100644
--- a/wgpu/src/shader/quad.wgsl
+++ b/wgpu/src/shader/quad.wgsl
@@ -5,17 +5,50 @@ struct Globals {
@group(0) @binding(0) var<uniform> globals: Globals;
-struct VertexInput {
+fn distance_alg(
+ frag_coord: vec2<f32>,
+ position: vec2<f32>,
+ size: vec2<f32>,
+ radius: f32
+) -> f32 {
+ var inner_size: vec2<f32> = size - vec2<f32>(radius, radius) * 2.0;
+ var top_left: vec2<f32> = position + vec2<f32>(radius, radius);
+ var bottom_right: vec2<f32> = top_left + inner_size;
+
+ var top_left_distance: vec2<f32> = top_left - frag_coord;
+ var bottom_right_distance: vec2<f32> = frag_coord - bottom_right;
+
+ var dist: vec2<f32> = vec2<f32>(
+ 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<f32>, position: vec2<f32>, center: vec2<f32>) -> 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<f32>,
- @location(1) pos: vec2<f32>,
- @location(2) scale: vec2<f32>,
- @location(3) color: vec4<f32>,
+ @location(1) color: vec4<f32>,
+ @location(2) pos: vec2<f32>,
+ @location(3) scale: vec2<f32>,
@location(4) border_color: vec4<f32>,
@location(5) border_radius: vec4<f32>,
@location(6) border_width: f32,
}
-struct VertexOutput {
+struct SolidVertexOutput {
@builtin(position) position: vec4<f32>,
@location(0) color: vec4<f32>,
@location(1) border_color: vec4<f32>,
@@ -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<f32> = input.pos * globals.scale;
var scale: vec2<f32> = input.scale * globals.scale;
@@ -47,54 +80,20 @@ fn vs_main(input: VertexInput) -> VertexOutput {
vec4<f32>(pos - vec2<f32>(0.5, 0.5), 0.0, 1.0)
);
+ out.position = globals.transform * transform * vec4<f32>(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<f32>(input.v_pos, 0.0, 1.0);
return out;
}
-fn distance_alg(
- frag_coord: vec2<f32>,
- position: vec2<f32>,
- size: vec2<f32>,
- radius: f32
-) -> f32 {
- var inner_size: vec2<f32> = size - vec2<f32>(radius, radius) * 2.0;
- var top_left: vec2<f32> = position + vec2<f32>(radius, radius);
- var bottom_right: vec2<f32> = top_left + inner_size;
-
- var top_left_distance: vec2<f32> = top_left - frag_coord;
- var bottom_right_distance: vec2<f32> = frag_coord - bottom_right;
-
- var dist: vec2<f32> = vec2<f32>(
- 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<f32>, position: vec2<f32>, center: vec2<f32>) -> 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<f32> {
var mixed_color: vec4<f32> = input.color;
@@ -138,3 +137,214 @@ fn fs_main(
return vec4<f32>(mixed_color.x, mixed_color.y, mixed_color.z, mixed_color.w * radius_alpha);
}
+
+struct GradientVertexInput {
+ @location(0) v_pos: vec2<f32>,
+ @location(1) color_1: vec4<f32>,
+ @location(2) color_2: vec4<f32>,
+ @location(3) color_3: vec4<f32>,
+ @location(4) color_4: vec4<f32>,
+ @location(5) color_5: vec4<f32>,
+ @location(6) color_6: vec4<f32>,
+ @location(7) color_7: vec4<f32>,
+ @location(8) color_8: vec4<f32>,
+ @location(9) offsets_1: vec4<f32>,
+ @location(10) offsets_2: vec4<f32>,
+ @location(11) direction: vec4<f32>,
+ @location(12) position_and_scale: vec4<f32>,
+ @location(13) border_color: vec4<f32>,
+ @location(14) border_radius: vec4<f32>,
+ @location(15) border_width: f32
+}
+
+struct GradientVertexOutput {
+ @builtin(position) position: vec4<f32>,
+ @location(1) color_1: vec4<f32>,
+ @location(2) color_2: vec4<f32>,
+ @location(3) color_3: vec4<f32>,
+ @location(4) color_4: vec4<f32>,
+ @location(5) color_5: vec4<f32>,
+ @location(6) color_6: vec4<f32>,
+ @location(7) color_7: vec4<f32>,
+ @location(8) color_8: vec4<f32>,
+ @location(9) offsets_1: vec4<f32>,
+ @location(10) offsets_2: vec4<f32>,
+ @location(11) direction: vec4<f32>,
+ @location(12) position_and_scale: vec4<f32>,
+ @location(13) border_color: vec4<f32>,
+ @location(14) border_radius: vec4<f32>,
+ @location(15) border_width: f32
+}
+
+@vertex
+fn gradient_vs_main(input: GradientVertexInput) -> GradientVertexOutput {
+ var out: GradientVertexOutput;
+
+ var pos: vec2<f32> = input.position_and_scale.xy * globals.scale;
+ var scale: vec2<f32> = 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<f32> = vec4<f32>(
+ 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<f32> = mat4x4<f32>(
+ vec4<f32>(scale.x + 1.0, 0.0, 0.0, 0.0),
+ vec4<f32>(0.0, scale.y + 1.0, 0.0, 0.0),
+ vec4<f32>(0.0, 0.0, 1.0, 0.0),
+ vec4<f32>(pos - vec2<f32>(0.5, 0.5), 0.0, 1.0)
+ );
+
+ out.position = globals.transform * transform * vec4<f32>(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<f32>(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;
+}
+
+fn random(coords: vec2<f32>) -> f32 {
+ return fract(sin(dot(coords, vec2(12.9898,78.233))) * 43758.5453);
+}
+
+/// Returns the current interpolated color with a max 8-stop gradient
+fn gradient(
+ raw_position: vec2<f32>,
+ direction: vec4<f32>,
+ colors: array<vec4<f32>, 8>,
+ offsets: array<f32, 8>,
+ last_index: i32
+) -> vec4<f32> {
+ 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<f32>;
+
+ let noise_granularity: f32 = 0.3/255.0;
+
+ 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 + mix(-noise_granularity, noise_granularity, random(raw_position));
+}
+
+@fragment
+fn gradient_fs_main(input: GradientVertexOutput) -> @location(0) vec4<f32> {
+ let colors = array<vec4<f32>, 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<f32, 8>(
+ 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<f32> = 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<f32>(input.border_width, input.border_width),
+ scale - vec2<f32>(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<f32>(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<f32>(mixed_color.x, mixed_color.y, mixed_color.z, mixed_color.w * radius_alpha);
+}
diff --git a/wgpu/src/shader/solid.wgsl b/wgpu/src/shader/solid.wgsl
deleted file mode 100644
index b24402f8..00000000
--- a/wgpu/src/shader/solid.wgsl
+++ /dev/null
@@ -1,30 +0,0 @@
-struct Globals {
- transform: mat4x4<f32>,
-}
-
-@group(0) @binding(0) var<uniform> globals: Globals;
-
-struct VertexInput {
- @location(0) position: vec2<f32>,
- @location(1) color: vec4<f32>,
-}
-
-struct VertexOutput {
- @builtin(position) position: vec4<f32>,
- @location(0) color: vec4<f32>,
-}
-
-@vertex
-fn vs_main(input: VertexInput) -> VertexOutput {
- var out: VertexOutput;
-
- out.color = input.color;
- out.position = globals.transform * vec4<f32>(input.position, 0.0, 1.0);
-
- return out;
-}
-
-@fragment
-fn fs_main(input: VertexOutput) -> @location(0) vec4<f32> {
- return input.color;
-}
diff --git a/wgpu/src/shader/triangle.wgsl b/wgpu/src/shader/triangle.wgsl
new file mode 100644
index 00000000..625fa46e
--- /dev/null
+++ b/wgpu/src/shader/triangle.wgsl
@@ -0,0 +1,168 @@
+struct Globals {
+ transform: mat4x4<f32>,
+}
+
+@group(0) @binding(0) var<uniform> globals: Globals;
+
+struct SolidVertexInput {
+ @location(0) position: vec2<f32>,
+ @location(1) color: vec4<f32>,
+}
+
+struct SolidVertexOutput {
+ @builtin(position) position: vec4<f32>,
+ @location(0) color: vec4<f32>,
+}
+
+@vertex
+fn solid_vs_main(input: SolidVertexInput) -> SolidVertexOutput {
+ var out: SolidVertexOutput;
+
+ out.color = input.color;
+ out.position = globals.transform * vec4<f32>(input.position, 0.0, 1.0);
+
+ return out;
+}
+
+@fragment
+fn solid_fs_main(input: SolidVertexOutput) -> @location(0) vec4<f32> {
+ return input.color;
+}
+
+struct GradientVertexOutput {
+ @builtin(position) position: vec4<f32>,
+ @location(0) raw_position: vec2<f32>,
+ @location(1) color_1: vec4<f32>,
+ @location(2) color_2: vec4<f32>,
+ @location(3) color_3: vec4<f32>,
+ @location(4) color_4: vec4<f32>,
+ @location(5) color_5: vec4<f32>,
+ @location(6) color_6: vec4<f32>,
+ @location(7) color_7: vec4<f32>,
+ @location(8) color_8: vec4<f32>,
+ @location(9) offsets_1: vec4<f32>,
+ @location(10) offsets_2: vec4<f32>,
+ @location(11) direction: vec4<f32>,
+}
+
+@vertex
+fn gradient_vs_main(
+ @location(0) input: vec2<f32>,
+ @location(1) color_1: vec4<f32>,
+ @location(2) color_2: vec4<f32>,
+ @location(3) color_3: vec4<f32>,
+ @location(4) color_4: vec4<f32>,
+ @location(5) color_5: vec4<f32>,
+ @location(6) color_6: vec4<f32>,
+ @location(7) color_7: vec4<f32>,
+ @location(8) color_8: vec4<f32>,
+ @location(9) offsets_1: vec4<f32>,
+ @location(10) offsets_2: vec4<f32>,
+ @location(11) direction: vec4<f32>,
+) -> GradientVertexOutput {
+ var output: GradientVertexOutput;
+
+ output.position = globals.transform * vec4<f32>(input.xy, 0.0, 1.0);
+ output.raw_position = input;
+ 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;
+}
+
+fn random(coords: vec2<f32>) -> f32 {
+ return fract(sin(dot(coords, vec2(12.9898,78.233))) * 43758.5453);
+}
+
+/// Returns the current interpolated color with a max 8-stop gradient
+fn gradient(
+ raw_position: vec2<f32>,
+ direction: vec4<f32>,
+ colors: array<vec4<f32>, 8>,
+ offsets: array<f32, 8>,
+ last_index: i32
+) -> vec4<f32> {
+ 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<f32>;
+
+ let noise_granularity: f32 = 0.3/255.0;
+
+ 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 + mix(-noise_granularity, noise_granularity, random(raw_position));
+}
+
+@fragment
+fn gradient_fs_main(input: GradientVertexOutput) -> @location(0) vec4<f32> {
+ let colors = array<vec4<f32>, 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<f32, 8>(
+ 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,
+ );
+
+ 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/triangle.rs b/wgpu/src/triangle.rs
index eb15a458..0d1fead1 100644
--- a/wgpu/src/triangle.rs
+++ b/wgpu/src/triangle.rs
@@ -1,26 +1,19 @@
//! Draw meshes of triangles.
mod msaa;
-use crate::buffer::r#static::Buffer;
+use crate::buffer::Buffer;
use crate::core::Size;
use crate::graphics::{Antialiasing, Transformation};
use crate::layer::mesh::{self, Mesh};
-#[cfg(not(target_arch = "wasm32"))]
-use crate::core::Gradient;
-
-#[cfg(feature = "tracing")]
-use tracing::info_span;
+const INITIAL_INDEX_COUNT: usize = 1_000;
+const INITIAL_VERTEX_COUNT: usize = 1_000;
#[derive(Debug)]
pub struct Pipeline {
blit: Option<msaa::Blit>,
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,
-
layers: Vec<Layer>,
prepare_layer: usize,
}
@@ -30,8 +23,6 @@ struct Layer {
index_buffer: Buffer<u32>,
index_strides: Vec<u32>,
solid: solid::Layer,
-
- #[cfg(not(target_arch = "wasm32"))]
gradient: gradient::Layer,
}
@@ -39,18 +30,17 @@ impl Layer {
fn new(
device: &wgpu::Device,
solid: &solid::Pipeline,
- #[cfg(not(target_arch = "wasm32"))] gradient: &gradient::Pipeline,
+ gradient: &gradient::Pipeline,
) -> Self {
Self {
index_buffer: Buffer::new(
device,
- "iced_wgpu::triangle index buffer",
+ "iced_wgpu.triangle.index_buffer",
+ INITIAL_INDEX_COUNT,
wgpu::BufferUsages::INDEX | wgpu::BufferUsages::COPY_DST,
),
index_strides: Vec::new(),
solid: solid::Layer::new(device, &solid.constants_layout),
-
- #[cfg(not(target_arch = "wasm32"))]
gradient: gradient::Layer::new(device, &gradient.constants_layout),
}
}
@@ -60,7 +50,7 @@ impl Layer {
device: &wgpu::Device,
queue: &wgpu::Queue,
solid: &solid::Pipeline,
- #[cfg(not(target_arch = "wasm32"))] gradient: &gradient::Pipeline,
+ gradient: &gradient::Pipeline,
meshes: &[Mesh<'_>],
transformation: Transformation,
) {
@@ -73,177 +63,92 @@ impl Layer {
// 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_buffer.clear();
+ if self.solid.uniforms.resize(device, count.solids) {
+ self.solid.constants = solid::Layer::bind_group(
+ device,
+ &self.solid.uniforms.raw,
+ &solid.constants_layout,
+ );
+ }
+
+ if self.gradient.uniforms.resize(device, count.gradients) {
+ self.gradient.constants = gradient::Layer::bind_group(
+ device,
+ &self.gradient.uniforms.raw,
+ &gradient.constants_layout,
+ );
+ }
+
self.index_strides.clear();
+ self.index_buffer.clear();
self.solid.vertices.clear();
self.solid.uniforms.clear();
-
- #[cfg(not(target_arch = "wasm32"))]
- {
- self.gradient.uniforms.clear();
- self.gradient.vertices.clear();
- self.gradient.storage.clear();
- }
+ self.gradient.vertices.clear();
+ self.gradient.uniforms.clear();
let mut solid_vertex_offset = 0;
- let mut index_offset = 0;
-
- #[cfg(not(target_arch = "wasm32"))]
+ let mut solid_uniform_offset = 0;
let mut gradient_vertex_offset = 0;
+ let mut gradient_uniform_offset = 0;
+ let mut index_offset = 0;
for mesh in meshes {
let origin = mesh.origin();
let indices = mesh.indices();
- let transform =
- transformation * Transformation::translate(origin.x, origin.y);
+ let uniforms = Uniforms::new(
+ transformation * Transformation::translate(origin.x, origin.y),
+ );
- let new_index_offset =
+ index_offset +=
self.index_buffer.write(queue, index_offset, indices);
-
- index_offset += new_index_offset;
self.index_strides.push(indices.len() as u32);
- //push uniform data to CPU buffers
match mesh {
Mesh::Solid { buffers, .. } => {
- self.solid.uniforms.push(&solid::Uniforms::new(transform));
-
- let written_bytes = self.solid.vertices.write(
+ solid_vertex_offset += self.solid.vertices.write(
queue,
solid_vertex_offset,
&buffers.vertices,
);
- solid_vertex_offset += written_bytes;
+ solid_uniform_offset += self.solid.uniforms.write(
+ queue,
+ solid_uniform_offset,
+ &[uniforms],
+ );
}
- #[cfg(not(target_arch = "wasm32"))]
- Mesh::Gradient {
- buffers, gradient, ..
- } => {
- let written_bytes = self.gradient.vertices.write(
+ Mesh::Gradient { buffers, .. } => {
+ gradient_vertex_offset += self.gradient.vertices.write(
queue,
gradient_vertex_offset,
&buffers.vertices,
);
- gradient_vertex_offset += written_bytes;
-
- match gradient {
- 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<gradient::ColorStop> = 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);
- }
- }
+ gradient_uniform_offset += self.gradient.uniforms.write(
+ queue,
+ gradient_uniform_offset,
+ &[uniforms],
+ );
}
- #[cfg(target_arch = "wasm32")]
- Mesh::Gradient { .. } => {}
}
}
-
- // Write uniform data to GPU
- if count.solid_vertices > 0 {
- let uniforms_resized = self.solid.uniforms.resize(device);
-
- if uniforms_resized {
- self.solid.constants = solid::Layer::bind_group(
- device,
- self.solid.uniforms.raw(),
- &solid.constants_layout,
- )
- }
-
- self.solid.uniforms.write(queue);
- }
-
- #[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 {
- self.gradient.constants = gradient::Layer::bind_group(
- device,
- self.gradient.uniforms.raw(),
- self.gradient.storage.raw(),
- &gradient.constants_layout,
- );
- }
-
- // Write to GPU
- self.gradient.uniforms.write(queue);
- self.gradient.storage.write(queue);
-
- // Cleanup
- self.gradient.color_stop_offset = 0;
- self.gradient.color_stops_pending_write.color_stops.clear();
- }
}
fn render<'a>(
&'a self,
solid: &'a solid::Pipeline,
- #[cfg(not(target_arch = "wasm32"))] gradient: &'a gradient::Pipeline,
+ gradient: &'a gradient::Pipeline,
meshes: &[Mesh<'_>],
scale_factor: f32,
render_pass: &mut wgpu::RenderPass<'a>,
) {
let mut num_solids = 0;
- #[cfg(not(target_arch = "wasm32"))]
let mut num_gradients = 0;
let mut last_is_solid = None;
@@ -268,7 +173,8 @@ impl Layer {
render_pass.set_bind_group(
0,
&self.solid.constants,
- &[self.solid.uniforms.offset_at_index(num_solids)],
+ &[(num_solids * std::mem::size_of::<Uniforms>())
+ as u32],
);
render_pass.set_vertex_buffer(
@@ -278,7 +184,6 @@ impl Layer {
num_solids += 1;
}
- #[cfg(not(target_arch = "wasm32"))]
Mesh::Gradient { .. } => {
if last_is_solid.unwrap_or(true) {
render_pass.set_pipeline(&gradient.pipeline);
@@ -289,10 +194,8 @@ impl Layer {
render_pass.set_bind_group(
0,
&self.gradient.constants,
- &[self
- .gradient
- .uniforms
- .offset_at_index(num_gradients)],
+ &[(num_gradients * std::mem::size_of::<Uniforms>())
+ as u32],
);
render_pass.set_vertex_buffer(
@@ -302,8 +205,6 @@ impl Layer {
num_gradients += 1;
}
- #[cfg(target_arch = "wasm32")]
- Mesh::Gradient { .. } => {}
};
render_pass.set_index_buffer(
@@ -325,10 +226,7 @@ impl Pipeline {
Pipeline {
blit: antialiasing.map(|a| msaa::Blit::new(device, format, a)),
solid: solid::Pipeline::new(device, format, antialiasing),
-
- #[cfg(not(target_arch = "wasm32"))]
gradient: gradient::Pipeline::new(device, format, antialiasing),
-
layers: Vec::new(),
prepare_layer: 0,
}
@@ -342,15 +240,11 @@ impl Pipeline {
transformation: Transformation,
) {
#[cfg(feature = "tracing")]
- let _ = info_span!("Wgpu::Triangle", "PREPARE").entered();
+ let _ = tracing::info_span!("Wgpu::Triangle", "PREPARE").entered();
if self.layers.len() <= self.prepare_layer {
- self.layers.push(Layer::new(
- device,
- &self.solid,
- #[cfg(not(target_arch = "wasm32"))]
- &self.gradient,
- ));
+ self.layers
+ .push(Layer::new(device, &self.solid, &self.gradient));
}
let layer = &mut self.layers[self.prepare_layer];
@@ -358,7 +252,6 @@ impl Pipeline {
device,
queue,
&self.solid,
- #[cfg(not(target_arch = "wasm32"))]
&self.gradient,
meshes,
transformation,
@@ -378,9 +271,8 @@ impl Pipeline {
scale_factor: f32,
) {
#[cfg(feature = "tracing")]
- let _ = info_span!("Wgpu::Triangle", "DRAW").entered();
+ let _ = tracing::info_span!("Wgpu::Triangle", "DRAW").entered();
- // Configure render pass
{
let (attachment, resolve_target, load) = if let Some(blit) =
&mut self.blit
@@ -397,12 +289,9 @@ impl Pipeline {
(target, None, wgpu::LoadOp::Load)
};
- #[cfg(feature = "tracing")]
- let _ = info_span!("Wgpu::Triangle", "BEGIN_RENDER_PASS").enter();
-
let mut render_pass =
encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
- label: Some("iced_wgpu::triangle render pass"),
+ label: Some("iced_wgpu.triangle.render_pass"),
color_attachments: &[Some(
wgpu::RenderPassColorAttachment {
view: attachment,
@@ -417,7 +306,6 @@ impl Pipeline {
layer.render(
&self.solid,
- #[cfg(not(target_arch = "wasm32"))]
&self.gradient,
meshes,
scale_factor,
@@ -463,15 +351,49 @@ fn multisample_state(
}
}
+#[derive(Debug, Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
+#[repr(C)]
+pub struct Uniforms {
+ transform: [f32; 16],
+ /// Uniform values must be 256-aligned;
+ /// see: [`wgpu::Limits`] `min_uniform_buffer_offset_alignment`.
+ _padding: [f32; 48],
+}
+
+impl Uniforms {
+ pub fn new(transform: Transformation) -> Self {
+ Self {
+ transform: transform.into(),
+ _padding: [0.0; 48],
+ }
+ }
+
+ pub fn entry() -> wgpu::BindGroupLayoutEntry {
+ wgpu::BindGroupLayoutEntry {
+ binding: 0,
+ visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
+ ty: wgpu::BindingType::Buffer {
+ ty: wgpu::BufferBindingType::Uniform,
+ has_dynamic_offset: true,
+ min_binding_size: wgpu::BufferSize::new(
+ std::mem::size_of::<Self>() as u64,
+ ),
+ },
+ count: None,
+ }
+ }
+
+ pub fn min_size() -> Option<wgpu::BufferSize> {
+ wgpu::BufferSize::new(std::mem::size_of::<Self>() as u64)
+ }
+}
+
mod solid {
- use crate::buffer::dynamic;
- use crate::buffer::r#static::Buffer;
+ use crate::buffer::Buffer;
use crate::graphics::primitive;
- use crate::graphics::{Antialiasing, Transformation};
+ use crate::graphics::Antialiasing;
use crate::triangle;
- use encase::ShaderType;
-
#[derive(Debug)]
pub struct Pipeline {
pub pipeline: wgpu::RenderPipeline,
@@ -481,7 +403,7 @@ mod solid {
#[derive(Debug)]
pub struct Layer {
pub vertices: Buffer<primitive::ColoredVertex2D>,
- pub uniforms: dynamic::Buffer<Uniforms>,
+ pub uniforms: Buffer<triangle::Uniforms>,
pub constants: wgpu::BindGroup,
}
@@ -492,17 +414,20 @@ mod solid {
) -> Self {
let vertices = Buffer::new(
device,
- "iced_wgpu::triangle::solid vertex buffer",
+ "iced_wgpu.triangle.solid.vertex_buffer",
+ triangle::INITIAL_VERTEX_COUNT,
wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
);
- let uniforms = dynamic::Buffer::uniform(
+ let uniforms = Buffer::new(
device,
- "iced_wgpu::triangle::solid uniforms",
+ "iced_wgpu.triangle.solid.uniforms",
+ 1,
+ wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
);
let constants =
- Self::bind_group(device, uniforms.raw(), constants_layout);
+ Self::bind_group(device, &uniforms.raw, constants_layout);
Self {
vertices,
@@ -517,7 +442,7 @@ mod solid {
layout: &wgpu::BindGroupLayout,
) -> wgpu::BindGroup {
device.create_bind_group(&wgpu::BindGroupDescriptor {
- label: Some("iced_wgpu::triangle::solid bind group"),
+ label: Some("iced_wgpu.triangle.solid.bind_group"),
layout,
entries: &[wgpu::BindGroupEntry {
binding: 0,
@@ -525,7 +450,7 @@ mod solid {
wgpu::BufferBinding {
buffer,
offset: 0,
- size: Some(Uniforms::min_size()),
+ size: triangle::Uniforms::min_size(),
},
),
}],
@@ -533,21 +458,7 @@ mod solid {
}
}
- #[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.
pub fn new(
device: &wgpu::Device,
format: wgpu::TextureFormat,
@@ -555,23 +466,14 @@ mod solid {
) -> Self {
let constants_layout = device.create_bind_group_layout(
&wgpu::BindGroupLayoutDescriptor {
- label: Some("iced_wgpu::triangle::solid 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,
- }],
+ label: Some("iced_wgpu.triangle.solid.bind_group_layout"),
+ entries: &[triangle::Uniforms::entry()],
},
);
let layout = device.create_pipeline_layout(
&wgpu::PipelineLayoutDescriptor {
- label: Some("iced_wgpu::triangle::solid pipeline layout"),
+ label: Some("iced_wgpu.triangle.solid.pipeline_layout"),
bind_group_layouts: &[&constants_layout],
push_constant_ranges: &[],
},
@@ -579,12 +481,10 @@ mod solid {
let shader =
device.create_shader_module(wgpu::ShaderModuleDescriptor {
- label: Some(
- "iced_wgpu triangle solid create shader module",
- ),
+ label: Some("iced_wgpu.triangle.solid.shader"),
source: wgpu::ShaderSource::Wgsl(
std::borrow::Cow::Borrowed(include_str!(
- "shader/solid.wgsl"
+ "shader/triangle.wgsl"
)),
),
});
@@ -595,7 +495,7 @@ mod solid {
layout: Some(&layout),
vertex: wgpu::VertexState {
module: &shader,
- entry_point: "vs_main",
+ entry_point: "solid_vs_main",
buffers: &[wgpu::VertexBufferLayout {
array_stride: std::mem::size_of::<
primitive::ColoredVertex2D,
@@ -612,7 +512,7 @@ mod solid {
},
fragment: Some(wgpu::FragmentState {
module: &shader,
- entry_point: "fs_main",
+ entry_point: "solid_fs_main",
targets: &[triangle::fragment_target(format)],
}),
primitive: triangle::primitive_state(),
@@ -630,16 +530,11 @@ mod solid {
}
}
-#[cfg(not(target_arch = "wasm32"))]
mod gradient {
- use crate::buffer::dynamic;
- use crate::buffer::r#static::Buffer;
- use crate::graphics::Antialiasing;
+ use crate::graphics::{primitive, Antialiasing};
use crate::triangle;
- use encase::ShaderType;
- use glam::{IVec4, Vec4};
- use iced_graphics::primitive;
+ use crate::buffer::Buffer;
#[derive(Debug)]
pub struct Pipeline {
@@ -649,14 +544,9 @@ mod gradient {
#[derive(Debug)]
pub struct Layer {
- pub vertices: Buffer<primitive::Vertex2D>,
- pub uniforms: dynamic::Buffer<Uniforms>,
- pub storage: dynamic::Buffer<Storage>,
+ pub vertices: Buffer<primitive::GradientVertex2D>,
+ pub uniforms: Buffer<triangle::Uniforms>,
pub constants: wgpu::BindGroup,
- 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,
}
impl Layer {
@@ -666,94 +556,52 @@ mod gradient {
) -> Self {
let vertices = Buffer::new(
device,
- "iced_wgpu::triangle::gradient vertex buffer",
+ "iced_wgpu.triangle.gradient.vertex_buffer",
+ triangle::INITIAL_VERTEX_COUNT,
wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
);
- let uniforms = dynamic::Buffer::uniform(
+ let uniforms = Buffer::new(
device,
- "iced_wgpu::triangle::gradient uniforms",
+ "iced_wgpu.triangle.gradient.uniforms",
+ 1,
+ wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
);
- // 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 constants = Self::bind_group(
- device,
- uniforms.raw(),
- storage.raw(),
- constants_layout,
- );
+ let constants =
+ Self::bind_group(device, &uniforms.raw, constants_layout);
Self {
vertices,
uniforms,
- storage,
constants,
- color_stop_offset: 0,
- color_stops_pending_write: Storage {
- color_stops: vec![],
- },
}
}
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"),
+ 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: triangle::Uniforms::min_size(),
+ },
+ ),
+ }],
})
}
}
- #[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<ColorStop>,
- }
-
impl Pipeline {
- /// Creates a new [GradientPipeline] using `gradient.wgsl` shader.
- pub(super) fn new(
+ pub fn new(
device: &wgpu::Device,
format: wgpu::TextureFormat,
antialiasing: Option<Antialiasing>,
@@ -761,40 +609,15 @@ mod gradient {
let constants_layout = device.create_bind_group_layout(
&wgpu::BindGroupLayoutDescriptor {
label: Some(
- "iced_wgpu::triangle::gradient bind group layout",
+ "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: &[triangle::Uniforms::entry()],
},
);
let layout = device.create_pipeline_layout(
&wgpu::PipelineLayoutDescriptor {
- label: Some(
- "iced_wgpu::triangle::gradient pipeline layout",
- ),
+ label: Some("iced_wgpu.triangle.gradient.pipeline_layout"),
bind_group_layouts: &[&constants_layout],
push_constant_ranges: &[],
},
@@ -802,48 +625,66 @@ mod gradient {
let shader =
device.create_shader_module(wgpu::ShaderModuleDescriptor {
- label: Some(
- "iced_wgpu::triangle::gradient create shader module",
- ),
+ label: Some("iced_wgpu.triangle.gradient.shader"),
source: wgpu::ShaderSource::Wgsl(
std::borrow::Cow::Borrowed(include_str!(
- "shader/gradient.wgsl"
+ "shader/triangle.wgsl"
)),
),
});
- let pipeline =
- device.create_render_pipeline(
- &wgpu::RenderPipelineDescriptor {
- label: Some("iced_wgpu::triangle::gradient pipeline"),
- layout: Some(&layout),
- vertex: wgpu::VertexState {
- module: &shader,
- entry_point: "vs_main",
- buffers: &[wgpu::VertexBufferLayout {
- array_stride: std::mem::size_of::<
- primitive::Vertex2D,
- >(
- )
- as u64,
- step_mode: wgpu::VertexStepMode::Vertex,
- attributes: &wgpu::vertex_attr_array!(
- // Position
- 0 => Float32x2,
- ),
- }],
- },
- fragment: Some(wgpu::FragmentState {
- module: &shader,
- entry_point: "fs_main",
- targets: &[triangle::fragment_target(format)],
- }),
- primitive: triangle::primitive_state(),
- depth_stencil: None,
- multisample: triangle::multisample_state(antialiasing),
- multiview: None,
+ let pipeline = device.create_render_pipeline(
+ &wgpu::RenderPipelineDescriptor {
+ label: Some("iced_wgpu.triangle.gradient.pipeline"),
+ layout: Some(&layout),
+ vertex: wgpu::VertexState {
+ module: &shader,
+ entry_point: "gradient_vs_main",
+ buffers: &[wgpu::VertexBufferLayout {
+ array_stride: std::mem::size_of::<
+ primitive::GradientVertex2D,
+ >()
+ 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
+ ),
+ }],
},
- );
+ fragment: Some(wgpu::FragmentState {
+ module: &shader,
+ entry_point: "gradient_fs_main",
+ targets: &[triangle::fragment_target(format)],
+ }),
+ primitive: triangle::primitive_state(),
+ depth_stencil: None,
+ multisample: triangle::multisample_state(antialiasing),
+ multiview: None,
+ },
+ );
Self {
pipeline,
diff --git a/widget/src/canvas.rs b/widget/src/canvas.rs
index 171c4534..bc969dae 100644
--- a/widget/src/canvas.rs
+++ b/widget/src/canvas.rs
@@ -4,6 +4,7 @@ pub mod event;
mod cursor;
mod program;
+pub use crate::graphics::Gradient;
pub use cursor::Cursor;
pub use event::Event;
pub use program::Program;