Merge branch 'iced-rs:master' into viewer_content_fit

This commit is contained in:
Gigas002 2024-04-16 00:08:17 +09:00 committed by GitHub
commit 0ebe0629ce
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
124 changed files with 5702 additions and 4490 deletions

View file

@ -1,53 +1,2 @@
[alias]
lint = """
clippy --workspace --no-deps -- \
-D warnings \
-A clippy::type_complexity \
-D clippy::semicolon_if_nothing_returned \
-D clippy::trivially-copy-pass-by-ref \
-D clippy::default_trait_access \
-D clippy::match-wildcard-for-single-variants \
-D clippy::redundant-closure-for-method-calls \
-D clippy::filter_map_next \
-D clippy::manual_let_else \
-D clippy::unused_async \
-D clippy::from_over_into \
-D clippy::needless_borrow \
-D clippy::new_without_default \
-D clippy::useless_conversion
"""
nitpick = """
clippy --workspace --no-deps -- \
-D warnings \
-D clippy::pedantic \
-A clippy::type_complexity \
-A clippy::must_use_candidate \
-A clippy::return_self_not_must_use \
-A clippy::needless_pass_by_value \
-A clippy::cast_precision_loss \
-A clippy::cast_sign_loss \
-A clippy::cast_possible_truncation \
-A clippy::match_same_arms \
-A clippy::missing-errors-doc \
-A clippy::missing-panics-doc \
-A clippy::cast_lossless \
-A clippy::doc_markdown \
-A clippy::items_after_statements \
-A clippy::too_many_lines \
-A clippy::module_name_repetitions \
-A clippy::if_not_else \
-A clippy::redundant_else \
-A clippy::used_underscore_binding \
-A clippy::cast_possible_wrap \
-A clippy::unnecessary_wraps \
-A clippy::struct-excessive-bools \
-A clippy::float-cmp \
-A clippy::single_match_else \
-A clippy::unreadable_literal \
-A clippy::explicit_deref_methods \
-A clippy::map_unwrap_or \
-A clippy::unnested_or_patterns \
-A clippy::similar_names \
-A clippy::unused_self
"""
lint = "clippy --workspace --benches --all-features --no-deps -- -D warnings"

View file

@ -1,14 +1,6 @@
name: Check
on: [push, pull_request]
jobs:
widget:
runs-on: ubuntu-latest
steps:
- uses: hecrj/setup-rust-action@v2
- uses: actions/checkout@master
- name: Check standalone `iced_widget` crate
run: cargo check --package iced_widget --features image,svg,canvas
wasm:
runs-on: ubuntu-latest
env:
@ -27,3 +19,11 @@ jobs:
run: cargo build --package todos --target wasm32-unknown-unknown
- name: Check compilation of `integration` example
run: cargo build --package integration --target wasm32-unknown-unknown
widget:
runs-on: ubuntu-latest
steps:
- uses: hecrj/setup-rust-action@v2
- uses: actions/checkout@master
- name: Check standalone `iced_widget` crate
run: cargo check --package iced_widget --features image,svg,canvas

View file

@ -2,11 +2,16 @@ name: Lint
on: [push, pull_request]
jobs:
all:
runs-on: macOS-latest
runs-on: ubuntu-latest
steps:
- uses: hecrj/setup-rust-action@v2
with:
components: clippy
- uses: actions/checkout@master
- name: Install dependencies
run: |
export DEBIAN_FRONTED=noninteractive
sudo apt-get -qq update
sudo apt-get install -y libxkbcommon-dev libgtk-3-dev
- name: Check lints
run: cargo lint

View file

@ -10,6 +10,9 @@ homepage.workspace = true
categories.workspace = true
keywords.workspace = true
[lints]
workspace = true
[package.metadata.docs.rs]
rustdoc-args = ["--cfg", "docsrs"]
all-features = true
@ -74,6 +77,15 @@ thiserror.workspace = true
image.workspace = true
image.optional = true
[dev-dependencies]
criterion = "0.5"
iced_wgpu.workspace = true
[[bench]]
name = "wgpu"
harness = false
required-features = ["canvas"]
[profile.release-opt]
inherits = "release"
codegen-units = 1
@ -129,7 +141,7 @@ cosmic-text = "0.10"
dark-light = "1.0"
futures = "0.3"
glam = "0.25"
glyphon = "0.5"
glyphon = { git = "https://github.com/hecrj/glyphon.git", rev = "ceed55403ce53e120ce9d1fae17dcfe388726118" }
guillotiere = "0.6"
half = "2.2"
image = "0.24"
@ -155,7 +167,6 @@ thiserror = "1.0"
tiny-skia = "0.11"
tokio = "1.0"
tracing = "0.1"
xxhash-rust = { version = "0.8", features = ["xxh3"] }
unicode-segmentation = "1.0"
wasm-bindgen-futures = "0.4"
wasm-timer = "0.2"
@ -165,3 +176,28 @@ wgpu = "0.19"
winapi = "0.3"
window_clipboard = "0.4.1"
winit = { git = "https://github.com/iced-rs/winit.git", rev = "592bd152f6d5786fae7d918532d7db752c0d164f" }
[workspace.lints.rust]
rust_2018_idioms = "forbid"
missing_debug_implementations = "deny"
missing_docs = "deny"
unsafe_code = "deny"
unused_results = "deny"
[workspace.lints.clippy]
type-complexity = "allow"
semicolon_if_nothing_returned = "deny"
trivially-copy-pass-by-ref = "deny"
default_trait_access = "deny"
match-wildcard-for-single-variants = "deny"
redundant-closure-for-method-calls = "deny"
filter_map_next = "deny"
manual_let_else = "deny"
unused_async = "deny"
from_over_into = "deny"
needless_borrow = "deny"
new_without_default = "deny"
useless_conversion = "deny"
[workspace.lints.rustdoc]
broken_intra_doc_links = "forbid"

View file

@ -20,7 +20,7 @@ pkgs.mkShell rec {
freetype
freetype.dev
libGL
pkgconfig
pkg-config
xorg.libX11
xorg.libXcursor
xorg.libXi

176
benches/wgpu.rs Normal file
View file

@ -0,0 +1,176 @@
#![allow(missing_docs)]
use criterion::{criterion_group, criterion_main, Bencher, Criterion};
use iced::alignment;
use iced::mouse;
use iced::widget::{canvas, text};
use iced::{
Color, Element, Font, Length, Pixels, Point, Rectangle, Size, Theme,
};
use iced_wgpu::Renderer;
criterion_main!(benches);
criterion_group!(benches, wgpu_benchmark);
#[allow(unused_results)]
pub fn wgpu_benchmark(c: &mut Criterion) {
c.bench_function("wgpu — canvas (light)", |b| benchmark(b, scene(10)));
c.bench_function("wgpu — canvas (heavy)", |b| benchmark(b, scene(1_000)));
}
fn benchmark(
bencher: &mut Bencher<'_>,
widget: Element<'_, (), Theme, Renderer>,
) {
use iced_futures::futures::executor;
use iced_wgpu::graphics;
use iced_wgpu::graphics::Antialiasing;
use iced_wgpu::wgpu;
use iced_winit::core;
use iced_winit::runtime;
let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
backends: wgpu::Backends::all(),
..Default::default()
});
let adapter = executor::block_on(instance.request_adapter(
&wgpu::RequestAdapterOptions {
power_preference: wgpu::PowerPreference::HighPerformance,
compatible_surface: None,
force_fallback_adapter: false,
},
))
.expect("request adapter");
let (device, queue) = executor::block_on(adapter.request_device(
&wgpu::DeviceDescriptor {
label: None,
required_features: wgpu::Features::empty(),
required_limits: wgpu::Limits::default(),
},
None,
))
.expect("request device");
let format = wgpu::TextureFormat::Bgra8UnormSrgb;
let mut engine = iced_wgpu::Engine::new(
&adapter,
&device,
&queue,
format,
Some(Antialiasing::MSAAx4),
);
let mut renderer = Renderer::new(&engine, Font::DEFAULT, Pixels::from(16));
let viewport =
graphics::Viewport::with_physical_size(Size::new(3840, 2160), 2.0);
let texture = device.create_texture(&wgpu::TextureDescriptor {
label: None,
size: wgpu::Extent3d {
width: 3840,
height: 2160,
depth_or_array_layers: 1,
},
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format,
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
view_formats: &[],
});
let texture_view =
texture.create_view(&wgpu::TextureViewDescriptor::default());
let mut user_interface = runtime::UserInterface::build(
widget,
viewport.logical_size(),
runtime::user_interface::Cache::default(),
&mut renderer,
);
bencher.iter(|| {
let _ = user_interface.draw(
&mut renderer,
&Theme::Dark,
&core::renderer::Style {
text_color: Color::WHITE,
},
mouse::Cursor::Unavailable,
);
let mut encoder =
device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
label: None,
});
renderer.present::<&str>(
&mut engine,
&device,
&queue,
&mut encoder,
Some(Color::BLACK),
format,
&texture_view,
&viewport,
&[],
);
let submission = engine.submit(&queue, encoder);
let _ = device.poll(wgpu::Maintain::WaitForSubmissionIndex(submission));
});
}
fn scene<'a, Message: 'a, Theme: 'a>(
n: usize,
) -> Element<'a, Message, Theme, Renderer> {
canvas(Scene { n })
.width(Length::Fill)
.height(Length::Fill)
.into()
}
struct Scene {
n: usize,
}
impl<Message, Theme> canvas::Program<Message, Theme, Renderer> for Scene {
type State = canvas::Cache<Renderer>;
fn draw(
&self,
cache: &Self::State,
renderer: &Renderer,
_theme: &Theme,
bounds: Rectangle,
_cursor: mouse::Cursor,
) -> Vec<canvas::Geometry<Renderer>> {
vec![cache.draw(renderer, bounds.size(), |frame| {
for i in 0..self.n {
frame.fill_rectangle(
Point::new(0.0, i as f32),
Size::new(10.0, 10.0),
Color::WHITE,
);
}
for i in 0..self.n {
frame.fill_text(canvas::Text {
content: i.to_string(),
position: Point::new(0.0, i as f32),
color: Color::BLACK,
size: Pixels::from(16),
line_height: text::LineHeight::default(),
font: Font::DEFAULT,
horizontal_alignment: alignment::Horizontal::Left,
vertical_alignment: alignment::Vertical::Top,
shaping: text::Shaping::Basic,
});
}
})]
}
}

View file

@ -10,6 +10,9 @@ homepage.workspace = true
categories.workspace = true
keywords.workspace = true
[lints]
workspace = true
[features]
auto-detect-theme = ["dep:dark-light"]
advanced = []
@ -21,10 +24,10 @@ log.workspace = true
num-traits.workspace = true
once_cell.workspace = true
palette.workspace = true
rustc-hash.workspace = true
smol_str.workspace = true
thiserror.workspace = true
web-time.workspace = true
xxhash-rust.workspace = true
dark-light.workspace = true
dark-light.optional = true

View file

@ -1,7 +1,7 @@
/// The hasher used to compare layouts.
#[allow(missing_debug_implementations)] // Doesn't really make sense to have debug on the hasher state anyways.
#[derive(Default)]
pub struct Hasher(xxhash_rust::xxh3::Xxh3);
pub struct Hasher(rustc_hash::FxHasher);
impl core::hash::Hasher for Hasher {
fn write(&mut self, bytes: &[u8]) {

View file

@ -1,6 +1,7 @@
//! Load and draw raster graphics.
use crate::{Hasher, Rectangle, Size};
use crate::{Rectangle, Size};
use rustc_hash::FxHasher;
use std::hash::{Hash, Hasher as _};
use std::path::PathBuf;
use std::sync::Arc;
@ -50,7 +51,7 @@ impl Handle {
}
fn from_data(data: Data) -> Handle {
let mut hasher = Hasher::default();
let mut hasher = FxHasher::default();
data.hash(&mut hasher);
Handle {

View file

@ -9,13 +9,6 @@
#![doc(
html_logo_url = "https://raw.githubusercontent.com/iced-rs/iced/9ab6923e943f784985e9ef9ca28b10278297225d/docs/logo.svg"
)]
#![forbid(unsafe_code, rust_2018_idioms)]
#![deny(
missing_debug_implementations,
missing_docs,
unused_results,
rustdoc::broken_intra_doc_links
)]
pub mod alignment;
pub mod border;
pub mod clipboard;
@ -41,7 +34,6 @@ mod background;
mod color;
mod content_fit;
mod element;
mod hasher;
mod length;
mod padding;
mod pixels;
@ -64,7 +56,6 @@ pub use element::Element;
pub use event::Event;
pub use font::Font;
pub use gradient::Gradient;
pub use hasher::Hasher;
pub use layout::Layout;
pub use length::Length;
pub use overlay::Overlay;

View file

@ -16,24 +16,32 @@ pub struct Rectangle<T = f32> {
pub height: T,
}
impl Rectangle<f32> {
/// Creates a new [`Rectangle`] with its top-left corner in the given
/// [`Point`] and with the provided [`Size`].
pub fn new(top_left: Point, size: Size) -> Self {
impl<T> Rectangle<T>
where
T: Default,
{
/// Creates a new [`Rectangle`] with its top-left corner at the origin
/// and with the provided [`Size`].
pub fn with_size(size: Size<T>) -> Self {
Self {
x: top_left.x,
y: top_left.y,
x: T::default(),
y: T::default(),
width: size.width,
height: size.height,
}
}
}
/// Creates a new [`Rectangle`] with its top-left corner at the origin
/// and with the provided [`Size`].
pub fn with_size(size: Size) -> Self {
impl Rectangle<f32> {
/// A rectangle starting at [`Point::ORIGIN`] with infinite width and height.
pub const INFINITE: Self = Self::new(Point::ORIGIN, Size::INFINITY);
/// Creates a new [`Rectangle`] with its top-left corner in the given
/// [`Point`] and with the provided [`Size`].
pub const fn new(top_left: Point, size: Size) -> Self {
Self {
x: 0.0,
y: 0.0,
x: top_left.x,
y: top_left.y,
width: size.width,
height: size.height,
}
@ -139,13 +147,20 @@ impl Rectangle<f32> {
}
/// Snaps the [`Rectangle`] to __unsigned__ integer coordinates.
pub fn snap(self) -> Rectangle<u32> {
Rectangle {
pub fn snap(self) -> Option<Rectangle<u32>> {
let width = self.width as u32;
let height = self.height as u32;
if width < 1 || height < 1 {
return None;
}
Some(Rectangle {
x: self.x as u32,
y: self.y as u32,
width: self.width as u32,
height: self.height as u32,
}
width,
height,
})
}
/// Expands the [`Rectangle`] a given amount.

View file

@ -9,29 +9,29 @@ use crate::{
/// A component that can be used by widgets to draw themselves on a screen.
pub trait Renderer {
/// Starts recording a new layer.
fn start_layer(&mut self);
fn start_layer(&mut self, bounds: Rectangle);
/// Ends recording a new layer.
///
/// The new layer will clip its contents to the provided `bounds`.
fn end_layer(&mut self, bounds: Rectangle);
fn end_layer(&mut self);
/// Draws the primitives recorded in the given closure in a new layer.
///
/// The layer will clip its contents to the provided `bounds`.
fn with_layer(&mut self, bounds: Rectangle, f: impl FnOnce(&mut Self)) {
self.start_layer();
self.start_layer(bounds);
f(self);
self.end_layer(bounds);
self.end_layer();
}
/// Starts recording with a new [`Transformation`].
fn start_transformation(&mut self);
fn start_transformation(&mut self, transformation: Transformation);
/// Ends recording a new layer.
///
/// The new layer will clip its contents to the provided `bounds`.
fn end_transformation(&mut self, transformation: Transformation);
fn end_transformation(&mut self);
/// Applies a [`Transformation`] to the primitives recorded in the given closure.
fn with_transformation(
@ -39,9 +39,9 @@ pub trait Renderer {
transformation: Transformation,
f: impl FnOnce(&mut Self),
) {
self.start_transformation();
self.start_transformation(transformation);
f(self);
self.end_transformation(transformation);
self.end_transformation();
}
/// Applies a translation to the primitives recorded in the given closure.

View file

@ -7,16 +7,14 @@ use crate::{
Background, Color, Font, Pixels, Point, Rectangle, Size, Transformation,
};
use std::borrow::Cow;
impl Renderer for () {
fn start_layer(&mut self) {}
fn start_layer(&mut self, _bounds: Rectangle) {}
fn end_layer(&mut self, _bounds: Rectangle) {}
fn end_layer(&mut self) {}
fn start_transformation(&mut self) {}
fn start_transformation(&mut self, _transformation: Transformation) {}
fn end_transformation(&mut self, _transformation: Transformation) {}
fn end_transformation(&mut self) {}
fn clear(&mut self) {}
@ -45,8 +43,6 @@ impl text::Renderer for () {
Pixels(16.0)
}
fn load_font(&mut self, _font: Cow<'static, [u8]>) {}
fn fill_paragraph(
&mut self,
_paragraph: &Self::Paragraph,
@ -67,7 +63,7 @@ impl text::Renderer for () {
fn fill_text(
&mut self,
_paragraph: Text<'_, Self::Font>,
_paragraph: Text,
_position: Point,
_color: Color,
_clip_bounds: Rectangle,
@ -78,11 +74,11 @@ impl text::Renderer for () {
impl text::Paragraph for () {
type Font = Font;
fn with_text(_text: Text<'_, Self::Font>) -> Self {}
fn with_text(_text: Text<&str>) -> Self {}
fn resize(&mut self, _new_bounds: Size) {}
fn compare(&self, _text: Text<'_, Self::Font>) -> text::Difference {
fn compare(&self, _text: Text<&str>) -> text::Difference {
text::Difference::None
}

View file

@ -99,3 +99,17 @@ where
}
}
}
impl<T> std::ops::Mul<T> for Size<T>
where
T: std::ops::Mul<Output = T> + Copy,
{
type Output = Size<T>;
fn mul(self, rhs: T) -> Self::Output {
Size {
width: self.width * rhs,
height: self.height * rhs,
}
}
}

View file

@ -1,6 +1,7 @@
//! Load and draw vector graphics.
use crate::{Color, Hasher, Rectangle, Size};
use crate::{Color, Rectangle, Size};
use rustc_hash::FxHasher;
use std::borrow::Cow;
use std::hash::{Hash, Hasher as _};
use std::path::PathBuf;
@ -30,7 +31,7 @@ impl Handle {
}
fn from_data(data: Data) -> Handle {
let mut hasher = Hasher::default();
let mut hasher = FxHasher::default();
data.hash(&mut hasher);
Handle {

View file

@ -11,14 +11,13 @@ pub use paragraph::Paragraph;
use crate::alignment;
use crate::{Color, Pixels, Point, Rectangle, Size};
use std::borrow::Cow;
use std::hash::{Hash, Hasher};
/// A paragraph.
#[derive(Debug, Clone, Copy)]
pub struct Text<'a, Font> {
pub struct Text<Content = String, Font = crate::Font> {
/// The content of the paragraph.
pub content: &'a str,
pub content: Content,
/// The bounds of the paragraph.
pub bounds: Size,
@ -192,9 +191,6 @@ pub trait Renderer: crate::Renderer {
/// Returns the default size of [`Text`].
fn default_size(&self) -> Pixels;
/// Loads a [`Self::Font`] from its bytes.
fn load_font(&mut self, font: Cow<'static, [u8]>);
/// Draws the given [`Paragraph`] at the given position and with the given
/// [`Color`].
fn fill_paragraph(
@ -219,7 +215,7 @@ pub trait Renderer: crate::Renderer {
/// [`Color`].
fn fill_text(
&mut self,
text: Text<'_, Self::Font>,
text: Text<String, Self::Font>,
position: Point,
color: Color,
clip_bounds: Rectangle,

View file

@ -8,14 +8,14 @@ pub trait Paragraph: Sized + Default {
type Font: Copy + PartialEq;
/// Creates a new [`Paragraph`] laid out with the given [`Text`].
fn with_text(text: Text<'_, Self::Font>) -> Self;
fn with_text(text: Text<&str, Self::Font>) -> Self;
/// Lays out the [`Paragraph`] with some new boundaries.
fn resize(&mut self, new_bounds: Size);
/// Compares the [`Paragraph`] with some desired [`Text`] and returns the
/// [`Difference`].
fn compare(&self, text: Text<'_, Self::Font>) -> Difference;
fn compare(&self, text: Text<&str, Self::Font>) -> Difference;
/// Returns the horizontal alignment of the [`Paragraph`].
fn horizontal_alignment(&self) -> alignment::Horizontal;
@ -35,7 +35,7 @@ pub trait Paragraph: Sized + Default {
fn grapheme_position(&self, line: usize, index: usize) -> Option<Point>;
/// Updates the [`Paragraph`] to match the given [`Text`], if needed.
fn update(&mut self, text: Text<'_, Self::Font>) {
fn update(&mut self, text: Text<&str, Self::Font>) {
match self.compare(text) {
Difference::None => {}
Difference::Bounds => {

View file

@ -613,10 +613,15 @@ fn mix(a: Color, b: Color, factor: f32) -> Color {
fn readable(background: Color, text: Color) -> Color {
if is_readable(background, text) {
text
} else if is_dark(background) {
Color::WHITE
} else {
Color::BLACK
let white_contrast = relative_contrast(background, Color::WHITE);
let black_contrast = relative_contrast(background, Color::BLACK);
if white_contrast >= black_contrast {
Color::WHITE
} else {
Color::BLACK
}
}
}
@ -631,6 +636,13 @@ fn is_readable(a: Color, b: Color) -> bool {
a_srgb.has_enhanced_contrast_text(b_srgb)
}
fn relative_contrast(a: Color, b: Color) -> f32 {
let a_srgb = Rgb::from(a);
let b_srgb = Rgb::from(b);
a_srgb.relative_contrast(b_srgb)
}
fn to_hsl(color: Color) -> Hsl {
Hsl::from_color(Rgb::from(color))
}

View file

@ -42,6 +42,12 @@ impl Transformation {
}
}
impl Default for Transformation {
fn default() -> Self {
Transformation::IDENTITY
}
}
impl Mul for Transformation {
type Output = Self;

View file

@ -21,7 +21,7 @@ where
Theme: Catalog,
Renderer: text::Renderer,
{
content: Cow<'a, str>,
fragment: Fragment<'a>,
size: Option<Pixels>,
line_height: LineHeight,
width: Length,
@ -39,9 +39,9 @@ where
Renderer: text::Renderer,
{
/// Create a new fragment of [`Text`] with the given contents.
pub fn new(content: impl Into<Cow<'a, str>>) -> Self {
pub fn new(fragment: impl IntoFragment<'a>) -> Self {
Text {
content: content.into(),
fragment: fragment.into_fragment(),
size: None,
line_height: LineHeight::default(),
font: None,
@ -184,7 +184,7 @@ where
limits,
self.width,
self.height,
&self.content,
&self.fragment,
self.line_height,
self.size,
self.font,
@ -366,3 +366,81 @@ impl Catalog for Theme {
class(self)
}
}
/// A fragment of [`Text`].
///
/// This is just an alias to a string that may be either
/// borrowed or owned.
pub type Fragment<'a> = Cow<'a, str>;
/// A trait for converting a value to some text [`Fragment`].
pub trait IntoFragment<'a> {
/// Converts the value to some text [`Fragment`].
fn into_fragment(self) -> Fragment<'a>;
}
impl<'a> IntoFragment<'a> for Fragment<'a> {
fn into_fragment(self) -> Fragment<'a> {
self
}
}
impl<'a, 'b> IntoFragment<'a> for &'a Fragment<'b> {
fn into_fragment(self) -> Fragment<'a> {
Fragment::Borrowed(self)
}
}
impl<'a> IntoFragment<'a> for &'a str {
fn into_fragment(self) -> Fragment<'a> {
Fragment::Borrowed(self)
}
}
impl<'a> IntoFragment<'a> for &'a String {
fn into_fragment(self) -> Fragment<'a> {
Fragment::Borrowed(self.as_str())
}
}
impl<'a> IntoFragment<'a> for String {
fn into_fragment(self) -> Fragment<'a> {
Fragment::Owned(self)
}
}
macro_rules! into_fragment {
($type:ty) => {
impl<'a> IntoFragment<'a> for $type {
fn into_fragment(self) -> Fragment<'a> {
Fragment::Owned(self.to_string())
}
}
impl<'a> IntoFragment<'a> for &$type {
fn into_fragment(self) -> Fragment<'a> {
Fragment::Owned(self.to_string())
}
}
};
}
into_fragment!(char);
into_fragment!(bool);
into_fragment!(u8);
into_fragment!(u16);
into_fragment!(u32);
into_fragment!(u64);
into_fragment!(u128);
into_fragment!(usize);
into_fragment!(i8);
into_fragment!(i16);
into_fragment!(i32);
into_fragment!(i64);
into_fragment!(i128);
into_fragment!(isize);
into_fragment!(f32);
into_fragment!(f64);

View file

@ -9,8 +9,8 @@ use pipeline::cube::{self, Cube};
use iced::mouse;
use iced::time::Duration;
use iced::widget::shader;
use iced::{Color, Rectangle, Size};
use iced::widget::shader::{self, Viewport};
use iced::{Color, Rectangle};
use glam::Vec3;
use rand::Rng;
@ -130,25 +130,29 @@ impl Primitive {
impl shader::Primitive for Primitive {
fn prepare(
&self,
format: wgpu::TextureFormat,
device: &wgpu::Device,
queue: &wgpu::Queue,
_bounds: Rectangle,
target_size: Size<u32>,
_scale_factor: f32,
format: wgpu::TextureFormat,
storage: &mut shader::Storage,
_bounds: &Rectangle,
viewport: &Viewport,
) {
if !storage.has::<Pipeline>() {
storage.store(Pipeline::new(device, queue, format, target_size));
storage.store(Pipeline::new(
device,
queue,
format,
viewport.physical_size(),
));
}
let pipeline = storage.get_mut::<Pipeline>().unwrap();
//upload data to GPU
// Upload data to GPU
pipeline.update(
device,
queue,
target_size,
viewport.physical_size(),
&self.uniforms,
self.cubes.len(),
&self.cubes,
@ -157,20 +161,19 @@ impl shader::Primitive for Primitive {
fn render(
&self,
encoder: &mut wgpu::CommandEncoder,
storage: &shader::Storage,
target: &wgpu::TextureView,
_target_size: Size<u32>,
viewport: Rectangle<u32>,
encoder: &mut wgpu::CommandEncoder,
clip_bounds: &Rectangle<u32>,
) {
//at this point our pipeline should always be initialized
// At this point our pipeline should always be initialized
let pipeline = storage.get::<Pipeline>().unwrap();
//render primitive
// Render primitive
pipeline.render(
target,
encoder,
viewport,
*clip_bounds,
self.cubes.len() as u32,
self.show_depth_buffer,
);

View file

@ -12,12 +12,6 @@ pub fn file<I: 'static + Hash + Copy + Send + Sync, T: ToString>(
})
}
#[derive(Debug, Hash, Clone)]
pub struct Download<I> {
id: I,
url: String,
}
async fn download<I: Copy>(id: I, state: State) -> ((I, Progress), State) {
match state {
State::Ready(url) => {

View file

@ -6,7 +6,10 @@ mod rainbow {
use iced::advanced::renderer;
use iced::advanced::widget::{self, Widget};
use iced::mouse;
use iced::{Element, Length, Rectangle, Renderer, Size, Theme, Vector};
use iced::{
Element, Length, Rectangle, Renderer, Size, Theme, Transformation,
Vector,
};
#[derive(Debug, Clone, Copy, Default)]
pub struct Rainbow;
@ -79,7 +82,6 @@ mod rainbow {
let posn_l = [0.0, bounds.height / 2.0];
let mesh = Mesh::Solid {
size: bounds.size(),
buffers: mesh::Indexed {
vertices: vec![
SolidVertex2D {
@ -130,6 +132,8 @@ mod rainbow {
0, 8, 1, // L
],
},
transformation: Transformation::IDENTITY,
clip_bounds: Rectangle::INFINITE,
};
renderer.with_translation(

View file

@ -5,7 +5,7 @@ use controls::Controls;
use scene::Scene;
use iced_wgpu::graphics::Viewport;
use iced_wgpu::{wgpu, Backend, Renderer, Settings};
use iced_wgpu::{wgpu, Engine, Renderer};
use iced_winit::conversion;
use iced_winit::core::mouse;
use iced_winit::core::renderer;
@ -155,11 +155,9 @@ pub fn main() -> Result<(), Box<dyn std::error::Error>> {
// Initialize iced
let mut debug = Debug::new();
let mut renderer = Renderer::new(
Backend::new(&adapter, &device, &queue, Settings::default(), format),
Font::default(),
Pixels(16.0),
);
let mut engine = Engine::new(&adapter, &device, &queue, format, None);
let mut renderer =
Renderer::new(&engine, Font::default(), Pixels::from(16));
let mut state = program::State::new(
controls,
@ -228,19 +226,17 @@ pub fn main() -> Result<(), Box<dyn std::error::Error>> {
}
// And then iced on top
renderer.with_primitives(|backend, primitive| {
backend.present(
&device,
&queue,
&mut encoder,
None,
frame.texture.format(),
&view,
primitive,
&viewport,
&debug.overlay(),
);
});
renderer.present(
&mut engine,
&device,
&queue,
&mut encoder,
None,
frame.texture.format(),
&view,
&viewport,
&debug.overlay(),
);
// Then we submit the work
queue.submit(Some(encoder.finish()));

View file

@ -173,7 +173,7 @@ impl App {
.style(button::danger);
row![
text(&item.name).color(item.color),
text(item.name.clone()).color(item.color),
horizontal_space(),
pick_list(Color::ALL, Some(item.color), move |color| {
Message::ItemColorChanged(item.clone(), color)

View file

@ -357,7 +357,7 @@ impl<'a> Step {
.into()
}
fn container(title: &str) -> Column<'a, StepMessage> {
fn container(title: &str) -> Column<'_, StepMessage> {
column![text(title).size(50)].spacing(20)
}
@ -589,7 +589,7 @@ impl<'a> Step {
value: &str,
is_secure: bool,
is_showing_icon: bool,
) -> Column<'a, StepMessage> {
) -> Column<'_, StepMessage> {
let mut text_input = text_input("Type something to continue...", value)
.on_input(StepMessage::InputChanged)
.padding(10)
@ -674,7 +674,7 @@ fn ferris<'a>(
.center_x()
}
fn padded_button<'a, Message: Clone>(label: &str) -> Button<'a, Message> {
fn padded_button<Message: Clone>(label: &str) -> Button<'_, Message> {
button(text(label)).padding([12, 24])
}

View file

@ -2,6 +2,7 @@ pub mod server;
use iced::futures;
use iced::subscription::{self, Subscription};
use iced::widget::text;
use futures::channel::mpsc;
use futures::sink::SinkExt;
@ -136,16 +137,24 @@ impl Message {
pub fn disconnected() -> Self {
Message::Disconnected
}
pub fn as_str(&self) -> &str {
match self {
Message::Connected => "Connected successfully!",
Message::Disconnected => "Connection lost... Retrying...",
Message::User(message) => message.as_str(),
}
}
}
impl fmt::Display for Message {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Message::Connected => write!(f, "Connected successfully!"),
Message::Disconnected => {
write!(f, "Connection lost... Retrying...")
}
Message::User(message) => write!(f, "{message}"),
}
f.write_str(self.as_str())
}
}
impl<'a> text::IntoFragment<'a> for &'a Message {
fn into_fragment(self) -> text::Fragment<'a> {
text::Fragment::Borrowed(self.as_str())
}
}

View file

@ -96,10 +96,8 @@ impl WebSocket {
.into()
} else {
scrollable(
column(
self.messages.iter().cloned().map(text).map(Element::from),
)
.spacing(10),
column(self.messages.iter().map(text).map(Element::from))
.spacing(10),
)
.id(MESSAGE_LOG.clone())
.height(Length::Fill)

View file

@ -10,6 +10,9 @@ homepage.workspace = true
categories.workspace = true
keywords.workspace = true
[lints]
workspace = true
[package.metadata.docs.rs]
rustdoc-args = ["--cfg", "docsrs"]
all-features = true
@ -22,6 +25,7 @@ iced_core.workspace = true
futures.workspace = true
log.workspace = true
rustc-hash.workspace = true
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
async-std.workspace = true

View file

@ -18,8 +18,7 @@ impl crate::Executor for Executor {
pub mod time {
//! Listen and react to time.
use crate::core::Hasher;
use crate::subscription::{self, Subscription};
use crate::subscription::{self, Hasher, Subscription};
/// Returns a [`Subscription`] that produces messages at a set interval.
///

View file

@ -17,8 +17,7 @@ impl crate::Executor for Executor {
pub mod time {
//! Listen and react to time.
use crate::core::Hasher;
use crate::subscription::{self, Subscription};
use crate::subscription::{self, Hasher, Subscription};
/// Returns a [`Subscription`] that produces messages at a set interval.
///

View file

@ -22,8 +22,7 @@ impl crate::Executor for Executor {
pub mod time {
//! Listen and react to time.
use crate::core::Hasher;
use crate::subscription::{self, Subscription};
use crate::subscription::{self, Hasher, Subscription};
/// Returns a [`Subscription`] that produces messages at a set interval.
///

View file

@ -16,8 +16,7 @@ impl crate::Executor for Executor {
pub mod time {
//! Listen and react to time.
use crate::core::Hasher;
use crate::subscription::{self, Subscription};
use crate::subscription::{self, Hasher, Subscription};
use crate::BoxStream;
/// Returns a [`Subscription`] that produces messages at a set interval.

View file

@ -4,13 +4,6 @@
#![doc(
html_logo_url = "https://raw.githubusercontent.com/iced-rs/iced/9ab6923e943f784985e9ef9ca28b10278297225d/docs/logo.svg"
)]
#![forbid(unsafe_code, rust_2018_idioms)]
#![deny(
missing_debug_implementations,
missing_docs,
unused_results,
rustdoc::broken_intra_doc_links
)]
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
pub use futures;
pub use iced_core as core;

View file

@ -4,7 +4,6 @@ mod tracker;
pub use tracker::Tracker;
use crate::core::event::{self, Event};
use crate::core::Hasher;
use crate::futures::{Future, Stream};
use crate::{BoxStream, MaybeSend};
@ -18,6 +17,9 @@ use std::hash::Hash;
/// It is the input of a [`Subscription`].
pub type EventStream = BoxStream<(Event, event::Status)>;
/// The hasher used for identifying subscriptions.
pub type Hasher = rustc_hash::FxHasher;
/// A request to listen to external events.
///
/// Besides performing async actions on demand with `Command`, most

View file

@ -1,12 +1,11 @@
use crate::core::event::{self, Event};
use crate::core::Hasher;
use crate::subscription::Recipe;
use crate::subscription::{Hasher, Recipe};
use crate::{BoxFuture, MaybeSend};
use futures::channel::mpsc;
use futures::sink::{Sink, SinkExt};
use rustc_hash::FxHashMap;
use std::collections::HashMap;
use std::hash::Hasher as _;
/// A registry of subscription streams.
@ -18,7 +17,7 @@ use std::hash::Hasher as _;
/// [`Subscription`]: crate::Subscription
#[derive(Debug, Default)]
pub struct Tracker {
subscriptions: HashMap<u64, Execution>,
subscriptions: FxHashMap<u64, Execution>,
}
#[derive(Debug)]
@ -31,7 +30,7 @@ impl Tracker {
/// Creates a new empty [`Tracker`].
pub fn new() -> Self {
Self {
subscriptions: HashMap::new(),
subscriptions: FxHashMap::default(),
}
}

View file

@ -10,6 +10,9 @@ homepage.workspace = true
categories.workspace = true
keywords.workspace = true
[lints]
workspace = true
[package.metadata.docs.rs]
rustdoc-args = ["--cfg", "docsrs"]
all-features = true
@ -34,7 +37,6 @@ raw-window-handle.workspace = true
rustc-hash.workspace = true
thiserror.workspace = true
unicode-segmentation.workspace = true
xxhash-rust.workspace = true
image.workspace = true
image.optional = true

View file

@ -1,36 +0,0 @@
//! Write a graphics backend.
use crate::core::image;
use crate::core::svg;
use crate::core::Size;
use crate::{Compositor, Mesh, Renderer};
use std::borrow::Cow;
/// The graphics backend of a [`Renderer`].
///
/// [`Renderer`]: crate::Renderer
pub trait Backend: Sized {
/// The custom kind of primitives this [`Backend`] supports.
type Primitive: TryFrom<Mesh, Error = &'static str>;
/// The default compositor of this [`Backend`].
type Compositor: Compositor<Renderer = Renderer<Self>>;
}
/// A graphics backend that supports text rendering.
pub trait Text {
/// Loads a font from its bytes.
fn load_font(&mut self, font: Cow<'static, [u8]>);
}
/// A graphics backend that supports image rendering.
pub trait Image {
/// Returns the dimensions of the provided image.
fn dimensions(&self, handle: &image::Handle) -> Size<u32>;
}
/// A graphics backend that supports SVG rendering.
pub trait Svg {
/// Returns the viewport dimensions of the provided SVG.
fn viewport_dimensions(&self, handle: &svg::Handle) -> Size<u32>;
}

View file

@ -1,11 +1,7 @@
use crate::Primitive;
use std::sync::Arc;
/// A piece of data that can be cached.
pub trait Cached: Sized {
/// The type of cache produced.
type Cache;
type Cache: Clone;
/// Loads the [`Cache`] into a proper instance.
///
@ -15,21 +11,7 @@ pub trait Cached: Sized {
/// Caches this value, producing its corresponding [`Cache`].
///
/// [`Cache`]: Self::Cache
fn cache(self) -> Self::Cache;
}
impl<T> Cached for Primitive<T> {
type Cache = Arc<Self>;
fn load(cache: &Arc<Self>) -> Self {
Self::Cache {
content: cache.clone(),
}
}
fn cache(self) -> Arc<Self> {
Arc::new(self)
}
fn cache(self, previous: Option<Self::Cache>) -> Self::Cache;
}
#[cfg(debug_assertions)]
@ -38,5 +20,5 @@ impl Cached for () {
fn load(_cache: &Self::Cache) -> Self {}
fn cache(self) -> Self::Cache {}
fn cache(self, _previous: Option<Self::Cache>) -> Self::Cache {}
}

View file

@ -5,9 +5,11 @@ use crate::futures::{MaybeSend, MaybeSync};
use crate::{Error, Settings, Viewport};
use raw_window_handle::{HasDisplayHandle, HasWindowHandle};
use std::future::Future;
use thiserror::Error;
use std::borrow::Cow;
use std::future::Future;
/// A graphics compositor that can draw to windows.
pub trait Compositor: Sized {
/// The iced renderer of the backend.
@ -60,6 +62,14 @@ pub trait Compositor: Sized {
/// Returns [`Information`] used by this [`Compositor`].
fn fetch_information(&self) -> Information;
/// Loads a font from its bytes.
fn load_font(&mut self, font: Cow<'static, [u8]>) {
crate::text::font_system()
.write()
.expect("Write to font system")
.load_font(font);
}
/// Presents the [`Renderer`] primitives to the next frame of the given [`Surface`].
///
/// [`Renderer`]: Self::Renderer
@ -168,6 +178,8 @@ impl Compositor for () {
) {
}
fn load_font(&mut self, _font: Cow<'static, [u8]>) {}
fn fetch_information(&self) -> Information {
Information {
adapter: String::from("Null Renderer"),

View file

@ -1,196 +1,14 @@
//! Track and compute the damage of graphical primitives.
use crate::core::alignment;
use crate::core::{Rectangle, Size};
use crate::Primitive;
//! Compute the damage between frames.
use crate::core::{Point, Rectangle};
use std::sync::Arc;
/// A type that has some damage bounds.
pub trait Damage: PartialEq {
/// Returns the bounds of the [`Damage`].
fn bounds(&self) -> Rectangle;
}
impl<T: Damage> Damage for Primitive<T> {
fn bounds(&self) -> Rectangle {
match self {
Self::Text {
bounds,
horizontal_alignment,
vertical_alignment,
..
} => {
let mut bounds = *bounds;
bounds.x = match horizontal_alignment {
alignment::Horizontal::Left => bounds.x,
alignment::Horizontal::Center => {
bounds.x - bounds.width / 2.0
}
alignment::Horizontal::Right => bounds.x - bounds.width,
};
bounds.y = match vertical_alignment {
alignment::Vertical::Top => bounds.y,
alignment::Vertical::Center => {
bounds.y - bounds.height / 2.0
}
alignment::Vertical::Bottom => bounds.y - bounds.height,
};
bounds.expand(1.5)
}
Self::Paragraph {
paragraph,
position,
..
} => {
let mut bounds =
Rectangle::new(*position, paragraph.min_bounds);
bounds.x = match paragraph.horizontal_alignment {
alignment::Horizontal::Left => bounds.x,
alignment::Horizontal::Center => {
bounds.x - bounds.width / 2.0
}
alignment::Horizontal::Right => bounds.x - bounds.width,
};
bounds.y = match paragraph.vertical_alignment {
alignment::Vertical::Top => bounds.y,
alignment::Vertical::Center => {
bounds.y - bounds.height / 2.0
}
alignment::Vertical::Bottom => bounds.y - bounds.height,
};
bounds.expand(1.5)
}
Self::Editor {
editor, position, ..
} => {
let bounds = Rectangle::new(*position, editor.bounds);
bounds.expand(1.5)
}
Self::RawText(raw) => {
// TODO: Add `size` field to `raw` to compute more accurate
// damage bounds (?)
raw.clip_bounds.expand(1.5)
}
Self::Quad { bounds, shadow, .. } if shadow.color.a > 0.0 => {
let bounds_with_shadow = Rectangle {
x: bounds.x + shadow.offset.x.min(0.0) - shadow.blur_radius,
y: bounds.y + shadow.offset.y.min(0.0) - shadow.blur_radius,
width: bounds.width
+ shadow.offset.x.abs()
+ shadow.blur_radius * 2.0,
height: bounds.height
+ shadow.offset.y.abs()
+ shadow.blur_radius * 2.0,
};
bounds_with_shadow.expand(1.0)
}
Self::Quad { bounds, .. }
| Self::Image { bounds, .. }
| Self::Svg { bounds, .. } => bounds.expand(1.0),
Self::Clip { bounds, .. } => bounds.expand(1.0),
Self::Group { primitives } => primitives
.iter()
.map(Self::bounds)
.fold(Rectangle::with_size(Size::ZERO), |a, b| {
Rectangle::union(&a, &b)
}),
Self::Transform {
transformation,
content,
} => content.bounds() * *transformation,
Self::Cache { content } => content.bounds(),
Self::Custom(custom) => custom.bounds(),
}
}
}
fn regions<T: Damage>(a: &Primitive<T>, b: &Primitive<T>) -> Vec<Rectangle> {
match (a, b) {
(
Primitive::Group {
primitives: primitives_a,
},
Primitive::Group {
primitives: primitives_b,
},
) => return list(primitives_a, primitives_b),
(
Primitive::Clip {
bounds: bounds_a,
content: content_a,
..
},
Primitive::Clip {
bounds: bounds_b,
content: content_b,
..
},
) => {
if bounds_a == bounds_b {
return regions(content_a, content_b)
.into_iter()
.filter_map(|r| r.intersection(&bounds_a.expand(1.0)))
.collect();
} else {
return vec![bounds_a.expand(1.0), bounds_b.expand(1.0)];
}
}
(
Primitive::Transform {
transformation: transformation_a,
content: content_a,
},
Primitive::Transform {
transformation: transformation_b,
content: content_b,
},
) => {
if transformation_a == transformation_b {
return regions(content_a, content_b)
.into_iter()
.map(|r| r * *transformation_a)
.collect();
}
}
(
Primitive::Cache { content: content_a },
Primitive::Cache { content: content_b },
) => {
if Arc::ptr_eq(content_a, content_b) {
return vec![];
}
}
_ if a == b => return vec![],
_ => {}
}
let bounds_a = a.bounds();
let bounds_b = b.bounds();
if bounds_a == bounds_b {
vec![bounds_a]
} else {
vec![bounds_a, bounds_b]
}
}
/// Computes the damage regions between the two given lists of primitives.
pub fn list<T: Damage>(
previous: &[Primitive<T>],
current: &[Primitive<T>],
/// Diffs the damage regions given some previous and current primitives.
pub fn diff<T>(
previous: &[T],
current: &[T],
bounds: impl Fn(&T) -> Vec<Rectangle>,
diff: impl Fn(&T, &T) -> Vec<Rectangle>,
) -> Vec<Rectangle> {
let damage = previous
.iter()
.zip(current)
.flat_map(|(a, b)| regions(a, b));
let damage = previous.iter().zip(current).flat_map(|(a, b)| diff(a, b));
if previous.len() == current.len() {
damage.collect()
@ -203,39 +21,45 @@ pub fn list<T: Damage>(
// Extend damage by the added/removed primitives
damage
.chain(bigger[smaller.len()..].iter().map(Damage::bounds))
.chain(bigger[smaller.len()..].iter().flat_map(bounds))
.collect()
}
}
/// Computes the damage regions given some previous and current primitives.
pub fn list<T>(
previous: &[T],
current: &[T],
bounds: impl Fn(&T) -> Vec<Rectangle>,
are_equal: impl Fn(&T, &T) -> bool,
) -> Vec<Rectangle> {
diff(previous, current, &bounds, |a, b| {
if are_equal(a, b) {
vec![]
} else {
bounds(a).into_iter().chain(bounds(b)).collect()
}
})
}
/// Groups the given damage regions that are close together inside the given
/// bounds.
pub fn group(
mut damage: Vec<Rectangle>,
scale_factor: f32,
bounds: Size<u32>,
) -> Vec<Rectangle> {
pub fn group(mut damage: Vec<Rectangle>, bounds: Rectangle) -> Vec<Rectangle> {
use std::cmp::Ordering;
const AREA_THRESHOLD: f32 = 20_000.0;
let bounds = Rectangle {
x: 0.0,
y: 0.0,
width: bounds.width as f32,
height: bounds.height as f32,
};
damage.sort_by(|a, b| {
a.x.partial_cmp(&b.x)
a.center()
.distance(Point::ORIGIN)
.partial_cmp(&b.center().distance(Point::ORIGIN))
.unwrap_or(Ordering::Equal)
.then_with(|| a.y.partial_cmp(&b.y).unwrap_or(Ordering::Equal))
});
let mut output = Vec::new();
let mut scaled = damage
.into_iter()
.filter_map(|region| (region * scale_factor).intersection(&bounds))
.filter_map(|region| region.intersection(&bounds))
.filter(|region| region.width >= 1.0 && region.height >= 1.0);
if let Some(mut current) = scaled.next() {

View file

@ -36,15 +36,6 @@ pub trait Renderer: core::Renderer {
fn draw_geometry(&mut self, geometry: Self::Geometry);
}
/// The graphics backend of a geometry renderer.
pub trait Backend {
/// The kind of [`Frame`] this backend supports.
type Frame: frame::Backend;
/// Creates a new [`Self::Frame`].
fn new_frame(&self, size: Size) -> Self::Frame;
}
#[cfg(debug_assertions)]
impl Renderer for () {
type Geometry = ();

View file

@ -22,13 +22,20 @@ where
/// Creates a new empty [`Cache`].
pub fn new() -> Self {
Cache {
state: RefCell::new(State::Empty),
state: RefCell::new(State::Empty { previous: None }),
}
}
/// Clears the [`Cache`], forcing a redraw the next time it is used.
pub fn clear(&self) {
*self.state.borrow_mut() = State::Empty;
use std::ops::Deref;
let previous = match self.state.borrow().deref() {
State::Empty { previous } => previous.clone(),
State::Filled { geometry, .. } => Some(geometry.clone()),
};
*self.state.borrow_mut() = State::Empty { previous };
}
/// Draws geometry using the provided closure and stores it in the
@ -49,20 +56,24 @@ where
) -> Renderer::Geometry {
use std::ops::Deref;
if let State::Filled {
bounds: cached_bounds,
geometry,
} = self.state.borrow().deref()
{
if *cached_bounds == bounds {
return Cached::load(geometry);
let previous = match self.state.borrow().deref() {
State::Empty { previous } => previous.clone(),
State::Filled {
bounds: cached_bounds,
geometry,
} => {
if *cached_bounds == bounds {
return Cached::load(geometry);
}
Some(geometry.clone())
}
}
};
let mut frame = Frame::new(renderer, bounds);
draw_fn(&mut frame);
let geometry = frame.into_geometry().cache();
let geometry = frame.into_geometry().cache(previous);
let result = Cached::load(&geometry);
*self.state.borrow_mut() = State::Filled { bounds, geometry };
@ -79,7 +90,7 @@ where
let state = self.state.borrow();
match *state {
State::Empty => write!(f, "Cache::Empty"),
State::Empty { .. } => write!(f, "Cache::Empty"),
State::Filled { bounds, .. } => {
write!(f, "Cache::Filled {{ bounds: {bounds:?} }}")
}
@ -100,7 +111,9 @@ enum State<Geometry>
where
Geometry: Cached,
{
Empty,
Empty {
previous: Option<Geometry::Cache>,
},
Filled {
bounds: Size,
geometry: Geometry::Cache,

View file

@ -113,13 +113,11 @@ where
region: Rectangle,
f: impl FnOnce(&mut Self) -> R,
) -> R {
let mut frame = self.draft(region.size());
let mut frame = self.draft(region);
let result = f(&mut frame);
let origin = Point::new(region.x, region.y);
self.paste(frame, origin);
self.paste(frame, Point::new(region.x, region.y));
result
}
@ -129,14 +127,14 @@ where
/// Draw its contents back to this [`Frame`] with [`paste`].
///
/// [`paste`]: Self::paste
pub fn draft(&mut self, size: Size) -> Self {
fn draft(&mut self, clip_bounds: Rectangle) -> Self {
Self {
raw: self.raw.draft(size),
raw: self.raw.draft(clip_bounds),
}
}
/// Draws the contents of the given [`Frame`] with origin at the given [`Point`].
pub fn paste(&mut self, frame: Self, at: Point) {
fn paste(&mut self, frame: Self, at: Point) {
self.raw.paste(frame.raw, at);
}
@ -187,7 +185,7 @@ pub trait Backend: Sized {
fn scale(&mut self, scale: impl Into<f32>);
fn scale_nonuniform(&mut self, scale: impl Into<Vector>);
fn draft(&mut self, size: Size) -> Self;
fn draft(&mut self, clip_bounds: Rectangle) -> Self;
fn paste(&mut self, frame: Self, at: Point);
fn stroke<'a>(&mut self, path: &Path, stroke: impl Into<Stroke<'a>>);
@ -232,7 +230,7 @@ impl Backend for () {
fn scale(&mut self, _scale: impl Into<f32>) {}
fn scale_nonuniform(&mut self, _scale: impl Into<Vector>) {}
fn draft(&mut self, _size: Size) -> Self {}
fn draft(&mut self, _clip_bounds: Rectangle) -> Self {}
fn paste(&mut self, _frame: Self, _at: Point) {}
fn stroke<'a>(&mut self, _path: &Path, _stroke: impl Into<Stroke<'a>>) {}

View file

@ -1,14 +1,107 @@
//! Load and operate on images.
use crate::core::image::{Data, Handle};
use bitflags::bitflags;
#[cfg(feature = "image")]
pub use ::image as image_rs;
use crate::core::image;
use crate::core::svg;
use crate::core::{Color, Rectangle};
/// A raster or vector image.
#[derive(Debug, Clone, PartialEq)]
pub enum Image {
/// A raster image.
Raster {
/// The handle of a raster image.
handle: image::Handle,
/// The filter method of a raster image.
filter_method: image::FilterMethod,
/// The bounds of the image.
bounds: Rectangle,
},
/// A vector image.
Vector {
/// The handle of a vector image.
handle: svg::Handle,
/// The [`Color`] filter
color: Option<Color>,
/// The bounds of the image.
bounds: Rectangle,
},
}
impl Image {
/// Returns the bounds of the [`Image`].
pub fn bounds(&self) -> Rectangle {
match self {
Image::Raster { bounds, .. } | Image::Vector { bounds, .. } => {
*bounds
}
}
}
}
#[cfg(feature = "image")]
/// Tries to load an image by its [`Handle`].
pub fn load(handle: &Handle) -> image_rs::ImageResult<image_rs::DynamicImage> {
///
/// [`Handle`]: image::Handle
pub fn load(
handle: &image::Handle,
) -> ::image::ImageResult<::image::DynamicImage> {
use bitflags::bitflags;
bitflags! {
struct Operation: u8 {
const FLIP_HORIZONTALLY = 0b001;
const ROTATE_180 = 0b010;
const FLIP_DIAGONALLY = 0b100;
}
}
impl Operation {
// Meaning of the returned value is described e.g. at:
// https://magnushoff.com/articles/jpeg-orientation/
fn from_exif<R>(reader: &mut R) -> Result<Self, exif::Error>
where
R: std::io::BufRead + std::io::Seek,
{
let exif = exif::Reader::new().read_from_container(reader)?;
Ok(exif
.get_field(exif::Tag::Orientation, exif::In::PRIMARY)
.and_then(|field| field.value.get_uint(0))
.and_then(|value| u8::try_from(value).ok())
.and_then(|value| Self::from_bits(value.saturating_sub(1)))
.unwrap_or_else(Self::empty))
}
fn perform(
self,
mut image: ::image::DynamicImage,
) -> ::image::DynamicImage {
use ::image::imageops;
if self.contains(Self::FLIP_DIAGONALLY) {
imageops::flip_vertical_in_place(&mut image);
}
if self.contains(Self::ROTATE_180) {
imageops::rotate180_in_place(&mut image);
}
if self.contains(Self::FLIP_HORIZONTALLY) {
imageops::flip_horizontal_in_place(&mut image);
}
image
}
}
match handle.data() {
Data::Path(path) => {
image::Data::Path(path) => {
let image = ::image::open(path)?;
let operation = std::fs::File::open(path)
@ -19,7 +112,7 @@ pub fn load(handle: &Handle) -> image_rs::ImageResult<image_rs::DynamicImage> {
Ok(operation.perform(image))
}
Data::Bytes(bytes) => {
image::Data::Bytes(bytes) => {
let image = ::image::load_from_memory(bytes)?;
let operation =
Operation::from_exif(&mut std::io::Cursor::new(bytes))
@ -28,68 +121,22 @@ pub fn load(handle: &Handle) -> image_rs::ImageResult<image_rs::DynamicImage> {
Ok(operation.perform(image))
}
Data::Rgba {
image::Data::Rgba {
width,
height,
pixels,
} => {
if let Some(image) = image_rs::ImageBuffer::from_vec(
*width,
*height,
pixels.to_vec(),
) {
Ok(image_rs::DynamicImage::ImageRgba8(image))
if let Some(image) =
::image::ImageBuffer::from_vec(*width, *height, pixels.to_vec())
{
Ok(::image::DynamicImage::ImageRgba8(image))
} else {
Err(image_rs::error::ImageError::Limits(
image_rs::error::LimitError::from_kind(
image_rs::error::LimitErrorKind::DimensionError,
Err(::image::error::ImageError::Limits(
::image::error::LimitError::from_kind(
::image::error::LimitErrorKind::DimensionError,
),
))
}
}
}
}
bitflags! {
struct Operation: u8 {
const FLIP_HORIZONTALLY = 0b001;
const ROTATE_180 = 0b010;
const FLIP_DIAGONALLY = 0b100;
}
}
impl Operation {
// Meaning of the returned value is described e.g. at:
// https://magnushoff.com/articles/jpeg-orientation/
fn from_exif<R>(reader: &mut R) -> Result<Self, exif::Error>
where
R: std::io::BufRead + std::io::Seek,
{
let exif = exif::Reader::new().read_from_container(reader)?;
Ok(exif
.get_field(exif::Tag::Orientation, exif::In::PRIMARY)
.and_then(|field| field.value.get_uint(0))
.and_then(|value| u8::try_from(value).ok())
.and_then(|value| Self::from_bits(value.saturating_sub(1)))
.unwrap_or_else(Self::empty))
}
fn perform(self, mut image: image::DynamicImage) -> image::DynamicImage {
use image::imageops;
if self.contains(Self::FLIP_DIAGONALLY) {
imageops::flip_vertical_in_place(&mut image);
}
if self.contains(Self::ROTATE_180) {
imageops::rotate180_in_place(&mut image);
}
if self.contains(Self::FLIP_HORIZONTALLY) {
imageops::flip_horizontal_in_place(&mut image);
}
image
}
}

144
graphics/src/layer.rs Normal file
View file

@ -0,0 +1,144 @@
//! Draw and stack layers of graphical primitives.
use crate::core::{Rectangle, Transformation};
/// A layer of graphical primitives.
///
/// Layers normally dictate a set of primitives that are
/// rendered in a specific order.
pub trait Layer: Default {
/// Creates a new [`Layer`] with the given bounds.
fn with_bounds(bounds: Rectangle) -> Self;
/// Flushes and settles any pending group of primitives in the [`Layer`].
///
/// This will be called when a [`Layer`] is finished. It allows layers to efficiently
/// record primitives together and defer grouping until the end.
fn flush(&mut self);
/// Resizes the [`Layer`] to the given bounds.
fn resize(&mut self, bounds: Rectangle);
/// Clears all the layers contents and resets its bounds.
fn reset(&mut self);
}
/// A stack of layers used for drawing.
#[derive(Debug)]
pub struct Stack<T: Layer> {
layers: Vec<T>,
transformations: Vec<Transformation>,
previous: Vec<usize>,
current: usize,
active_count: usize,
}
impl<T: Layer> Stack<T> {
/// Creates a new empty [`Stack`].
pub fn new() -> Self {
Self {
layers: vec![T::default()],
transformations: vec![Transformation::IDENTITY],
previous: vec![],
current: 0,
active_count: 1,
}
}
/// Returns a mutable reference to the current [`Layer`] of the [`Stack`], together with
/// the current [`Transformation`].
#[inline]
pub fn current_mut(&mut self) -> (&mut T, Transformation) {
let transformation = self.transformation();
(&mut self.layers[self.current], transformation)
}
/// Returns the current [`Transformation`] of the [`Stack`].
#[inline]
pub fn transformation(&self) -> Transformation {
self.transformations.last().copied().unwrap()
}
/// Pushes a new clipping region in the [`Stack`]; creating a new layer in the
/// process.
pub fn push_clip(&mut self, bounds: Rectangle) {
self.previous.push(self.current);
self.current = self.active_count;
self.active_count += 1;
let bounds = bounds * self.transformation();
if self.current == self.layers.len() {
self.layers.push(T::with_bounds(bounds));
} else {
self.layers[self.current].resize(bounds);
}
}
/// Pops the current clipping region from the [`Stack`] and restores the previous one.
///
/// The current layer will be recorded for drawing.
pub fn pop_clip(&mut self) {
self.flush();
self.current = self.previous.pop().unwrap();
}
/// Pushes a new [`Transformation`] in the [`Stack`].
///
/// Future drawing operations will be affected by this new [`Transformation`] until
/// it is popped using [`pop_transformation`].
///
/// [`pop_transformation`]: Self::pop_transformation
pub fn push_transformation(&mut self, transformation: Transformation) {
self.transformations
.push(self.transformation() * transformation);
}
/// Pops the current [`Transformation`] in the [`Stack`].
pub fn pop_transformation(&mut self) {
let _ = self.transformations.pop();
}
/// Returns an iterator over mutable references to the layers in the [`Stack`].
pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut T> {
self.flush();
self.layers[..self.active_count].iter_mut()
}
/// Returns an iterator over immutable references to the layers in the [`Stack`].
pub fn iter(&self) -> impl Iterator<Item = &T> {
self.layers[..self.active_count].iter()
}
/// Returns the slice of layers in the [`Stack`].
pub fn as_slice(&self) -> &[T] {
&self.layers[..self.active_count]
}
/// Flushes and settles any primitives in the current layer of the [`Stack`].
pub fn flush(&mut self) {
self.layers[self.current].flush();
}
/// Clears the layers of the [`Stack`], allowing reuse.
///
/// This will normally keep layer allocations for future drawing operations.
pub fn clear(&mut self) {
for layer in self.layers[..self.active_count].iter_mut() {
layer.reset();
}
self.current = 0;
self.active_count = 1;
self.previous.clear();
}
}
impl<T: Layer> Default for Stack<T> {
fn default() -> Self {
Self::new()
}
}

View file

@ -7,48 +7,35 @@
#![doc(
html_logo_url = "https://raw.githubusercontent.com/iced-rs/iced/9ab6923e943f784985e9ef9ca28b10278297225d/docs/logo.svg"
)]
#![forbid(rust_2018_idioms)]
#![deny(
missing_debug_implementations,
missing_docs,
unsafe_code,
unused_results,
rustdoc::broken_intra_doc_links
)]
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
mod antialiasing;
mod cached;
mod primitive;
mod settings;
mod viewport;
pub mod backend;
pub mod color;
pub mod compositor;
pub mod damage;
pub mod error;
pub mod gradient;
pub mod image;
pub mod layer;
pub mod mesh;
pub mod renderer;
pub mod text;
#[cfg(feature = "geometry")]
pub mod geometry;
#[cfg(feature = "image")]
pub mod image;
pub use antialiasing::Antialiasing;
pub use backend::Backend;
pub use cached::Cached;
pub use compositor::Compositor;
pub use damage::Damage;
pub use error::Error;
pub use gradient::Gradient;
pub use image::Image;
pub use layer::Layer;
pub use mesh::Mesh;
pub use primitive::Primitive;
pub use renderer::Renderer;
pub use settings::Settings;
pub use text::Text;
pub use viewport::Viewport;
pub use iced_core as core;

View file

@ -1,8 +1,7 @@
//! Draw triangles!
use crate::color;
use crate::core::{Rectangle, Size};
use crate::core::{Rectangle, Transformation};
use crate::gradient;
use crate::Damage;
use bytemuck::{Pod, Zeroable};
@ -14,29 +13,55 @@ pub enum Mesh {
/// The vertices and indices of the mesh.
buffers: Indexed<SolidVertex2D>,
/// The size of the drawable region of the mesh.
///
/// Any geometry that falls out of this region will be clipped.
size: Size,
/// The [`Transformation`] for the vertices of the [`Mesh`].
transformation: Transformation,
/// The clip bounds of the [`Mesh`].
clip_bounds: Rectangle,
},
/// A mesh with a gradient.
Gradient {
/// The vertices and indices of the mesh.
buffers: Indexed<GradientVertex2D>,
/// The size of the drawable region of the mesh.
///
/// Any geometry that falls out of this region will be clipped.
size: Size,
/// The [`Transformation`] for the vertices of the [`Mesh`].
transformation: Transformation,
/// The clip bounds of the [`Mesh`].
clip_bounds: Rectangle,
},
}
impl Damage for Mesh {
fn bounds(&self) -> Rectangle {
impl Mesh {
/// Returns the indices of the [`Mesh`].
pub fn indices(&self) -> &[u32] {
match self {
Self::Solid { size, .. } | Self::Gradient { size, .. } => {
Rectangle::with_size(*size)
Self::Solid { buffers, .. } => &buffers.indices,
Self::Gradient { buffers, .. } => &buffers.indices,
}
}
/// Returns the [`Transformation`] of the [`Mesh`].
pub fn transformation(&self) -> Transformation {
match self {
Self::Solid { transformation, .. }
| Self::Gradient { transformation, .. } => *transformation,
}
}
/// Returns the clip bounds of the [`Mesh`].
pub fn clip_bounds(&self) -> Rectangle {
match self {
Self::Solid {
clip_bounds,
transformation,
..
}
| Self::Gradient {
clip_bounds,
transformation,
..
} => *clip_bounds * *transformation,
}
}
}
@ -75,6 +100,47 @@ pub struct GradientVertex2D {
pub gradient: gradient::Packed,
}
/// The result of counting the attributes of a set of meshes.
#[derive(Debug, Clone, Copy, Default)]
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,
}
/// Returns the number of total vertices & total indices of all [`Mesh`]es.
pub fn attribute_count_of(meshes: &[Mesh]) -> AttributeCount {
meshes
.iter()
.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();
}
}
count
})
}
/// A renderer capable of drawing a [`Mesh`].
pub trait Renderer {
/// Draws the given [`Mesh`].

View file

@ -1,160 +0,0 @@
//! Draw using different graphical primitives.
use crate::core::alignment;
use crate::core::image;
use crate::core::svg;
use crate::core::text;
use crate::core::{
Background, Border, Color, Font, Pixels, Point, Rectangle, Shadow,
Transformation, Vector,
};
use crate::text::editor;
use crate::text::paragraph;
use std::sync::Arc;
/// A rendering primitive.
#[derive(Debug, Clone, PartialEq)]
pub enum Primitive<T> {
/// A text primitive
Text {
/// The contents of the text.
content: String,
/// The bounds of the text.
bounds: Rectangle,
/// The color of the text.
color: Color,
/// The size of the text in logical pixels.
size: Pixels,
/// The line height of the text.
line_height: text::LineHeight,
/// The font of the text.
font: Font,
/// The horizontal alignment of the text.
horizontal_alignment: alignment::Horizontal,
/// The vertical alignment of the text.
vertical_alignment: alignment::Vertical,
/// The shaping strategy of the text.
shaping: text::Shaping,
/// The clip bounds of the text.
clip_bounds: Rectangle,
},
/// A paragraph primitive
Paragraph {
/// The [`paragraph::Weak`] reference.
paragraph: paragraph::Weak,
/// The position of the paragraph.
position: Point,
/// The color of the paragraph.
color: Color,
/// The clip bounds of the paragraph.
clip_bounds: Rectangle,
},
/// An editor primitive
Editor {
/// The [`editor::Weak`] reference.
editor: editor::Weak,
/// The position of the editor.
position: Point,
/// The color of the editor.
color: Color,
/// The clip bounds of the editor.
clip_bounds: Rectangle,
},
/// A raw `cosmic-text` primitive
RawText(crate::text::Raw),
/// A quad primitive
Quad {
/// The bounds of the quad
bounds: Rectangle,
/// The background of the quad
background: Background,
/// The [`Border`] of the quad
border: Border,
/// The [`Shadow`] of the quad
shadow: Shadow,
},
/// An image primitive
Image {
/// The handle of the image
handle: image::Handle,
/// The filter method of the image
filter_method: image::FilterMethod,
/// The bounds of the image
bounds: Rectangle,
},
/// An SVG primitive
Svg {
/// The path of the SVG file
handle: svg::Handle,
/// The [`Color`] filter
color: Option<Color>,
/// The bounds of the viewport
bounds: Rectangle,
},
/// A group of primitives
Group {
/// The primitives of the group
primitives: Vec<Primitive<T>>,
},
/// A clip primitive
Clip {
/// The bounds of the clip
bounds: Rectangle,
/// The content of the clip
content: Box<Primitive<T>>,
},
/// A primitive that applies a [`Transformation`]
Transform {
/// The [`Transformation`]
transformation: Transformation,
/// The primitive to transform
content: Box<Primitive<T>>,
},
/// A cached primitive.
///
/// This can be useful if you are implementing a widget where primitive
/// generation is expensive.
Cache {
/// The cached primitive
content: Arc<Primitive<T>>,
},
/// A backend-specific primitive.
Custom(T),
}
impl<T> Primitive<T> {
/// Groups the current [`Primitive`].
pub fn group(primitives: Vec<Self>) -> Self {
Self::Group { primitives }
}
/// Clips the current [`Primitive`].
pub fn clip(self, bounds: Rectangle) -> Self {
Self::Clip {
bounds,
content: Box::new(self),
}
}
/// Translates the current [`Primitive`].
pub fn translate(self, translation: Vector) -> Self {
Self::Transform {
transformation: Transformation::translate(
translation.x,
translation.y,
),
content: Box::new(self),
}
}
/// Transforms the current [`Primitive`].
pub fn transform(self, transformation: Transformation) -> Self {
Self::Transform {
transformation,
content: Box::new(self),
}
}
}

View file

@ -1,269 +0,0 @@
//! Create a renderer from a [`Backend`].
use crate::backend::{self, Backend};
use crate::compositor;
use crate::core;
use crate::core::image;
use crate::core::renderer;
use crate::core::svg;
use crate::core::text::Text;
use crate::core::{
Background, Color, Font, Pixels, Point, Rectangle, Size, Transformation,
};
use crate::mesh;
use crate::text;
use crate::{Mesh, Primitive};
use std::borrow::Cow;
/// A backend-agnostic renderer that supports all the built-in widgets.
#[derive(Debug)]
pub struct Renderer<B: Backend> {
backend: B,
default_font: Font,
default_text_size: Pixels,
primitives: Vec<Primitive<B::Primitive>>,
stack: Vec<Vec<Primitive<B::Primitive>>>,
}
impl<B: Backend> Renderer<B> {
/// Creates a new [`Renderer`] from the given [`Backend`].
pub fn new(
backend: B,
default_font: Font,
default_text_size: Pixels,
) -> Self {
Self {
backend,
default_font,
default_text_size,
primitives: Vec::new(),
stack: Vec::new(),
}
}
/// Returns a reference to the [`Backend`] of the [`Renderer`].
pub fn backend(&self) -> &B {
&self.backend
}
/// Enqueues the given [`Primitive`] in the [`Renderer`] for drawing.
pub fn draw_primitive(&mut self, primitive: Primitive<B::Primitive>) {
self.primitives.push(primitive);
}
/// Runs the given closure with the [`Backend`] and the recorded primitives
/// of the [`Renderer`].
pub fn with_primitives<O>(
&mut self,
f: impl FnOnce(&mut B, &[Primitive<B::Primitive>]) -> O,
) -> O {
f(&mut self.backend, &self.primitives)
}
}
impl<B: Backend> iced_core::Renderer for Renderer<B> {
fn start_layer(&mut self) {
self.stack.push(std::mem::take(&mut self.primitives));
}
fn end_layer(&mut self, bounds: Rectangle) {
let layer = std::mem::replace(
&mut self.primitives,
self.stack.pop().expect("a layer should be recording"),
);
self.primitives.push(Primitive::group(layer).clip(bounds));
}
fn start_transformation(&mut self) {
self.stack.push(std::mem::take(&mut self.primitives));
}
fn end_transformation(&mut self, transformation: Transformation) {
let layer = std::mem::replace(
&mut self.primitives,
self.stack.pop().expect("a layer should be recording"),
);
self.primitives
.push(Primitive::group(layer).transform(transformation));
}
fn fill_quad(
&mut self,
quad: renderer::Quad,
background: impl Into<Background>,
) {
self.primitives.push(Primitive::Quad {
bounds: quad.bounds,
background: background.into(),
border: quad.border,
shadow: quad.shadow,
});
}
fn clear(&mut self) {
self.primitives.clear();
}
}
impl<B> core::text::Renderer for Renderer<B>
where
B: Backend + backend::Text,
{
type Font = Font;
type Paragraph = text::Paragraph;
type Editor = text::Editor;
const ICON_FONT: Font = Font::with_name("Iced-Icons");
const CHECKMARK_ICON: char = '\u{f00c}';
const ARROW_DOWN_ICON: char = '\u{e800}';
fn default_font(&self) -> Self::Font {
self.default_font
}
fn default_size(&self) -> Pixels {
self.default_text_size
}
fn load_font(&mut self, bytes: Cow<'static, [u8]>) {
self.backend.load_font(bytes);
}
fn fill_paragraph(
&mut self,
paragraph: &Self::Paragraph,
position: Point,
color: Color,
clip_bounds: Rectangle,
) {
self.primitives.push(Primitive::Paragraph {
paragraph: paragraph.downgrade(),
position,
color,
clip_bounds,
});
}
fn fill_editor(
&mut self,
editor: &Self::Editor,
position: Point,
color: Color,
clip_bounds: Rectangle,
) {
self.primitives.push(Primitive::Editor {
editor: editor.downgrade(),
position,
color,
clip_bounds,
});
}
fn fill_text(
&mut self,
text: Text<'_, Self::Font>,
position: Point,
color: Color,
clip_bounds: Rectangle,
) {
self.primitives.push(Primitive::Text {
content: text.content.to_string(),
bounds: Rectangle::new(position, text.bounds),
size: text.size,
line_height: text.line_height,
color,
font: text.font,
horizontal_alignment: text.horizontal_alignment,
vertical_alignment: text.vertical_alignment,
shaping: text.shaping,
clip_bounds,
});
}
}
impl<B> image::Renderer for Renderer<B>
where
B: Backend + backend::Image,
{
type Handle = image::Handle;
fn measure_image(&self, handle: &image::Handle) -> Size<u32> {
self.backend().dimensions(handle)
}
fn draw_image(
&mut self,
handle: image::Handle,
filter_method: image::FilterMethod,
bounds: Rectangle,
) {
self.primitives.push(Primitive::Image {
handle,
filter_method,
bounds,
});
}
}
impl<B> svg::Renderer for Renderer<B>
where
B: Backend + backend::Svg,
{
fn measure_svg(&self, handle: &svg::Handle) -> Size<u32> {
self.backend().viewport_dimensions(handle)
}
fn draw_svg(
&mut self,
handle: svg::Handle,
color: Option<Color>,
bounds: Rectangle,
) {
self.primitives.push(Primitive::Svg {
handle,
color,
bounds,
});
}
}
impl<B: Backend> mesh::Renderer for Renderer<B> {
fn draw_mesh(&mut self, mesh: Mesh) {
match B::Primitive::try_from(mesh) {
Ok(primitive) => {
self.draw_primitive(Primitive::Custom(primitive));
}
Err(error) => {
log::warn!("mesh primitive could not be drawn: {error:?}");
}
}
}
}
#[cfg(feature = "geometry")]
impl<B> crate::geometry::Renderer for Renderer<B>
where
B: Backend + crate::geometry::Backend,
B::Frame:
crate::geometry::frame::Backend<Geometry = Primitive<B::Primitive>>,
{
type Frame = B::Frame;
type Geometry = Primitive<B::Primitive>;
fn new_frame(&self, size: Size) -> Self::Frame {
self.backend.new_frame(size)
}
fn draw_geometry(&mut self, geometry: Self::Geometry) {
self.draw_primitive(geometry);
}
}
impl<B> compositor::Default for Renderer<B>
where
B: Backend,
{
type Compositor = B::Compositor;
}

View file

@ -1,7 +1,7 @@
use crate::core::{Font, Pixels};
use crate::Antialiasing;
/// The settings of a Backend.
/// The settings of a renderer.
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Settings {
/// The default [`Font`] to use.

View file

@ -9,14 +9,141 @@ pub use paragraph::Paragraph;
pub use cosmic_text;
use crate::core::alignment;
use crate::core::font::{self, Font};
use crate::core::text::Shaping;
use crate::core::{Color, Point, Rectangle, Size};
use crate::core::{Color, Pixels, Point, Rectangle, Size, Transformation};
use once_cell::sync::OnceCell;
use std::borrow::Cow;
use std::sync::{Arc, RwLock, Weak};
/// A text primitive.
#[derive(Debug, Clone, PartialEq)]
pub enum Text {
/// A paragraph.
#[allow(missing_docs)]
Paragraph {
paragraph: paragraph::Weak,
position: Point,
color: Color,
clip_bounds: Rectangle,
transformation: Transformation,
},
/// An editor.
#[allow(missing_docs)]
Editor {
editor: editor::Weak,
position: Point,
color: Color,
clip_bounds: Rectangle,
transformation: Transformation,
},
/// Some cached text.
Cached {
/// The contents of the text.
content: String,
/// The bounds of the text.
bounds: Rectangle,
/// The color of the text.
color: Color,
/// The size of the text in logical pixels.
size: Pixels,
/// The line height of the text.
line_height: Pixels,
/// The font of the text.
font: Font,
/// The horizontal alignment of the text.
horizontal_alignment: alignment::Horizontal,
/// The vertical alignment of the text.
vertical_alignment: alignment::Vertical,
/// The shaping strategy of the text.
shaping: Shaping,
/// The clip bounds of the text.
clip_bounds: Rectangle,
},
/// Some raw text.
#[allow(missing_docs)]
Raw {
raw: Raw,
transformation: Transformation,
},
}
impl Text {
/// Returns the visible bounds of the [`Text`].
pub fn visible_bounds(&self) -> Option<Rectangle> {
let (bounds, horizontal_alignment, vertical_alignment) = match self {
Text::Paragraph {
position,
paragraph,
clip_bounds,
transformation,
..
} => (
Rectangle::new(*position, paragraph.min_bounds)
.intersection(clip_bounds)
.map(|bounds| bounds * *transformation),
Some(paragraph.horizontal_alignment),
Some(paragraph.vertical_alignment),
),
Text::Editor {
editor,
position,
clip_bounds,
transformation,
..
} => (
Rectangle::new(*position, editor.bounds)
.intersection(clip_bounds)
.map(|bounds| bounds * *transformation),
None,
None,
),
Text::Cached {
bounds,
clip_bounds,
horizontal_alignment,
vertical_alignment,
..
} => (
bounds.intersection(clip_bounds),
Some(*horizontal_alignment),
Some(*vertical_alignment),
),
Text::Raw { raw, .. } => (Some(raw.clip_bounds), None, None),
};
let mut bounds = bounds?;
if let Some(alignment) = horizontal_alignment {
match alignment {
alignment::Horizontal::Left => {}
alignment::Horizontal::Center => {
bounds.x -= bounds.width / 2.0;
}
alignment::Horizontal::Right => {
bounds.x -= bounds.width;
}
}
}
if let Some(alignment) = vertical_alignment {
match alignment {
alignment::Vertical::Top => {}
alignment::Vertical::Center => {
bounds.y -= bounds.height / 2.0;
}
alignment::Vertical::Bottom => {
bounds.y -= bounds.height;
}
}
}
Some(bounds)
}
}
/// The regular variant of the [Fira Sans] font.
///
/// It is loaded as part of the default fonts in Wasm builds.

View file

@ -2,22 +2,18 @@
use crate::core::{Font, Size};
use crate::text;
use rustc_hash::{FxHashMap, FxHashSet};
use rustc_hash::{FxHashMap, FxHashSet, FxHasher};
use std::collections::hash_map;
use std::hash::{BuildHasher, Hash, Hasher};
use std::hash::{Hash, Hasher};
/// A store of recently used sections of text.
#[allow(missing_debug_implementations)]
#[derive(Default)]
#[derive(Debug, Default)]
pub struct Cache {
entries: FxHashMap<KeyHash, Entry>,
aliases: FxHashMap<KeyHash, KeyHash>,
recently_used: FxHashSet<KeyHash>,
hasher: HashBuilder,
}
type HashBuilder = xxhash_rust::xxh3::Xxh3Builder;
impl Cache {
/// Creates a new empty [`Cache`].
pub fn new() -> Self {
@ -35,7 +31,7 @@ impl Cache {
font_system: &mut cosmic_text::FontSystem,
key: Key<'_>,
) -> (KeyHash, &mut Entry) {
let hash = key.hash(self.hasher.build_hasher());
let hash = key.hash(FxHasher::default());
if let Some(hash) = self.aliases.get(&hash) {
let _ = self.recently_used.insert(*hash);
@ -77,7 +73,7 @@ impl Cache {
] {
if key.bounds != bounds {
let _ = self.aliases.insert(
Key { bounds, ..key }.hash(self.hasher.build_hasher()),
Key { bounds, ..key }.hash(FxHasher::default()),
hash,
);
}
@ -138,7 +134,7 @@ impl Key<'_> {
pub type KeyHash = u64;
/// A cache entry.
#[allow(missing_debug_implementations)]
#[derive(Debug)]
pub struct Entry {
/// The buffer of text, ready for drawing.
pub buffer: cosmic_text::Buffer,

View file

@ -61,7 +61,7 @@ impl Paragraph {
impl core::text::Paragraph for Paragraph {
type Font = Font;
fn with_text(text: Text<'_, Font>) -> Self {
fn with_text(text: Text<&str>) -> Self {
log::trace!("Allocating paragraph: {}", text.content);
let mut font_system =
@ -146,7 +146,7 @@ impl core::text::Paragraph for Paragraph {
}
}
fn compare(&self, text: Text<'_, Font>) -> core::text::Difference {
fn compare(&self, text: Text<&str>) -> core::text::Difference {
let font_system = text::font_system().read().expect("Read font system");
let paragraph = self.internal();
let metrics = paragraph.buffer.metrics();

View file

@ -10,6 +10,9 @@ homepage.workspace = true
categories.workspace = true
keywords.workspace = true
[lints]
workspace = true
[dependencies]
iced_core.workspace = true

View file

@ -1,3 +1,4 @@
//! A syntax highlighter for iced.
use iced_core as core;
use crate::core::text::highlighter::{self, Format};
@ -16,6 +17,8 @@ static THEMES: Lazy<highlighting::ThemeSet> =
const LINES_PER_SNAPSHOT: usize = 50;
/// A syntax highlighter.
#[derive(Debug)]
pub struct Highlighter {
syntax: &'static parsing::SyntaxReference,
highlighter: highlighting::Highlighter<'static>,
@ -131,25 +134,47 @@ impl highlighter::Highlighter for Highlighter {
}
}
/// The settings of a [`Highlighter`].
#[derive(Debug, Clone, PartialEq)]
pub struct Settings {
/// The [`Theme`] of the [`Highlighter`].
///
/// It dictates the color scheme that will be used for highlighting.
pub theme: Theme,
/// The extension of the file to highlight.
///
/// The [`Highlighter`] will use the extension to automatically determine
/// the grammar to use for highlighting.
pub extension: String,
}
/// A highlight produced by a [`Highlighter`].
#[derive(Debug)]
pub struct Highlight(highlighting::StyleModifier);
impl Highlight {
/// Returns the color of this [`Highlight`].
///
/// If `None`, the original text color should be unchanged.
pub fn color(&self) -> Option<Color> {
self.0.foreground.map(|color| {
Color::from_rgba8(color.r, color.g, color.b, color.a as f32 / 255.0)
})
}
/// Returns the font of this [`Highlight`].
///
/// If `None`, the original font should be unchanged.
pub fn font(&self) -> Option<Font> {
None
}
/// Returns the [`Format`] of the [`Highlight`].
///
/// It contains both the [`color`] and the [`font`].
///
/// [`color`]: Self::color
/// [`font`]: Self::font
pub fn to_format(&self) -> Format<Font> {
Format {
color: self.color(),
@ -158,6 +183,8 @@ impl Highlight {
}
}
/// A highlighting theme.
#[allow(missing_docs)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Theme {
SolarizedDark,
@ -168,6 +195,7 @@ pub enum Theme {
}
impl Theme {
/// A static slice containing all the available themes.
pub const ALL: &'static [Self] = &[
Self::SolarizedDark,
Self::Base16Mocha,
@ -176,6 +204,7 @@ impl Theme {
Self::InspiredGitHub,
];
/// Returns `true` if the [`Theme`] is dark, and false otherwise.
pub fn is_dark(self) -> bool {
match self {
Self::SolarizedDark
@ -209,7 +238,7 @@ impl std::fmt::Display for Theme {
}
}
pub struct ScopeRangeIterator {
struct ScopeRangeIterator {
ops: Vec<(usize, parsing::ScopeStackOp)>,
line_length: usize,
index: usize,

View file

@ -10,13 +10,15 @@ homepage.workspace = true
categories.workspace = true
keywords.workspace = true
[lints]
workspace = true
[features]
wgpu = ["iced_wgpu"]
tiny-skia = ["iced_tiny_skia"]
image = ["iced_tiny_skia?/image", "iced_wgpu?/image"]
svg = ["iced_tiny_skia?/svg", "iced_wgpu?/svg"]
geometry = ["iced_graphics/geometry", "iced_tiny_skia?/geometry", "iced_wgpu?/geometry"]
tracing = ["iced_wgpu?/tracing"]
web-colors = ["iced_wgpu?/web-colors"]
webgl = ["iced_wgpu?/webgl"]
fira-sans = ["iced_graphics/fira-sans"]

View file

@ -1,3 +1,4 @@
//! Compose existing renderers and create type-safe fallback strategies.
use crate::core::image;
use crate::core::renderer;
use crate::core::svg;
@ -8,24 +9,33 @@ use crate::graphics;
use crate::graphics::compositor;
use crate::graphics::mesh;
pub enum Renderer<L, R> {
Left(L),
Right(R),
use std::borrow::Cow;
/// A renderer `A` with a fallback strategy `B`.
///
/// This type can be used to easily compose existing renderers and
/// create custom, type-safe fallback strategies.
#[derive(Debug)]
pub enum Renderer<A, B> {
/// The primary rendering option.
Primary(A),
/// The secondary (or fallback) rendering option.
Secondary(B),
}
macro_rules! delegate {
($renderer:expr, $name:ident, $body:expr) => {
match $renderer {
Self::Left($name) => $body,
Self::Right($name) => $body,
Self::Primary($name) => $body,
Self::Secondary($name) => $body,
}
};
}
impl<L, R> core::Renderer for Renderer<L, R>
impl<A, B> core::Renderer for Renderer<A, B>
where
L: core::Renderer,
R: core::Renderer,
A: core::Renderer,
B: core::Renderer,
{
fn fill_quad(
&mut self,
@ -39,39 +49,43 @@ where
delegate!(self, renderer, renderer.clear());
}
fn start_layer(&mut self) {
delegate!(self, renderer, renderer.start_layer());
fn start_layer(&mut self, bounds: Rectangle) {
delegate!(self, renderer, renderer.start_layer(bounds));
}
fn end_layer(&mut self, bounds: Rectangle) {
delegate!(self, renderer, renderer.end_layer(bounds));
fn end_layer(&mut self) {
delegate!(self, renderer, renderer.end_layer());
}
fn start_transformation(&mut self) {
delegate!(self, renderer, renderer.start_transformation());
fn start_transformation(&mut self, transformation: Transformation) {
delegate!(
self,
renderer,
renderer.start_transformation(transformation)
);
}
fn end_transformation(&mut self, transformation: Transformation) {
delegate!(self, renderer, renderer.end_transformation(transformation));
fn end_transformation(&mut self) {
delegate!(self, renderer, renderer.end_transformation());
}
}
impl<L, R> core::text::Renderer for Renderer<L, R>
impl<A, B> core::text::Renderer for Renderer<A, B>
where
L: core::text::Renderer,
R: core::text::Renderer<
Font = L::Font,
Paragraph = L::Paragraph,
Editor = L::Editor,
A: core::text::Renderer,
B: core::text::Renderer<
Font = A::Font,
Paragraph = A::Paragraph,
Editor = A::Editor,
>,
{
type Font = L::Font;
type Paragraph = L::Paragraph;
type Editor = L::Editor;
type Font = A::Font;
type Paragraph = A::Paragraph;
type Editor = A::Editor;
const ICON_FONT: Self::Font = L::ICON_FONT;
const CHECKMARK_ICON: char = L::CHECKMARK_ICON;
const ARROW_DOWN_ICON: char = L::ARROW_DOWN_ICON;
const ICON_FONT: Self::Font = A::ICON_FONT;
const CHECKMARK_ICON: char = A::CHECKMARK_ICON;
const ARROW_DOWN_ICON: char = A::ARROW_DOWN_ICON;
fn default_font(&self) -> Self::Font {
delegate!(self, renderer, renderer.default_font())
@ -81,10 +95,6 @@ where
delegate!(self, renderer, renderer.default_size())
}
fn load_font(&mut self, font: std::borrow::Cow<'static, [u8]>) {
delegate!(self, renderer, renderer.load_font(font));
}
fn fill_paragraph(
&mut self,
text: &Self::Paragraph,
@ -115,7 +125,7 @@ where
fn fill_text(
&mut self,
text: core::Text<'_, Self::Font>,
text: core::Text<String, Self::Font>,
position: Point,
color: Color,
clip_bounds: Rectangle,
@ -128,12 +138,12 @@ where
}
}
impl<L, R> image::Renderer for Renderer<L, R>
impl<A, B> image::Renderer for Renderer<A, B>
where
L: image::Renderer,
R: image::Renderer<Handle = L::Handle>,
A: image::Renderer,
B: image::Renderer<Handle = A::Handle>,
{
type Handle = L::Handle;
type Handle = A::Handle;
fn measure_image(&self, handle: &Self::Handle) -> Size<u32> {
delegate!(self, renderer, renderer.measure_image(handle))
@ -153,10 +163,10 @@ where
}
}
impl<L, R> svg::Renderer for Renderer<L, R>
impl<A, B> svg::Renderer for Renderer<A, B>
where
L: svg::Renderer,
R: svg::Renderer,
A: svg::Renderer,
B: svg::Renderer,
{
fn measure_svg(&self, handle: &svg::Handle) -> Size<u32> {
delegate!(self, renderer, renderer.measure_svg(handle))
@ -172,37 +182,49 @@ where
}
}
impl<L, R> mesh::Renderer for Renderer<L, R>
impl<A, B> mesh::Renderer for Renderer<A, B>
where
L: mesh::Renderer,
R: mesh::Renderer,
A: mesh::Renderer,
B: mesh::Renderer,
{
fn draw_mesh(&mut self, mesh: graphics::Mesh) {
delegate!(self, renderer, renderer.draw_mesh(mesh));
}
}
pub enum Compositor<L, R>
/// A compositor `A` with a fallback strategy `B`.
///
/// It works analogously to [`Renderer`].
#[derive(Debug)]
pub enum Compositor<A, B>
where
L: graphics::Compositor,
R: graphics::Compositor,
A: graphics::Compositor,
B: graphics::Compositor,
{
Left(L),
Right(R),
/// The primary compositing option.
Primary(A),
/// The secondary (or fallback) compositing option.
Secondary(B),
}
pub enum Surface<L, R> {
Left(L),
Right(R),
/// A surface `A` with a fallback strategy `B`.
///
/// It works analogously to [`Renderer`].
#[derive(Debug)]
pub enum Surface<A, B> {
/// The primary surface option.
Primary(A),
/// The secondary (or fallback) surface option.
Secondary(B),
}
impl<L, R> graphics::Compositor for Compositor<L, R>
impl<A, B> graphics::Compositor for Compositor<A, B>
where
L: graphics::Compositor,
R: graphics::Compositor,
A: graphics::Compositor,
B: graphics::Compositor,
{
type Renderer = Renderer<L::Renderer, R::Renderer>;
type Surface = Surface<L::Surface, R::Surface>;
type Renderer = Renderer<A::Renderer, B::Renderer>;
type Surface = Surface<A::Surface, B::Surface>;
async fn with_backend<W: compositor::Window + Clone>(
settings: graphics::Settings,
@ -233,19 +255,19 @@ where
let mut errors = vec![];
for backend in candidates.iter().map(Option::as_deref) {
match L::with_backend(settings, compatible_window.clone(), backend)
match A::with_backend(settings, compatible_window.clone(), backend)
.await
{
Ok(compositor) => return Ok(Self::Left(compositor)),
Ok(compositor) => return Ok(Self::Primary(compositor)),
Err(error) => {
errors.push(error);
}
}
match R::with_backend(settings, compatible_window.clone(), backend)
match B::with_backend(settings, compatible_window.clone(), backend)
.await
{
Ok(compositor) => return Ok(Self::Right(compositor)),
Ok(compositor) => return Ok(Self::Secondary(compositor)),
Err(error) => {
errors.push(error);
}
@ -257,11 +279,11 @@ where
fn create_renderer(&self) -> Self::Renderer {
match self {
Self::Left(compositor) => {
Renderer::Left(compositor.create_renderer())
Self::Primary(compositor) => {
Renderer::Primary(compositor.create_renderer())
}
Self::Right(compositor) => {
Renderer::Right(compositor.create_renderer())
Self::Secondary(compositor) => {
Renderer::Secondary(compositor.create_renderer())
}
}
}
@ -273,12 +295,12 @@ where
height: u32,
) -> Self::Surface {
match self {
Self::Left(compositor) => {
Surface::Left(compositor.create_surface(window, width, height))
}
Self::Right(compositor) => {
Surface::Right(compositor.create_surface(window, width, height))
}
Self::Primary(compositor) => Surface::Primary(
compositor.create_surface(window, width, height),
),
Self::Secondary(compositor) => Surface::Secondary(
compositor.create_surface(window, width, height),
),
}
}
@ -289,16 +311,20 @@ where
height: u32,
) {
match (self, surface) {
(Self::Left(compositor), Surface::Left(surface)) => {
(Self::Primary(compositor), Surface::Primary(surface)) => {
compositor.configure_surface(surface, width, height);
}
(Self::Right(compositor), Surface::Right(surface)) => {
(Self::Secondary(compositor), Surface::Secondary(surface)) => {
compositor.configure_surface(surface, width, height);
}
_ => unreachable!(),
}
}
fn load_font(&mut self, font: Cow<'static, [u8]>) {
delegate!(self, compositor, compositor.load_font(font));
}
fn fetch_information(&self) -> compositor::Information {
delegate!(self, compositor, compositor.fetch_information())
}
@ -313,9 +339,9 @@ where
) -> Result<(), compositor::SurfaceError> {
match (self, renderer, surface) {
(
Self::Left(compositor),
Renderer::Left(renderer),
Surface::Left(surface),
Self::Primary(compositor),
Renderer::Primary(renderer),
Surface::Primary(surface),
) => compositor.present(
renderer,
surface,
@ -324,9 +350,9 @@ where
overlay,
),
(
Self::Right(compositor),
Renderer::Right(renderer),
Surface::Right(surface),
Self::Secondary(compositor),
Renderer::Secondary(renderer),
Surface::Secondary(surface),
) => compositor.present(
renderer,
surface,
@ -348,9 +374,9 @@ where
) -> Vec<u8> {
match (self, renderer, surface) {
(
Self::Left(compositor),
Renderer::Left(renderer),
Surface::Left(surface),
Self::Primary(compositor),
Renderer::Primary(renderer),
Surface::Primary(surface),
) => compositor.screenshot(
renderer,
surface,
@ -359,9 +385,9 @@ where
overlay,
),
(
Self::Right(compositor),
Renderer::Right(renderer),
Surface::Right(surface),
Self::Secondary(compositor),
Renderer::Secondary(renderer),
Surface::Secondary(surface),
) => compositor.screenshot(
renderer,
surface,
@ -375,21 +401,21 @@ where
}
#[cfg(feature = "wgpu")]
impl<L, R> iced_wgpu::primitive::pipeline::Renderer for Renderer<L, R>
impl<A, B> iced_wgpu::primitive::Renderer for Renderer<A, B>
where
L: iced_wgpu::primitive::pipeline::Renderer,
R: core::Renderer,
A: iced_wgpu::primitive::Renderer,
B: core::Renderer,
{
fn draw_pipeline_primitive(
fn draw_primitive(
&mut self,
bounds: Rectangle,
primitive: impl iced_wgpu::primitive::pipeline::Primitive,
primitive: impl iced_wgpu::Primitive,
) {
match self {
Self::Left(renderer) => {
renderer.draw_pipeline_primitive(bounds, primitive);
Self::Primary(renderer) => {
renderer.draw_primitive(bounds, primitive);
}
Self::Right(_) => {
Self::Secondary(_) => {
log::warn!(
"Custom shader primitive is not supported with this renderer."
);
@ -401,31 +427,35 @@ where
#[cfg(feature = "geometry")]
mod geometry {
use super::Renderer;
use crate::core::{Point, Radians, Size, Vector};
use crate::core::{Point, Radians, Rectangle, Size, Vector};
use crate::graphics::geometry::{self, Fill, Path, Stroke, Text};
use crate::graphics::Cached;
impl<L, R> geometry::Renderer for Renderer<L, R>
impl<A, B> geometry::Renderer for Renderer<A, B>
where
L: geometry::Renderer,
R: geometry::Renderer,
A: geometry::Renderer,
B: geometry::Renderer,
{
type Geometry = Geometry<L::Geometry, R::Geometry>;
type Frame = Frame<L::Frame, R::Frame>;
type Geometry = Geometry<A::Geometry, B::Geometry>;
type Frame = Frame<A::Frame, B::Frame>;
fn new_frame(&self, size: iced_graphics::core::Size) -> Self::Frame {
match self {
Self::Left(renderer) => Frame::Left(renderer.new_frame(size)),
Self::Right(renderer) => Frame::Right(renderer.new_frame(size)),
Self::Primary(renderer) => {
Frame::Primary(renderer.new_frame(size))
}
Self::Secondary(renderer) => {
Frame::Secondary(renderer.new_frame(size))
}
}
}
fn draw_geometry(&mut self, geometry: Self::Geometry) {
match (self, geometry) {
(Self::Left(renderer), Geometry::Left(geometry)) => {
(Self::Primary(renderer), Geometry::Primary(geometry)) => {
renderer.draw_geometry(geometry);
}
(Self::Right(renderer), Geometry::Right(geometry)) => {
(Self::Secondary(renderer), Geometry::Secondary(geometry)) => {
renderer.draw_geometry(geometry);
}
_ => unreachable!(),
@ -433,44 +463,59 @@ mod geometry {
}
}
pub enum Geometry<L, R> {
Left(L),
Right(R),
#[derive(Debug, Clone)]
pub enum Geometry<A, B> {
Primary(A),
Secondary(B),
}
impl<L, R> Cached for Geometry<L, R>
impl<A, B> Cached for Geometry<A, B>
where
L: Cached,
R: Cached,
A: Cached,
B: Cached,
{
type Cache = Geometry<L::Cache, R::Cache>;
type Cache = Geometry<A::Cache, B::Cache>;
fn load(cache: &Self::Cache) -> Self {
match cache {
Geometry::Left(cache) => Self::Left(L::load(cache)),
Geometry::Right(cache) => Self::Right(R::load(cache)),
Geometry::Primary(cache) => Self::Primary(A::load(cache)),
Geometry::Secondary(cache) => Self::Secondary(B::load(cache)),
}
}
fn cache(self) -> Self::Cache {
match self {
Self::Left(geometry) => Geometry::Left(geometry.cache()),
Self::Right(geometry) => Geometry::Right(geometry.cache()),
fn cache(self, previous: Option<Self::Cache>) -> Self::Cache {
match (self, previous) {
(
Self::Primary(geometry),
Some(Geometry::Primary(previous)),
) => Geometry::Primary(geometry.cache(Some(previous))),
(Self::Primary(geometry), None) => {
Geometry::Primary(geometry.cache(None))
}
(
Self::Secondary(geometry),
Some(Geometry::Secondary(previous)),
) => Geometry::Secondary(geometry.cache(Some(previous))),
(Self::Secondary(geometry), None) => {
Geometry::Secondary(geometry.cache(None))
}
_ => unreachable!(),
}
}
}
pub enum Frame<L, R> {
Left(L),
Right(R),
#[derive(Debug)]
pub enum Frame<A, B> {
Primary(A),
Secondary(B),
}
impl<L, R> geometry::frame::Backend for Frame<L, R>
impl<A, B> geometry::frame::Backend for Frame<A, B>
where
L: geometry::frame::Backend,
R: geometry::frame::Backend,
A: geometry::frame::Backend,
B: geometry::frame::Backend,
{
type Geometry = Geometry<L::Geometry, R::Geometry>;
type Geometry = Geometry<A::Geometry, B::Geometry>;
fn width(&self) -> f32 {
delegate!(self, frame, frame.width())
@ -517,19 +562,19 @@ mod geometry {
delegate!(self, frame, frame.pop_transform());
}
fn draft(&mut self, size: Size) -> Self {
fn draft(&mut self, bounds: Rectangle) -> Self {
match self {
Self::Left(frame) => Self::Left(frame.draft(size)),
Self::Right(frame) => Self::Right(frame.draft(size)),
Self::Primary(frame) => Self::Primary(frame.draft(bounds)),
Self::Secondary(frame) => Self::Secondary(frame.draft(bounds)),
}
}
fn paste(&mut self, frame: Self, at: Point) {
match (self, frame) {
(Self::Left(target), Self::Left(source)) => {
(Self::Primary(target), Self::Primary(source)) => {
target.paste(source, at);
}
(Self::Right(target), Self::Right(source)) => {
(Self::Secondary(target), Self::Secondary(source)) => {
target.paste(source, at);
}
_ => unreachable!(),
@ -554,17 +599,21 @@ mod geometry {
fn into_geometry(self) -> Self::Geometry {
match self {
Frame::Left(frame) => Geometry::Left(frame.into_geometry()),
Frame::Right(frame) => Geometry::Right(frame.into_geometry()),
Frame::Primary(frame) => {
Geometry::Primary(frame.into_geometry())
}
Frame::Secondary(frame) => {
Geometry::Secondary(frame.into_geometry())
}
}
}
}
}
impl<L, R> compositor::Default for Renderer<L, R>
impl<A, B> compositor::Default for Renderer<A, B>
where
L: compositor::Default,
R: compositor::Default,
A: compositor::Default,
B: compositor::Default,
{
type Compositor = Compositor<L::Compositor, R::Compositor>;
type Compositor = Compositor<A::Compositor, B::Compositor>;
}

View file

@ -1,5 +1,4 @@
#![forbid(rust_2018_idioms)]
#![deny(unsafe_code, unused_results, rustdoc::broken_intra_doc_links)]
//! The official renderer for iced.
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
#[cfg(feature = "wgpu")]
pub use iced_wgpu as wgpu;

View file

@ -10,6 +10,9 @@ homepage.workspace = true
categories.workspace = true
keywords.workspace = true
[lints]
workspace = true
[features]
debug = []
multi-window = []

View file

@ -8,13 +8,6 @@
#![doc(
html_logo_url = "https://raw.githubusercontent.com/iced-rs/iced/9ab6923e943f784985e9ef9ca28b10278297225d/docs/logo.svg"
)]
#![forbid(unsafe_code, rust_2018_idioms)]
#![deny(
missing_debug_implementations,
missing_docs,
unused_results,
rustdoc::broken_intra_doc_links
)]
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
pub mod clipboard;
pub mod command;

View file

@ -9,10 +9,12 @@ pub use crate::core::renderer::{self, Renderer};
pub use crate::core::svg;
pub use crate::core::text::{self, Text};
pub use crate::core::widget::{self, Widget};
pub use crate::core::{Hasher, Shell};
pub use crate::core::Shell;
pub use crate::renderer::graphics;
pub mod subscription {
//! Write your own subscriptions.
pub use crate::runtime::futures::subscription::{EventStream, Recipe};
pub use crate::runtime::futures::subscription::{
EventStream, Hasher, Recipe,
};
}

View file

@ -165,13 +165,6 @@
#![doc(
html_logo_url = "https://raw.githubusercontent.com/iced-rs/iced/9ab6923e943f784985e9ef9ca28b10278297225d/docs/logo.svg"
)]
#![forbid(rust_2018_idioms, unsafe_code)]
#![deny(
missing_debug_implementations,
missing_docs,
unused_results,
rustdoc::broken_intra_doc_links
)]
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
#![cfg_attr(docsrs, feature(doc_cfg))]
use iced_widget::graphics;

View file

@ -10,6 +10,9 @@ homepage.workspace = true
categories.workspace = true
keywords.workspace = true
[lints]
workspace = true
[features]
image = ["iced_graphics/image"]
svg = ["resvg"]
@ -25,7 +28,6 @@ log.workspace = true
rustc-hash.workspace = true
softbuffer.workspace = true
tiny-skia.workspace = true
xxhash-rust.workspace = true
resvg.workspace = true
resvg.optional = true

File diff suppressed because it is too large Load diff

831
tiny_skia/src/engine.rs Normal file
View file

@ -0,0 +1,831 @@
use crate::core::renderer::Quad;
use crate::core::{
Background, Color, Gradient, Rectangle, Size, Transformation, Vector,
};
use crate::graphics::{Image, Text};
use crate::text;
use crate::Primitive;
#[derive(Debug)]
pub struct Engine {
text_pipeline: text::Pipeline,
#[cfg(feature = "image")]
pub(crate) raster_pipeline: crate::raster::Pipeline,
#[cfg(feature = "svg")]
pub(crate) vector_pipeline: crate::vector::Pipeline,
}
impl Engine {
pub fn new() -> Self {
Self {
text_pipeline: text::Pipeline::new(),
#[cfg(feature = "image")]
raster_pipeline: crate::raster::Pipeline::new(),
#[cfg(feature = "svg")]
vector_pipeline: crate::vector::Pipeline::new(),
}
}
pub fn draw_quad(
&mut self,
quad: &Quad,
background: &Background,
transformation: Transformation,
pixels: &mut tiny_skia::PixmapMut<'_>,
clip_mask: &mut tiny_skia::Mask,
clip_bounds: Rectangle,
) {
debug_assert!(
quad.bounds.width.is_normal(),
"Quad with non-normal width!"
);
debug_assert!(
quad.bounds.height.is_normal(),
"Quad with non-normal height!"
);
let physical_bounds = quad.bounds * transformation;
if !clip_bounds.intersects(&physical_bounds) {
return;
}
let clip_mask = (!physical_bounds.is_within(&clip_bounds))
.then_some(clip_mask as &_);
let transform = into_transform(transformation);
// Make sure the border radius is not larger than the bounds
let border_width = quad
.border
.width
.min(quad.bounds.width / 2.0)
.min(quad.bounds.height / 2.0);
let mut fill_border_radius = <[f32; 4]>::from(quad.border.radius);
for radius in &mut fill_border_radius {
*radius = (*radius)
.min(quad.bounds.width / 2.0)
.min(quad.bounds.height / 2.0);
}
let path = rounded_rectangle(quad.bounds, fill_border_radius);
let shadow = quad.shadow;
if shadow.color.a > 0.0 {
let shadow_bounds = Rectangle {
x: quad.bounds.x + shadow.offset.x - shadow.blur_radius,
y: quad.bounds.y + shadow.offset.y - shadow.blur_radius,
width: quad.bounds.width + shadow.blur_radius * 2.0,
height: quad.bounds.height + shadow.blur_radius * 2.0,
} * transformation;
let radii = fill_border_radius
.into_iter()
.map(|radius| radius * transformation.scale_factor())
.collect::<Vec<_>>();
let (x, y, width, height) = (
shadow_bounds.x as u32,
shadow_bounds.y as u32,
shadow_bounds.width as u32,
shadow_bounds.height as u32,
);
let half_width = physical_bounds.width / 2.0;
let half_height = physical_bounds.height / 2.0;
let colors = (y..y + height)
.flat_map(|y| (x..x + width).map(move |x| (x as f32, y as f32)))
.filter_map(|(x, y)| {
tiny_skia::Size::from_wh(half_width, half_height).map(
|size| {
let shadow_distance = rounded_box_sdf(
Vector::new(
x - physical_bounds.position().x
- (shadow.offset.x
* transformation.scale_factor())
- half_width,
y - physical_bounds.position().y
- (shadow.offset.y
* transformation.scale_factor())
- half_height,
),
size,
&radii,
)
.max(0.0);
let shadow_alpha = 1.0
- smoothstep(
-shadow.blur_radius
* transformation.scale_factor(),
shadow.blur_radius
* transformation.scale_factor(),
shadow_distance,
);
let mut color = into_color(shadow.color);
color.apply_opacity(shadow_alpha);
color.to_color_u8().premultiply()
},
)
})
.collect();
if let Some(pixmap) = tiny_skia::IntSize::from_wh(width, height)
.and_then(|size| {
tiny_skia::Pixmap::from_vec(
bytemuck::cast_vec(colors),
size,
)
})
{
pixels.draw_pixmap(
x as i32,
y as i32,
pixmap.as_ref(),
&tiny_skia::PixmapPaint::default(),
tiny_skia::Transform::default(),
None,
);
}
}
pixels.fill_path(
&path,
&tiny_skia::Paint {
shader: match background {
Background::Color(color) => {
tiny_skia::Shader::SolidColor(into_color(*color))
}
Background::Gradient(Gradient::Linear(linear)) => {
let (start, end) =
linear.angle.to_distance(&quad.bounds);
let stops: Vec<tiny_skia::GradientStop> = linear
.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")
}
},
anti_alias: true,
..tiny_skia::Paint::default()
},
tiny_skia::FillRule::EvenOdd,
transform,
clip_mask,
);
if border_width > 0.0 {
// Border path is offset by half the border width
let border_bounds = Rectangle {
x: quad.bounds.x + border_width / 2.0,
y: quad.bounds.y + border_width / 2.0,
width: quad.bounds.width - border_width,
height: quad.bounds.height - border_width,
};
// Make sure the border radius is correct
let mut border_radius = <[f32; 4]>::from(quad.border.radius);
let mut is_simple_border = true;
for radius in &mut border_radius {
*radius = if *radius == 0.0 {
// Path should handle this fine
0.0
} else if *radius > border_width / 2.0 {
*radius - border_width / 2.0
} else {
is_simple_border = false;
0.0
}
.min(border_bounds.width / 2.0)
.min(border_bounds.height / 2.0);
}
// Stroking a path works well in this case
if is_simple_border {
let border_path =
rounded_rectangle(border_bounds, border_radius);
pixels.stroke_path(
&border_path,
&tiny_skia::Paint {
shader: tiny_skia::Shader::SolidColor(into_color(
quad.border.color,
)),
anti_alias: true,
..tiny_skia::Paint::default()
},
&tiny_skia::Stroke {
width: border_width,
..tiny_skia::Stroke::default()
},
transform,
clip_mask,
);
} else {
// Draw corners that have too small border radii as having no border radius,
// but mask them with the rounded rectangle with the correct border radius.
let mut temp_pixmap = tiny_skia::Pixmap::new(
quad.bounds.width as u32,
quad.bounds.height as u32,
)
.unwrap();
let mut quad_mask = tiny_skia::Mask::new(
quad.bounds.width as u32,
quad.bounds.height as u32,
)
.unwrap();
let zero_bounds = Rectangle {
x: 0.0,
y: 0.0,
width: quad.bounds.width,
height: quad.bounds.height,
};
let path = rounded_rectangle(zero_bounds, fill_border_radius);
quad_mask.fill_path(
&path,
tiny_skia::FillRule::EvenOdd,
true,
transform,
);
let path_bounds = Rectangle {
x: border_width / 2.0,
y: border_width / 2.0,
width: quad.bounds.width - border_width,
height: quad.bounds.height - border_width,
};
let border_radius_path =
rounded_rectangle(path_bounds, border_radius);
temp_pixmap.stroke_path(
&border_radius_path,
&tiny_skia::Paint {
shader: tiny_skia::Shader::SolidColor(into_color(
quad.border.color,
)),
anti_alias: true,
..tiny_skia::Paint::default()
},
&tiny_skia::Stroke {
width: border_width,
..tiny_skia::Stroke::default()
},
transform,
Some(&quad_mask),
);
pixels.draw_pixmap(
quad.bounds.x as i32,
quad.bounds.y as i32,
temp_pixmap.as_ref(),
&tiny_skia::PixmapPaint::default(),
transform,
clip_mask,
);
}
}
}
pub fn draw_text(
&mut self,
text: &Text,
transformation: Transformation,
pixels: &mut tiny_skia::PixmapMut<'_>,
clip_mask: &mut tiny_skia::Mask,
clip_bounds: Rectangle,
) {
match text {
Text::Paragraph {
paragraph,
position,
color,
clip_bounds: _, // TODO
transformation: local_transformation,
} => {
let transformation = transformation * *local_transformation;
let physical_bounds =
Rectangle::new(*position, paragraph.min_bounds)
* transformation;
if !clip_bounds.intersects(&physical_bounds) {
return;
}
let clip_mask = (!physical_bounds.is_within(&clip_bounds))
.then_some(clip_mask as &_);
self.text_pipeline.draw_paragraph(
paragraph,
*position,
*color,
pixels,
clip_mask,
transformation,
);
}
Text::Editor {
editor,
position,
color,
clip_bounds: _, // TODO
transformation: local_transformation,
} => {
let transformation = transformation * *local_transformation;
let physical_bounds =
Rectangle::new(*position, editor.bounds) * transformation;
if !clip_bounds.intersects(&physical_bounds) {
return;
}
let clip_mask = (!physical_bounds.is_within(&clip_bounds))
.then_some(clip_mask as &_);
self.text_pipeline.draw_editor(
editor,
*position,
*color,
pixels,
clip_mask,
transformation,
);
}
Text::Cached {
content,
bounds,
color,
size,
line_height,
font,
horizontal_alignment,
vertical_alignment,
shaping,
clip_bounds: text_bounds, // TODO
} => {
let physical_bounds = *text_bounds * transformation;
if !clip_bounds.intersects(&physical_bounds) {
return;
}
let clip_mask = (!physical_bounds.is_within(&clip_bounds))
.then_some(clip_mask as &_);
self.text_pipeline.draw_cached(
content,
*bounds,
*color,
*size,
*line_height,
*font,
*horizontal_alignment,
*vertical_alignment,
*shaping,
pixels,
clip_mask,
transformation,
);
}
Text::Raw {
raw,
transformation: local_transformation,
} => {
let Some(buffer) = raw.buffer.upgrade() else {
return;
};
let transformation = transformation * *local_transformation;
let (width, height) = buffer.size();
let physical_bounds =
Rectangle::new(raw.position, Size::new(width, height))
* transformation;
if !clip_bounds.intersects(&physical_bounds) {
return;
}
let clip_mask = (!physical_bounds.is_within(&clip_bounds))
.then_some(clip_mask as &_);
self.text_pipeline.draw_raw(
&buffer,
raw.position,
raw.color,
pixels,
clip_mask,
transformation,
);
}
}
}
pub fn draw_primitive(
&mut self,
primitive: &Primitive,
transformation: Transformation,
pixels: &mut tiny_skia::PixmapMut<'_>,
clip_mask: &mut tiny_skia::Mask,
layer_bounds: Rectangle,
) {
match primitive {
Primitive::Fill { path, paint, rule } => {
let physical_bounds = {
let bounds = path.bounds();
Rectangle {
x: bounds.x(),
y: bounds.y(),
width: bounds.width(),
height: bounds.height(),
} * transformation
};
let Some(clip_bounds) =
layer_bounds.intersection(&physical_bounds)
else {
return;
};
let clip_mask =
(physical_bounds != clip_bounds).then_some(clip_mask as &_);
pixels.fill_path(
path,
paint,
*rule,
into_transform(transformation),
clip_mask,
);
}
Primitive::Stroke {
path,
paint,
stroke,
} => {
let physical_bounds = {
let bounds = path.bounds();
Rectangle {
x: bounds.x(),
y: bounds.y(),
width: bounds.width(),
height: bounds.height(),
} * transformation
};
let Some(clip_bounds) =
layer_bounds.intersection(&physical_bounds)
else {
return;
};
let clip_mask =
(physical_bounds != clip_bounds).then_some(clip_mask as &_);
pixels.stroke_path(
path,
paint,
stroke,
into_transform(transformation),
clip_mask,
);
}
}
}
pub fn draw_image(
&mut self,
image: &Image,
_transformation: Transformation,
_pixels: &mut tiny_skia::PixmapMut<'_>,
_clip_mask: &mut tiny_skia::Mask,
_clip_bounds: Rectangle,
) {
match image {
#[cfg(feature = "image")]
Image::Raster {
handle,
filter_method,
bounds,
} => {
let physical_bounds = *bounds * _transformation;
if !_clip_bounds.intersects(&physical_bounds) {
return;
}
let clip_mask = (!physical_bounds.is_within(&_clip_bounds))
.then_some(_clip_mask as &_);
self.raster_pipeline.draw(
handle,
*filter_method,
*bounds,
_pixels,
into_transform(_transformation),
clip_mask,
);
}
#[cfg(feature = "svg")]
Image::Vector {
handle,
color,
bounds,
} => {
let physical_bounds = *bounds * _transformation;
if !_clip_bounds.intersects(&physical_bounds) {
return;
}
let clip_mask = (!physical_bounds.is_within(&_clip_bounds))
.then_some(_clip_mask as &_);
self.vector_pipeline.draw(
handle,
*color,
physical_bounds,
_pixels,
clip_mask,
);
}
#[cfg(not(feature = "image"))]
Image::Raster { .. } => {
log::warn!(
"Unsupported primitive in `iced_tiny_skia`: {image:?}",
);
}
#[cfg(not(feature = "svg"))]
Image::Vector { .. } => {
log::warn!(
"Unsupported primitive in `iced_tiny_skia`: {image:?}",
);
}
}
}
pub fn trim(&mut self) {
self.text_pipeline.trim_cache();
#[cfg(feature = "image")]
self.raster_pipeline.trim_cache();
#[cfg(feature = "svg")]
self.vector_pipeline.trim_cache();
}
}
pub fn into_color(color: Color) -> tiny_skia::Color {
tiny_skia::Color::from_rgba(color.b, color.g, color.r, color.a)
.expect("Convert color from iced to tiny_skia")
}
fn into_transform(transformation: Transformation) -> tiny_skia::Transform {
let translation = transformation.translation();
tiny_skia::Transform {
sx: transformation.scale_factor(),
kx: 0.0,
ky: 0.0,
sy: transformation.scale_factor(),
tx: translation.x,
ty: translation.y,
}
}
fn rounded_rectangle(
bounds: Rectangle,
border_radius: [f32; 4],
) -> tiny_skia::Path {
let [top_left, top_right, bottom_right, bottom_left] = border_radius;
if top_left == 0.0
&& top_right == 0.0
&& bottom_right == 0.0
&& bottom_left == 0.0
{
return tiny_skia::PathBuilder::from_rect(
tiny_skia::Rect::from_xywh(
bounds.x,
bounds.y,
bounds.width,
bounds.height,
)
.expect("Build quad rectangle"),
);
}
if top_left == top_right
&& top_left == bottom_right
&& top_left == bottom_left
&& top_left == bounds.width / 2.0
&& top_left == bounds.height / 2.0
{
return tiny_skia::PathBuilder::from_circle(
bounds.x + bounds.width / 2.0,
bounds.y + bounds.height / 2.0,
top_left,
)
.expect("Build circle path");
}
let mut builder = tiny_skia::PathBuilder::new();
builder.move_to(bounds.x + top_left, bounds.y);
builder.line_to(bounds.x + bounds.width - top_right, bounds.y);
if top_right > 0.0 {
arc_to(
&mut builder,
bounds.x + bounds.width - top_right,
bounds.y,
bounds.x + bounds.width,
bounds.y + top_right,
top_right,
);
}
maybe_line_to(
&mut builder,
bounds.x + bounds.width,
bounds.y + bounds.height - bottom_right,
);
if bottom_right > 0.0 {
arc_to(
&mut builder,
bounds.x + bounds.width,
bounds.y + bounds.height - bottom_right,
bounds.x + bounds.width - bottom_right,
bounds.y + bounds.height,
bottom_right,
);
}
maybe_line_to(
&mut builder,
bounds.x + bottom_left,
bounds.y + bounds.height,
);
if bottom_left > 0.0 {
arc_to(
&mut builder,
bounds.x + bottom_left,
bounds.y + bounds.height,
bounds.x,
bounds.y + bounds.height - bottom_left,
bottom_left,
);
}
maybe_line_to(&mut builder, bounds.x, bounds.y + top_left);
if top_left > 0.0 {
arc_to(
&mut builder,
bounds.x,
bounds.y + top_left,
bounds.x + top_left,
bounds.y,
top_left,
);
}
builder.finish().expect("Build rounded rectangle path")
}
fn maybe_line_to(path: &mut tiny_skia::PathBuilder, x: f32, y: f32) {
if path.last_point() != Some(tiny_skia::Point { x, y }) {
path.line_to(x, y);
}
}
fn arc_to(
path: &mut tiny_skia::PathBuilder,
x_from: f32,
y_from: f32,
x_to: f32,
y_to: f32,
radius: f32,
) {
let svg_arc = kurbo::SvgArc {
from: kurbo::Point::new(f64::from(x_from), f64::from(y_from)),
to: kurbo::Point::new(f64::from(x_to), f64::from(y_to)),
radii: kurbo::Vec2::new(f64::from(radius), f64::from(radius)),
x_rotation: 0.0,
large_arc: false,
sweep: true,
};
match kurbo::Arc::from_svg_arc(&svg_arc) {
Some(arc) => {
arc.to_cubic_beziers(0.1, |p1, p2, p| {
path.cubic_to(
p1.x as f32,
p1.y as f32,
p2.x as f32,
p2.y as f32,
p.x as f32,
p.y as f32,
);
});
}
None => {
path.line_to(x_to, y_to);
}
}
}
fn smoothstep(a: f32, b: f32, x: f32) -> f32 {
let x = ((x - a) / (b - a)).clamp(0.0, 1.0);
x * x * (3.0 - 2.0 * x)
}
fn rounded_box_sdf(
to_center: Vector,
size: tiny_skia::Size,
radii: &[f32],
) -> f32 {
let radius = match (to_center.x > 0.0, to_center.y > 0.0) {
(true, true) => radii[2],
(true, false) => radii[1],
(false, true) => radii[3],
(false, false) => radii[0],
};
let x = (to_center.x.abs() - size.width() + radius).max(0.0);
let y = (to_center.y.abs() - size.height() + radius).max(0.0);
(x.powf(2.0) + y.powf(2.0)).sqrt() - radius
}
pub fn adjust_clip_mask(clip_mask: &mut tiny_skia::Mask, bounds: Rectangle) {
clip_mask.clear();
let path = {
let mut builder = tiny_skia::PathBuilder::new();
builder.push_rect(
tiny_skia::Rect::from_xywh(
bounds.x,
bounds.y,
bounds.width,
bounds.height,
)
.unwrap(),
);
builder.finish().unwrap()
};
clip_mask.fill_path(
&path,
tiny_skia::FillRule::EvenOdd,
false,
tiny_skia::Transform::default(),
);
}

View file

@ -1,57 +1,98 @@
use crate::core::text::LineHeight;
use crate::core::{
Pixels, Point, Radians, Rectangle, Size, Transformation, Vector,
};
use crate::core::{Pixels, Point, Radians, Rectangle, Size, Vector};
use crate::graphics::geometry::fill::{self, Fill};
use crate::graphics::geometry::stroke::{self, Stroke};
use crate::graphics::geometry::{self, Path, Style, Text};
use crate::graphics::Gradient;
use crate::primitive::{self, Primitive};
use crate::graphics::geometry::{self, Path, Style};
use crate::graphics::{Cached, Gradient, Text};
use crate::Primitive;
use std::rc::Rc;
#[derive(Debug)]
pub enum Geometry {
Live {
text: Vec<Text>,
primitives: Vec<Primitive>,
clip_bounds: Rectangle,
},
Cache(Cache),
}
#[derive(Debug, Clone)]
pub struct Cache {
pub text: Rc<[Text]>,
pub primitives: Rc<[Primitive]>,
pub clip_bounds: Rectangle,
}
impl Cached for Geometry {
type Cache = Cache;
fn load(cache: &Cache) -> Self {
Self::Cache(cache.clone())
}
fn cache(self, _previous: Option<Cache>) -> Cache {
match self {
Self::Live {
primitives,
text,
clip_bounds,
} => Cache {
primitives: Rc::from(primitives),
text: Rc::from(text),
clip_bounds,
},
Self::Cache(cache) => cache,
}
}
}
#[derive(Debug)]
pub struct Frame {
size: Size,
clip_bounds: Rectangle,
transform: tiny_skia::Transform,
stack: Vec<tiny_skia::Transform>,
primitives: Vec<Primitive>,
text: Vec<Text>,
}
impl Frame {
pub fn new(size: Size) -> Self {
Self {
size,
transform: tiny_skia::Transform::identity(),
stack: Vec::new(),
primitives: Vec::new(),
}
Self::with_clip(Rectangle::with_size(size))
}
pub fn into_primitive(self) -> Primitive {
Primitive::Clip {
bounds: Rectangle::new(Point::ORIGIN, self.size),
content: Box::new(Primitive::Group {
primitives: self.primitives,
}),
pub fn with_clip(clip_bounds: Rectangle) -> Self {
Self {
clip_bounds,
stack: Vec::new(),
primitives: Vec::new(),
text: Vec::new(),
transform: tiny_skia::Transform::from_translate(
clip_bounds.x,
clip_bounds.y,
),
}
}
}
impl geometry::frame::Backend for Frame {
type Geometry = Primitive;
type Geometry = Geometry;
fn width(&self) -> f32 {
self.size.width
self.clip_bounds.width
}
fn height(&self) -> f32 {
self.size.height
self.clip_bounds.height
}
fn size(&self) -> Size {
self.size
self.clip_bounds.size()
}
fn center(&self) -> Point {
Point::new(self.size.width / 2.0, self.size.height / 2.0)
Point::new(self.clip_bounds.width / 2.0, self.clip_bounds.height / 2.0)
}
fn fill(&mut self, path: &Path, fill: impl Into<Fill>) {
@ -66,12 +107,11 @@ impl geometry::frame::Backend for Frame {
let mut paint = into_paint(fill.style);
paint.shader.transform(self.transform);
self.primitives
.push(Primitive::Custom(primitive::Custom::Fill {
path,
paint,
rule: into_fill_rule(fill.rule),
}));
self.primitives.push(Primitive::Fill {
path,
paint,
rule: into_fill_rule(fill.rule),
});
}
fn fill_rectangle(
@ -94,12 +134,11 @@ impl geometry::frame::Backend for Frame {
};
paint.shader.transform(self.transform);
self.primitives
.push(Primitive::Custom(primitive::Custom::Fill {
path,
paint,
rule: into_fill_rule(fill.rule),
}));
self.primitives.push(Primitive::Fill {
path,
paint,
rule: into_fill_rule(fill.rule),
});
}
fn stroke<'a>(&mut self, path: &Path, stroke: impl Into<Stroke<'a>>) {
@ -115,20 +154,19 @@ impl geometry::frame::Backend for Frame {
let mut paint = into_paint(stroke.style);
paint.shader.transform(self.transform);
self.primitives
.push(Primitive::Custom(primitive::Custom::Stroke {
path,
paint,
stroke: skia_stroke,
}));
self.primitives.push(Primitive::Stroke {
path,
paint,
stroke: skia_stroke,
});
}
fn fill_text(&mut self, text: impl Into<Text>) {
fn fill_text(&mut self, text: impl Into<geometry::Text>) {
let text = text.into();
let (scale_x, scale_y) = self.transform.get_scale();
if self.transform.is_scale_translate()
if !self.transform.has_skew()
&& scale_x == scale_y
&& scale_x > 0.0
&& scale_y > 0.0
@ -170,12 +208,12 @@ impl geometry::frame::Backend for Frame {
};
// TODO: Honor layering!
self.primitives.push(Primitive::Text {
self.text.push(Text::Cached {
content: text.content,
bounds,
color: text.color,
size,
line_height,
line_height: line_height.to_absolute(size),
font: text.font,
horizontal_alignment: text.horizontal_alignment,
vertical_alignment: text.vertical_alignment,
@ -195,15 +233,13 @@ impl geometry::frame::Backend for Frame {
self.transform = self.stack.pop().expect("Pop transform");
}
fn draft(&mut self, size: Size) -> Self {
Self::new(size)
fn draft(&mut self, clip_bounds: Rectangle) -> Self {
Self::with_clip(clip_bounds)
}
fn paste(&mut self, frame: Self, at: Point) {
self.primitives.push(Primitive::Transform {
transformation: Transformation::translate(at.x, at.y),
content: Box::new(frame.into_primitive()),
});
fn paste(&mut self, frame: Self, _at: Point) {
self.primitives.extend(frame.primitives);
self.text.extend(frame.text);
}
fn translate(&mut self, translation: Vector) {
@ -229,8 +265,12 @@ impl geometry::frame::Backend for Frame {
self.transform = self.transform.pre_scale(scale.x, scale.y);
}
fn into_geometry(self) -> Self::Geometry {
self.into_primitive()
fn into_geometry(self) -> Geometry {
Geometry::Live {
primitives: self.primitives,
text: self.text,
clip_bounds: self.clip_bounds,
}
}
}

333
tiny_skia/src/layer.rs Normal file
View file

@ -0,0 +1,333 @@
use crate::core::image;
use crate::core::renderer::Quad;
use crate::core::svg;
use crate::core::{Background, Color, Point, Rectangle, Transformation};
use crate::graphics::damage;
use crate::graphics::layer;
use crate::graphics::text::{Editor, Paragraph, Text};
use crate::graphics::{self, Image};
use crate::Primitive;
use std::rc::Rc;
pub type Stack = layer::Stack<Layer>;
#[derive(Debug, Clone)]
pub struct Layer {
pub bounds: Rectangle,
pub quads: Vec<(Quad, Background)>,
pub primitives: Vec<Item<Primitive>>,
pub text: Vec<Item<Text>>,
pub images: Vec<Image>,
}
impl Layer {
pub fn draw_quad(
&mut self,
mut quad: Quad,
background: Background,
transformation: Transformation,
) {
quad.bounds = quad.bounds * transformation;
self.quads.push((quad, background));
}
pub fn draw_paragraph(
&mut self,
paragraph: &Paragraph,
position: Point,
color: Color,
clip_bounds: Rectangle,
transformation: Transformation,
) {
let paragraph = Text::Paragraph {
paragraph: paragraph.downgrade(),
position,
color,
clip_bounds,
transformation,
};
self.text.push(Item::Live(paragraph));
}
pub fn draw_editor(
&mut self,
editor: &Editor,
position: Point,
color: Color,
clip_bounds: Rectangle,
transformation: Transformation,
) {
let editor = Text::Editor {
editor: editor.downgrade(),
position,
color,
clip_bounds,
transformation,
};
self.text.push(Item::Live(editor));
}
pub fn draw_text(
&mut self,
text: crate::core::Text,
position: Point,
color: Color,
clip_bounds: Rectangle,
transformation: Transformation,
) {
let text = Text::Cached {
content: text.content,
bounds: Rectangle::new(position, text.bounds) * transformation,
color,
size: text.size * transformation.scale_factor(),
line_height: text.line_height.to_absolute(text.size)
* transformation.scale_factor(),
font: text.font,
horizontal_alignment: text.horizontal_alignment,
vertical_alignment: text.vertical_alignment,
shaping: text.shaping,
clip_bounds: clip_bounds * transformation,
};
self.text.push(Item::Live(text));
}
pub fn draw_text_group(
&mut self,
text: Vec<Text>,
clip_bounds: Rectangle,
transformation: Transformation,
) {
self.text
.push(Item::Group(text, clip_bounds, transformation));
}
pub fn draw_text_cache(
&mut self,
text: Rc<[Text]>,
clip_bounds: Rectangle,
transformation: Transformation,
) {
self.text
.push(Item::Cached(text, clip_bounds, transformation));
}
pub fn draw_image(
&mut self,
handle: image::Handle,
filter_method: image::FilterMethod,
bounds: Rectangle,
transformation: Transformation,
) {
let image = Image::Raster {
handle,
filter_method,
bounds: bounds * transformation,
};
self.images.push(image);
}
pub fn draw_svg(
&mut self,
handle: svg::Handle,
color: Option<Color>,
bounds: Rectangle,
transformation: Transformation,
) {
let svg = Image::Vector {
handle,
color,
bounds: bounds * transformation,
};
self.images.push(svg);
}
pub fn draw_primitive_group(
&mut self,
primitives: Vec<Primitive>,
clip_bounds: Rectangle,
transformation: Transformation,
) {
self.primitives.push(Item::Group(
primitives,
clip_bounds,
transformation,
));
}
pub fn draw_primitive_cache(
&mut self,
primitives: Rc<[Primitive]>,
clip_bounds: Rectangle,
transformation: Transformation,
) {
self.primitives.push(Item::Cached(
primitives,
clip_bounds,
transformation,
));
}
pub fn damage(previous: &Self, current: &Self) -> Vec<Rectangle> {
if previous.bounds != current.bounds {
return vec![previous.bounds, current.bounds];
}
let mut damage = damage::list(
&previous.quads,
&current.quads,
|(quad, _)| {
quad.bounds
.expand(1.0)
.intersection(&current.bounds)
.into_iter()
.collect()
},
|(quad_a, background_a), (quad_b, background_b)| {
quad_a == quad_b && background_a == background_b
},
);
let text = damage::diff(
&previous.text,
&current.text,
|item| {
item.as_slice()
.iter()
.filter_map(Text::visible_bounds)
.map(|bounds| bounds * item.transformation())
.collect()
},
|text_a, text_b| {
damage::list(
text_a.as_slice(),
text_b.as_slice(),
|text| {
text.visible_bounds()
.into_iter()
.map(|bounds| bounds * text_a.transformation())
.collect()
},
|text_a, text_b| text_a == text_b,
)
},
);
let primitives = damage::list(
&previous.primitives,
&current.primitives,
|item| match item {
Item::Live(primitive) => vec![primitive.visible_bounds()],
Item::Group(primitives, group_bounds, transformation) => {
primitives
.as_slice()
.iter()
.map(Primitive::visible_bounds)
.map(|bounds| bounds * *transformation)
.filter_map(|bounds| bounds.intersection(group_bounds))
.collect()
}
Item::Cached(_, bounds, _) => {
vec![*bounds]
}
},
|primitive_a, primitive_b| match (primitive_a, primitive_b) {
(
Item::Cached(cache_a, bounds_a, transformation_a),
Item::Cached(cache_b, bounds_b, transformation_b),
) => {
Rc::ptr_eq(cache_a, cache_b)
&& bounds_a == bounds_b
&& transformation_a == transformation_b
}
_ => false,
},
);
let images = damage::list(
&previous.images,
&current.images,
|image| vec![image.bounds().expand(1.0)],
Image::eq,
);
damage.extend(text);
damage.extend(primitives);
damage.extend(images);
damage
}
}
impl Default for Layer {
fn default() -> Self {
Self {
bounds: Rectangle::INFINITE,
quads: Vec::new(),
primitives: Vec::new(),
text: Vec::new(),
images: Vec::new(),
}
}
}
impl graphics::Layer for Layer {
fn with_bounds(bounds: Rectangle) -> Self {
Self {
bounds,
..Self::default()
}
}
fn flush(&mut self) {}
fn resize(&mut self, bounds: graphics::core::Rectangle) {
self.bounds = bounds;
}
fn reset(&mut self) {
self.bounds = Rectangle::INFINITE;
self.quads.clear();
self.primitives.clear();
self.text.clear();
self.images.clear();
}
}
#[derive(Debug, Clone)]
pub enum Item<T> {
Live(T),
Group(Vec<T>, Rectangle, Transformation),
Cached(Rc<[T]>, Rectangle, Transformation),
}
impl<T> Item<T> {
pub fn transformation(&self) -> Transformation {
match self {
Item::Live(_) => Transformation::IDENTITY,
Item::Group(_, _, transformation)
| Item::Cached(_, _, transformation) => *transformation,
}
}
pub fn clip_bounds(&self) -> Rectangle {
match self {
Item::Live(_) => Rectangle::INFINITE,
Item::Group(_, clip_bounds, _)
| Item::Cached(_, clip_bounds, _) => *clip_bounds,
}
}
pub fn as_slice(&self) -> &[T] {
match self {
Item::Live(item) => std::slice::from_ref(item),
Item::Group(group, _, _) => group.as_slice(),
Item::Cached(cache, _, _) => cache,
}
}
}

View file

@ -1,9 +1,9 @@
#![forbid(rust_2018_idioms)]
#![deny(unsafe_code, unused_results, rustdoc::broken_intra_doc_links)]
#![allow(missing_docs)]
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
pub mod window;
mod backend;
mod engine;
mod layer;
mod primitive;
mod settings;
mod text;
@ -20,12 +20,389 @@ pub mod geometry;
pub use iced_graphics as graphics;
pub use iced_graphics::core;
pub use backend::Backend;
pub use layer::Layer;
pub use primitive::Primitive;
pub use settings::Settings;
#[cfg(feature = "geometry")]
pub use geometry::Geometry;
use crate::core::renderer;
use crate::core::{
Background, Color, Font, Pixels, Point, Rectangle, Transformation,
};
use crate::engine::Engine;
use crate::graphics::compositor;
use crate::graphics::text::{Editor, Paragraph};
use crate::graphics::Viewport;
/// A [`tiny-skia`] graphics renderer for [`iced`].
///
/// [`tiny-skia`]: https://github.com/RazrFalcon/tiny-skia
/// [`iced`]: https://github.com/iced-rs/iced
pub type Renderer = iced_graphics::Renderer<Backend>;
#[derive(Debug)]
pub struct Renderer {
default_font: Font,
default_text_size: Pixels,
layers: layer::Stack,
engine: Engine, // TODO: Shared engine
}
impl Renderer {
pub fn new(default_font: Font, default_text_size: Pixels) -> Self {
Self {
default_font,
default_text_size,
layers: layer::Stack::new(),
engine: Engine::new(),
}
}
pub fn layers(&mut self) -> &[Layer] {
self.layers.flush();
self.layers.as_slice()
}
pub fn draw<T: AsRef<str>>(
&mut self,
pixels: &mut tiny_skia::PixmapMut<'_>,
clip_mask: &mut tiny_skia::Mask,
viewport: &Viewport,
damage: &[Rectangle],
background_color: Color,
overlay: &[T],
) {
let physical_size = viewport.physical_size();
let scale_factor = viewport.scale_factor() as f32;
if !overlay.is_empty() {
let path = tiny_skia::PathBuilder::from_rect(
tiny_skia::Rect::from_xywh(
0.0,
0.0,
physical_size.width as f32,
physical_size.height as f32,
)
.expect("Create damage rectangle"),
);
pixels.fill_path(
&path,
&tiny_skia::Paint {
shader: tiny_skia::Shader::SolidColor(engine::into_color(
Color {
a: 0.1,
..background_color
},
)),
anti_alias: false,
..Default::default()
},
tiny_skia::FillRule::default(),
tiny_skia::Transform::identity(),
None,
);
}
self.layers.flush();
for &region in damage {
let region = region * scale_factor;
let path = tiny_skia::PathBuilder::from_rect(
tiny_skia::Rect::from_xywh(
region.x,
region.y,
region.width,
region.height,
)
.expect("Create damage rectangle"),
);
pixels.fill_path(
&path,
&tiny_skia::Paint {
shader: tiny_skia::Shader::SolidColor(engine::into_color(
background_color,
)),
anti_alias: false,
blend_mode: tiny_skia::BlendMode::Source,
..Default::default()
},
tiny_skia::FillRule::default(),
tiny_skia::Transform::identity(),
None,
);
for layer in self.layers.iter() {
let Some(clip_bounds) =
region.intersection(&(layer.bounds * scale_factor))
else {
continue;
};
engine::adjust_clip_mask(clip_mask, clip_bounds);
for (quad, background) in &layer.quads {
self.engine.draw_quad(
quad,
background,
Transformation::scale(scale_factor),
pixels,
clip_mask,
clip_bounds,
);
}
for group in &layer.primitives {
let Some(new_clip_bounds) = (group.clip_bounds()
* scale_factor)
.intersection(&clip_bounds)
else {
continue;
};
engine::adjust_clip_mask(clip_mask, new_clip_bounds);
for primitive in group.as_slice() {
self.engine.draw_primitive(
primitive,
group.transformation()
* Transformation::scale(scale_factor),
pixels,
clip_mask,
clip_bounds,
);
}
engine::adjust_clip_mask(clip_mask, clip_bounds);
}
for group in &layer.text {
for text in group.as_slice() {
self.engine.draw_text(
text,
group.transformation()
* Transformation::scale(scale_factor),
pixels,
clip_mask,
clip_bounds,
);
}
}
for image in &layer.images {
self.engine.draw_image(
image,
Transformation::scale(scale_factor),
pixels,
clip_mask,
clip_bounds,
);
}
}
if !overlay.is_empty() {
pixels.stroke_path(
&path,
&tiny_skia::Paint {
shader: tiny_skia::Shader::SolidColor(
engine::into_color(Color::from_rgb(1.0, 0.0, 0.0)),
),
anti_alias: false,
..tiny_skia::Paint::default()
},
&tiny_skia::Stroke {
width: 1.0,
..tiny_skia::Stroke::default()
},
tiny_skia::Transform::identity(),
None,
);
}
}
self.engine.trim();
}
}
impl core::Renderer for Renderer {
fn start_layer(&mut self, bounds: Rectangle) {
self.layers.push_clip(bounds);
}
fn end_layer(&mut self) {
self.layers.pop_clip();
}
fn start_transformation(&mut self, transformation: Transformation) {
self.layers.push_transformation(transformation);
}
fn end_transformation(&mut self) {
self.layers.pop_transformation();
}
fn fill_quad(
&mut self,
quad: renderer::Quad,
background: impl Into<Background>,
) {
let (layer, transformation) = self.layers.current_mut();
layer.draw_quad(quad, background.into(), transformation);
}
fn clear(&mut self) {
self.layers.clear();
}
}
impl core::text::Renderer for Renderer {
type Font = Font;
type Paragraph = Paragraph;
type Editor = Editor;
const ICON_FONT: Font = Font::with_name("Iced-Icons");
const CHECKMARK_ICON: char = '\u{f00c}';
const ARROW_DOWN_ICON: char = '\u{e800}';
fn default_font(&self) -> Self::Font {
self.default_font
}
fn default_size(&self) -> Pixels {
self.default_text_size
}
fn fill_paragraph(
&mut self,
text: &Self::Paragraph,
position: Point,
color: Color,
clip_bounds: Rectangle,
) {
let (layer, transformation) = self.layers.current_mut();
layer.draw_paragraph(
text,
position,
color,
clip_bounds,
transformation,
);
}
fn fill_editor(
&mut self,
editor: &Self::Editor,
position: Point,
color: Color,
clip_bounds: Rectangle,
) {
let (layer, transformation) = self.layers.current_mut();
layer.draw_editor(editor, position, color, clip_bounds, transformation);
}
fn fill_text(
&mut self,
text: core::Text,
position: Point,
color: Color,
clip_bounds: Rectangle,
) {
let (layer, transformation) = self.layers.current_mut();
layer.draw_text(text, position, color, clip_bounds, transformation);
}
}
#[cfg(feature = "geometry")]
impl graphics::geometry::Renderer for Renderer {
type Geometry = Geometry;
type Frame = geometry::Frame;
fn new_frame(&self, size: core::Size) -> Self::Frame {
geometry::Frame::new(size)
}
fn draw_geometry(&mut self, geometry: Self::Geometry) {
let (layer, transformation) = self.layers.current_mut();
match geometry {
Geometry::Live {
primitives,
text,
clip_bounds,
} => {
layer.draw_primitive_group(
primitives,
clip_bounds,
transformation,
);
layer.draw_text_group(text, clip_bounds, transformation);
}
Geometry::Cache(cache) => {
layer.draw_primitive_cache(
cache.primitives,
cache.clip_bounds,
transformation,
);
layer.draw_text_cache(
cache.text,
cache.clip_bounds,
transformation,
);
}
}
}
}
impl graphics::mesh::Renderer for Renderer {
fn draw_mesh(&mut self, _mesh: graphics::Mesh) {
log::warn!("iced_tiny_skia does not support drawing meshes");
}
}
#[cfg(feature = "image")]
impl core::image::Renderer for Renderer {
type Handle = core::image::Handle;
fn measure_image(&self, handle: &Self::Handle) -> crate::core::Size<u32> {
self.engine.raster_pipeline.dimensions(handle)
}
fn draw_image(
&mut self,
handle: Self::Handle,
filter_method: core::image::FilterMethod,
bounds: Rectangle,
) {
let (layer, transformation) = self.layers.current_mut();
layer.draw_image(handle, filter_method, bounds, transformation);
}
}
#[cfg(feature = "svg")]
impl core::svg::Renderer for Renderer {
fn measure_svg(
&self,
handle: &core::svg::Handle,
) -> crate::core::Size<u32> {
self.engine.vector_pipeline.viewport_dimensions(handle)
}
fn draw_svg(
&mut self,
handle: core::svg::Handle,
color: Option<Color>,
bounds: Rectangle,
) {
let (layer, transformation) = self.layers.current_mut();
layer.draw_svg(handle, color, bounds, transformation);
}
}
impl compositor::Default for Renderer {
type Compositor = window::Compositor;
}

View file

@ -1,10 +1,7 @@
use crate::core::Rectangle;
use crate::graphics::{Damage, Mesh};
pub type Primitive = crate::graphics::Primitive<Custom>;
#[derive(Debug, Clone, PartialEq)]
pub enum Custom {
pub enum Primitive {
/// A path filled with some paint.
Fill {
/// The path to fill.
@ -25,28 +22,19 @@ pub enum Custom {
},
}
impl Damage for Custom {
fn bounds(&self) -> Rectangle {
match self {
Self::Fill { path, .. } | Self::Stroke { path, .. } => {
let bounds = path.bounds();
impl Primitive {
/// Returns the visible bounds of the [`Primitive`].
pub fn visible_bounds(&self) -> Rectangle {
let bounds = match self {
Primitive::Fill { path, .. } => path.bounds(),
Primitive::Stroke { path, .. } => path.bounds(),
};
Rectangle {
x: bounds.x(),
y: bounds.y(),
width: bounds.width(),
height: bounds.height(),
}
.expand(1.0)
}
Rectangle {
x: bounds.x(),
y: bounds.y(),
width: bounds.width(),
height: bounds.height(),
}
}
}
impl TryFrom<Mesh> for Custom {
type Error = &'static str;
fn try_from(_mesh: Mesh) -> Result<Self, Self::Error> {
Err("unsupported")
}
}

View file

@ -6,6 +6,7 @@ use rustc_hash::{FxHashMap, FxHashSet};
use std::cell::RefCell;
use std::collections::hash_map;
#[derive(Debug)]
pub struct Pipeline {
cache: RefCell<Cache>,
}
@ -68,7 +69,7 @@ impl Pipeline {
}
}
#[derive(Default)]
#[derive(Debug, Default)]
struct Cache {
entries: FxHashMap<u64, Option<Entry>>,
hits: FxHashSet<u64>,
@ -119,6 +120,7 @@ impl Cache {
}
}
#[derive(Debug)]
struct Entry {
width: u32,
height: u32,

View file

@ -1,9 +1,9 @@
use crate::core::{Font, Pixels};
use crate::graphics;
/// The settings of a [`Backend`].
/// The settings of a [`Compositor`].
///
/// [`Backend`]: crate::Backend
/// [`Compositor`]: crate::window::Compositor
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Settings {
/// The default [`Font`] to use.

View file

@ -1,5 +1,5 @@
use crate::core::alignment;
use crate::core::text::{LineHeight, Shaping};
use crate::core::text::Shaping;
use crate::core::{
Color, Font, Pixels, Point, Rectangle, Size, Transformation,
};
@ -13,7 +13,7 @@ use std::borrow::Cow;
use std::cell::RefCell;
use std::collections::hash_map;
#[allow(missing_debug_implementations)]
#[derive(Debug)]
pub struct Pipeline {
glyph_cache: GlyphCache,
cache: RefCell<Cache>,
@ -27,6 +27,8 @@ impl Pipeline {
}
}
// TODO: Shared engine
#[allow(dead_code)]
pub fn load_font(&mut self, bytes: Cow<'static, [u8]>) {
font_system()
.write()
@ -41,7 +43,6 @@ impl Pipeline {
paragraph: &paragraph::Weak,
position: Point,
color: Color,
scale_factor: f32,
pixels: &mut tiny_skia::PixmapMut<'_>,
clip_mask: Option<&tiny_skia::Mask>,
transformation: Transformation,
@ -62,7 +63,6 @@ impl Pipeline {
color,
paragraph.horizontal_alignment(),
paragraph.vertical_alignment(),
scale_factor,
pixels,
clip_mask,
transformation,
@ -74,7 +74,6 @@ impl Pipeline {
editor: &editor::Weak,
position: Point,
color: Color,
scale_factor: f32,
pixels: &mut tiny_skia::PixmapMut<'_>,
clip_mask: Option<&tiny_skia::Mask>,
transformation: Transformation,
@ -95,7 +94,6 @@ impl Pipeline {
color,
alignment::Horizontal::Left,
alignment::Vertical::Top,
scale_factor,
pixels,
clip_mask,
transformation,
@ -108,17 +106,16 @@ impl Pipeline {
bounds: Rectangle,
color: Color,
size: Pixels,
line_height: LineHeight,
line_height: Pixels,
font: Font,
horizontal_alignment: alignment::Horizontal,
vertical_alignment: alignment::Vertical,
shaping: Shaping,
scale_factor: f32,
pixels: &mut tiny_skia::PixmapMut<'_>,
clip_mask: Option<&tiny_skia::Mask>,
transformation: Transformation,
) {
let line_height = f32::from(line_height.to_absolute(size));
let line_height = f32::from(line_height);
let mut font_system = font_system().write().expect("Write font system");
let font_system = font_system.raw();
@ -149,7 +146,6 @@ impl Pipeline {
color,
horizontal_alignment,
vertical_alignment,
scale_factor,
pixels,
clip_mask,
transformation,
@ -161,7 +157,6 @@ impl Pipeline {
buffer: &cosmic_text::Buffer,
position: Point,
color: Color,
scale_factor: f32,
pixels: &mut tiny_skia::PixmapMut<'_>,
clip_mask: Option<&tiny_skia::Mask>,
transformation: Transformation,
@ -178,7 +173,6 @@ impl Pipeline {
color,
alignment::Horizontal::Left,
alignment::Vertical::Top,
scale_factor,
pixels,
clip_mask,
transformation,
@ -199,12 +193,11 @@ fn draw(
color: Color,
horizontal_alignment: alignment::Horizontal,
vertical_alignment: alignment::Vertical,
scale_factor: f32,
pixels: &mut tiny_skia::PixmapMut<'_>,
clip_mask: Option<&tiny_skia::Mask>,
transformation: Transformation,
) {
let bounds = bounds * transformation * scale_factor;
let bounds = bounds * transformation;
let x = match horizontal_alignment {
alignment::Horizontal::Left => bounds.x,
@ -222,8 +215,8 @@ fn draw(
for run in buffer.layout_runs() {
for glyph in run.glyphs {
let physical_glyph = glyph
.physical((x, y), scale_factor * transformation.scale_factor());
let physical_glyph =
glyph.physical((x, y), transformation.scale_factor());
if let Some((buffer, placement)) = glyph_cache.allocate(
physical_glyph.cache_key,
@ -247,10 +240,8 @@ fn draw(
pixels.draw_pixmap(
physical_glyph.x + placement.left,
physical_glyph.y - placement.top
+ (run.line_y
* scale_factor
* transformation.scale_factor())
.round() as i32,
+ (run.line_y * transformation.scale_factor()).round()
as i32,
pixmap,
&tiny_skia::PixmapPaint {
opacity,

View file

@ -9,6 +9,7 @@ use std::cell::RefCell;
use std::collections::hash_map;
use std::fs;
#[derive(Debug)]
pub struct Pipeline {
cache: RefCell<Cache>,
}
@ -203,3 +204,13 @@ impl Cache {
self.raster_hits.clear();
}
}
impl std::fmt::Debug for Cache {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Cache")
.field("tree_hits", &self.tree_hits)
.field("rasters", &self.rasters)
.field("raster_hits", &self.raster_hits)
.finish_non_exhaustive()
}
}

View file

@ -3,23 +3,25 @@ use crate::graphics::compositor::{self, Information};
use crate::graphics::damage;
use crate::graphics::error::{self, Error};
use crate::graphics::{self, Viewport};
use crate::{Backend, Primitive, Renderer, Settings};
use crate::{Layer, Renderer, Settings};
use std::collections::VecDeque;
use std::num::NonZeroU32;
#[allow(missing_debug_implementations)]
pub struct Compositor {
context: softbuffer::Context<Box<dyn compositor::Window>>,
settings: Settings,
}
#[allow(missing_debug_implementations)]
pub struct Surface {
window: softbuffer::Surface<
Box<dyn compositor::Window>,
Box<dyn compositor::Window>,
>,
clip_mask: tiny_skia::Mask,
primitive_stack: VecDeque<Vec<Primitive>>,
layer_stack: VecDeque<Vec<Layer>>,
background_color: Color,
max_age: u8,
}
@ -48,7 +50,6 @@ impl crate::graphics::Compositor for Compositor {
fn create_renderer(&self) -> Self::Renderer {
Renderer::new(
Backend::new(),
self.settings.default_font,
self.settings.default_text_size,
)
@ -70,7 +71,7 @@ impl crate::graphics::Compositor for Compositor {
window,
clip_mask: tiny_skia::Mask::new(width, height)
.expect("Create clip mask"),
primitive_stack: VecDeque::new(),
layer_stack: VecDeque::new(),
background_color: Color::BLACK,
max_age: 0,
};
@ -96,7 +97,7 @@ impl crate::graphics::Compositor for Compositor {
surface.clip_mask =
tiny_skia::Mask::new(width, height).expect("Create clip mask");
surface.primitive_stack.clear();
surface.layer_stack.clear();
}
fn fetch_information(&self) -> Information {
@ -114,16 +115,7 @@ impl crate::graphics::Compositor for Compositor {
background_color: Color,
overlay: &[T],
) -> Result<(), compositor::SurfaceError> {
renderer.with_primitives(|backend, primitives| {
present(
backend,
surface,
primitives,
viewport,
background_color,
overlay,
)
})
present(renderer, surface, viewport, background_color, overlay)
}
fn screenshot<T: AsRef<str>>(
@ -134,16 +126,7 @@ impl crate::graphics::Compositor for Compositor {
background_color: Color,
overlay: &[T],
) -> Vec<u8> {
renderer.with_primitives(|backend, primitives| {
screenshot(
surface,
backend,
primitives,
viewport,
background_color,
overlay,
)
})
screenshot(renderer, surface, viewport, background_color, overlay)
}
}
@ -159,38 +142,42 @@ pub fn new<W: compositor::Window>(
}
pub fn present<T: AsRef<str>>(
backend: &mut Backend,
renderer: &mut Renderer,
surface: &mut Surface,
primitives: &[Primitive],
viewport: &Viewport,
background_color: Color,
overlay: &[T],
) -> Result<(), compositor::SurfaceError> {
let physical_size = viewport.physical_size();
let scale_factor = viewport.scale_factor() as f32;
let mut buffer = surface
.window
.buffer_mut()
.map_err(|_| compositor::SurfaceError::Lost)?;
let last_primitives = {
let last_layers = {
let age = buffer.age();
surface.max_age = surface.max_age.max(age);
surface.primitive_stack.truncate(surface.max_age as usize);
surface.layer_stack.truncate(surface.max_age as usize);
if age > 0 {
surface.primitive_stack.get(age as usize - 1)
surface.layer_stack.get(age as usize - 1)
} else {
None
}
};
let damage = last_primitives
.and_then(|last_primitives| {
(surface.background_color == background_color)
.then(|| damage::list(last_primitives, primitives))
let damage = last_layers
.and_then(|last_layers| {
(surface.background_color == background_color).then(|| {
damage::diff(
last_layers,
renderer.layers(),
|layer| vec![layer.bounds],
Layer::damage,
)
})
})
.unwrap_or_else(|| vec![Rectangle::with_size(viewport.logical_size())]);
@ -198,10 +185,11 @@ pub fn present<T: AsRef<str>>(
return Ok(());
}
surface.primitive_stack.push_front(primitives.to_vec());
surface.layer_stack.push_front(renderer.layers().to_vec());
surface.background_color = background_color;
let damage = damage::group(damage, scale_factor, physical_size);
let damage =
damage::group(damage, Rectangle::with_size(viewport.logical_size()));
let mut pixels = tiny_skia::PixmapMut::from_bytes(
bytemuck::cast_slice_mut(&mut buffer),
@ -210,10 +198,9 @@ pub fn present<T: AsRef<str>>(
)
.expect("Create pixel map");
backend.draw(
renderer.draw(
&mut pixels,
&mut surface.clip_mask,
primitives,
viewport,
&damage,
background_color,
@ -224,9 +211,8 @@ pub fn present<T: AsRef<str>>(
}
pub fn screenshot<T: AsRef<str>>(
renderer: &mut Renderer,
surface: &mut Surface,
backend: &mut Backend,
primitives: &[Primitive],
viewport: &Viewport,
background_color: Color,
overlay: &[T],
@ -236,7 +222,7 @@ pub fn screenshot<T: AsRef<str>>(
let mut offscreen_buffer: Vec<u32> =
vec![0; size.width as usize * size.height as usize];
backend.draw(
renderer.draw(
&mut tiny_skia::PixmapMut::from_bytes(
bytemuck::cast_slice_mut(&mut offscreen_buffer),
size.width,
@ -244,7 +230,6 @@ pub fn screenshot<T: AsRef<str>>(
)
.expect("Create offscreen pixel map"),
&mut surface.clip_mask,
primitives,
viewport,
&[Rectangle::with_size(Size::new(
size.width as f32,

View file

@ -10,6 +10,9 @@ homepage.workspace = true
categories.workspace = true
keywords.workspace = true
[lints]
workspace = true
[package.metadata.docs.rs]
rustdoc-args = ["--cfg", "docsrs"]
all-features = true
@ -32,6 +35,7 @@ glyphon.workspace = true
guillotiere.workspace = true
log.workspace = true
once_cell.workspace = true
rustc-hash.workspace = true
thiserror.workspace = true
wgpu.workspace = true
@ -40,6 +44,3 @@ lyon.optional = true
resvg.workspace = true
resvg.optional = true
tracing.workspace = true
tracing.optional = true

View file

@ -1,410 +0,0 @@
use crate::core::{Color, Size, Transformation};
use crate::graphics::backend;
use crate::graphics::color;
use crate::graphics::Viewport;
use crate::primitive::pipeline;
use crate::primitive::{self, Primitive};
use crate::quad;
use crate::text;
use crate::triangle;
use crate::window;
use crate::{Layer, Settings};
#[cfg(feature = "tracing")]
use tracing::info_span;
#[cfg(any(feature = "image", feature = "svg"))]
use crate::image;
use std::borrow::Cow;
/// A [`wgpu`] graphics backend for [`iced`].
///
/// [`wgpu`]: https://github.com/gfx-rs/wgpu-rs
/// [`iced`]: https://github.com/iced-rs/iced
#[allow(missing_debug_implementations)]
pub struct Backend {
quad_pipeline: quad::Pipeline,
text_pipeline: text::Pipeline,
triangle_pipeline: triangle::Pipeline,
pipeline_storage: pipeline::Storage,
#[cfg(any(feature = "image", feature = "svg"))]
image_pipeline: image::Pipeline,
}
impl Backend {
/// Creates a new [`Backend`].
pub fn new(
_adapter: &wgpu::Adapter,
device: &wgpu::Device,
queue: &wgpu::Queue,
settings: Settings,
format: wgpu::TextureFormat,
) -> Self {
let text_pipeline = text::Pipeline::new(device, queue, format);
let quad_pipeline = quad::Pipeline::new(device, format);
let triangle_pipeline =
triangle::Pipeline::new(device, format, settings.antialiasing);
#[cfg(any(feature = "image", feature = "svg"))]
let image_pipeline = {
let backend = _adapter.get_info().backend;
image::Pipeline::new(device, format, backend)
};
Self {
quad_pipeline,
text_pipeline,
triangle_pipeline,
pipeline_storage: pipeline::Storage::default(),
#[cfg(any(feature = "image", feature = "svg"))]
image_pipeline,
}
}
/// Draws the provided primitives in the given `TextureView`.
///
/// The text provided as overlay will be rendered on top of the primitives.
/// This is useful for rendering debug information.
pub fn present<T: AsRef<str>>(
&mut self,
device: &wgpu::Device,
queue: &wgpu::Queue,
encoder: &mut wgpu::CommandEncoder,
clear_color: Option<Color>,
format: wgpu::TextureFormat,
frame: &wgpu::TextureView,
primitives: &[Primitive],
viewport: &Viewport,
overlay_text: &[T],
) {
log::debug!("Drawing");
#[cfg(feature = "tracing")]
let _ = info_span!("Wgpu::Backend", "PRESENT").entered();
let target_size = viewport.physical_size();
let scale_factor = viewport.scale_factor() as f32;
let transformation = viewport.projection();
let mut layers = Layer::generate(primitives, viewport);
if !overlay_text.is_empty() {
layers.push(Layer::overlay(overlay_text, viewport));
}
self.prepare(
device,
queue,
format,
encoder,
scale_factor,
target_size,
transformation,
&layers,
);
self.render(
device,
encoder,
frame,
clear_color,
scale_factor,
target_size,
&layers,
);
self.quad_pipeline.end_frame();
self.text_pipeline.end_frame();
self.triangle_pipeline.end_frame();
#[cfg(any(feature = "image", feature = "svg"))]
self.image_pipeline.end_frame();
}
fn prepare(
&mut self,
device: &wgpu::Device,
queue: &wgpu::Queue,
format: wgpu::TextureFormat,
_encoder: &mut wgpu::CommandEncoder,
scale_factor: f32,
target_size: Size<u32>,
transformation: Transformation,
layers: &[Layer<'_>],
) {
for layer in layers {
let bounds = (layer.bounds * scale_factor).snap();
if bounds.width < 1 || bounds.height < 1 {
continue;
}
if !layer.quads.is_empty() {
self.quad_pipeline.prepare(
device,
queue,
&layer.quads,
transformation,
scale_factor,
);
}
if !layer.meshes.is_empty() {
let scaled =
transformation * Transformation::scale(scale_factor);
self.triangle_pipeline.prepare(
device,
queue,
&layer.meshes,
scaled,
);
}
#[cfg(any(feature = "image", feature = "svg"))]
{
if !layer.images.is_empty() {
let scaled =
transformation * Transformation::scale(scale_factor);
self.image_pipeline.prepare(
device,
queue,
_encoder,
&layer.images,
scaled,
scale_factor,
);
}
}
if !layer.text.is_empty() {
self.text_pipeline.prepare(
device,
queue,
&layer.text,
layer.bounds,
scale_factor,
target_size,
);
}
if !layer.pipelines.is_empty() {
for pipeline in &layer.pipelines {
pipeline.primitive.prepare(
format,
device,
queue,
pipeline.bounds,
target_size,
scale_factor,
&mut self.pipeline_storage,
);
}
}
}
}
fn render(
&mut self,
device: &wgpu::Device,
encoder: &mut wgpu::CommandEncoder,
target: &wgpu::TextureView,
clear_color: Option<Color>,
scale_factor: f32,
target_size: Size<u32>,
layers: &[Layer<'_>],
) {
use std::mem::ManuallyDrop;
let mut quad_layer = 0;
let mut triangle_layer = 0;
#[cfg(any(feature = "image", feature = "svg"))]
let mut image_layer = 0;
let mut text_layer = 0;
let mut render_pass = ManuallyDrop::new(encoder.begin_render_pass(
&wgpu::RenderPassDescriptor {
label: Some("iced_wgpu render pass"),
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
view: target,
resolve_target: None,
ops: wgpu::Operations {
load: match clear_color {
Some(background_color) => wgpu::LoadOp::Clear({
let [r, g, b, a] =
color::pack(background_color).components();
wgpu::Color {
r: f64::from(r),
g: f64::from(g),
b: f64::from(b),
a: f64::from(a),
}
}),
None => wgpu::LoadOp::Load,
},
store: wgpu::StoreOp::Store,
},
})],
depth_stencil_attachment: None,
timestamp_writes: None,
occlusion_query_set: None,
},
));
for layer in layers {
let bounds = (layer.bounds * scale_factor).snap();
if bounds.width < 1 || bounds.height < 1 {
continue;
}
if !layer.quads.is_empty() {
self.quad_pipeline.render(
quad_layer,
bounds,
&layer.quads,
&mut render_pass,
);
quad_layer += 1;
}
if !layer.meshes.is_empty() {
let _ = ManuallyDrop::into_inner(render_pass);
self.triangle_pipeline.render(
device,
encoder,
target,
triangle_layer,
target_size,
&layer.meshes,
scale_factor,
);
triangle_layer += 1;
render_pass = ManuallyDrop::new(encoder.begin_render_pass(
&wgpu::RenderPassDescriptor {
label: Some("iced_wgpu render pass"),
color_attachments: &[Some(
wgpu::RenderPassColorAttachment {
view: target,
resolve_target: None,
ops: wgpu::Operations {
load: wgpu::LoadOp::Load,
store: wgpu::StoreOp::Store,
},
},
)],
depth_stencil_attachment: None,
timestamp_writes: None,
occlusion_query_set: None,
},
));
}
#[cfg(any(feature = "image", feature = "svg"))]
{
if !layer.images.is_empty() {
self.image_pipeline.render(
image_layer,
bounds,
&mut render_pass,
);
image_layer += 1;
}
}
if !layer.text.is_empty() {
self.text_pipeline
.render(text_layer, bounds, &mut render_pass);
text_layer += 1;
}
if !layer.pipelines.is_empty() {
let _ = ManuallyDrop::into_inner(render_pass);
for pipeline in &layer.pipelines {
let viewport = (pipeline.viewport * scale_factor).snap();
if viewport.width < 1 || viewport.height < 1 {
continue;
}
pipeline.primitive.render(
&self.pipeline_storage,
target,
target_size,
viewport,
encoder,
);
}
render_pass = ManuallyDrop::new(encoder.begin_render_pass(
&wgpu::RenderPassDescriptor {
label: Some("iced_wgpu render pass"),
color_attachments: &[Some(
wgpu::RenderPassColorAttachment {
view: target,
resolve_target: None,
ops: wgpu::Operations {
load: wgpu::LoadOp::Load,
store: wgpu::StoreOp::Store,
},
},
)],
depth_stencil_attachment: None,
timestamp_writes: None,
occlusion_query_set: None,
},
));
}
}
let _ = ManuallyDrop::into_inner(render_pass);
}
}
impl backend::Backend for Backend {
type Primitive = primitive::Custom;
type Compositor = window::Compositor;
}
impl backend::Text for Backend {
fn load_font(&mut self, font: Cow<'static, [u8]>) {
self.text_pipeline.load_font(font);
}
}
#[cfg(feature = "image")]
impl backend::Image for Backend {
fn dimensions(&self, handle: &crate::core::image::Handle) -> Size<u32> {
self.image_pipeline.dimensions(handle)
}
}
#[cfg(feature = "svg")]
impl backend::Svg for Backend {
fn viewport_dimensions(
&self,
handle: &crate::core::svg::Handle,
) -> Size<u32> {
self.image_pipeline.viewport_dimensions(handle)
}
}
#[cfg(feature = "geometry")]
impl crate::graphics::geometry::Backend for Backend {
type Frame = crate::geometry::Frame;
fn new_frame(&self, size: Size) -> Self::Frame {
crate::geometry::Frame::new(size)
}
}

View file

@ -1,6 +1,13 @@
use std::marker::PhantomData;
use std::num::NonZeroU64;
use std::ops::RangeBounds;
pub const MAX_WRITE_SIZE: usize = 100 * 1024;
#[allow(unsafe_code)]
const MAX_WRITE_SIZE_U64: NonZeroU64 =
unsafe { NonZeroU64::new_unchecked(MAX_WRITE_SIZE as u64) };
#[derive(Debug)]
pub struct Buffer<T> {
label: &'static str,
@ -61,12 +68,46 @@ impl<T: bytemuck::Pod> Buffer<T> {
/// Returns the size of the written bytes.
pub fn write(
&mut self,
queue: &wgpu::Queue,
device: &wgpu::Device,
encoder: &mut wgpu::CommandEncoder,
belt: &mut wgpu::util::StagingBelt,
offset: usize,
contents: &[T],
) -> usize {
let bytes: &[u8] = bytemuck::cast_slice(contents);
queue.write_buffer(&self.raw, offset as u64, bytes);
let mut bytes_written = 0;
// Split write into multiple chunks if necessary
while bytes_written + MAX_WRITE_SIZE < bytes.len() {
belt.write_buffer(
encoder,
&self.raw,
(offset + bytes_written) as u64,
MAX_WRITE_SIZE_U64,
device,
)
.copy_from_slice(
&bytes[bytes_written..bytes_written + MAX_WRITE_SIZE],
);
bytes_written += MAX_WRITE_SIZE;
}
// There will always be some bytes left, since the previous
// loop guarantees `bytes_written < bytes.len()`
let bytes_left = ((bytes.len() - bytes_written) as u64)
.try_into()
.expect("non-empty write");
// Write them
belt.write_buffer(
encoder,
&self.raw,
(offset + bytes_written) as u64,
bytes_left,
device,
)
.copy_from_slice(&bytes[bytes_written..]);
self.offsets.push(offset as u64);

84
wgpu/src/engine.rs Normal file
View file

@ -0,0 +1,84 @@
use crate::buffer;
use crate::graphics::Antialiasing;
use crate::primitive;
use crate::quad;
use crate::text;
use crate::triangle;
#[allow(missing_debug_implementations)]
pub struct Engine {
pub(crate) staging_belt: wgpu::util::StagingBelt,
pub(crate) format: wgpu::TextureFormat,
pub(crate) quad_pipeline: quad::Pipeline,
pub(crate) text_pipeline: text::Pipeline,
pub(crate) triangle_pipeline: triangle::Pipeline,
#[cfg(any(feature = "image", feature = "svg"))]
pub(crate) image_pipeline: crate::image::Pipeline,
pub(crate) primitive_storage: primitive::Storage,
}
impl Engine {
pub fn new(
_adapter: &wgpu::Adapter,
device: &wgpu::Device,
queue: &wgpu::Queue,
format: wgpu::TextureFormat,
antialiasing: Option<Antialiasing>, // TODO: Initialize AA pipelines lazily
) -> Self {
let text_pipeline = text::Pipeline::new(device, queue, format);
let quad_pipeline = quad::Pipeline::new(device, format);
let triangle_pipeline =
triangle::Pipeline::new(device, format, antialiasing);
#[cfg(any(feature = "image", feature = "svg"))]
let image_pipeline = {
let backend = _adapter.get_info().backend;
crate::image::Pipeline::new(device, format, backend)
};
Self {
// TODO: Resize belt smartly (?)
// It would be great if the `StagingBelt` API exposed methods
// for introspection to detect when a resize may be worth it.
staging_belt: wgpu::util::StagingBelt::new(
buffer::MAX_WRITE_SIZE as u64,
),
format,
quad_pipeline,
text_pipeline,
triangle_pipeline,
#[cfg(any(feature = "image", feature = "svg"))]
image_pipeline,
primitive_storage: primitive::Storage::default(),
}
}
#[cfg(any(feature = "image", feature = "svg"))]
pub fn image_cache(&self) -> &crate::image::cache::Shared {
self.image_pipeline.cache()
}
pub fn submit(
&mut self,
queue: &wgpu::Queue,
encoder: wgpu::CommandEncoder,
) -> wgpu::SubmissionIndex {
self.staging_belt.finish();
let index = queue.submit(Some(encoder.finish()));
self.staging_belt.recall();
self.quad_pipeline.end_frame();
self.text_pipeline.end_frame();
self.triangle_pipeline.end_frame();
#[cfg(any(feature = "image", feature = "svg"))]
self.image_pipeline.end_frame();
index
}
}

View file

@ -6,23 +6,74 @@ use crate::core::{
use crate::graphics::color;
use crate::graphics::geometry::fill::{self, Fill};
use crate::graphics::geometry::{
self, LineCap, LineDash, LineJoin, Path, Stroke, Style, Text,
self, LineCap, LineDash, LineJoin, Path, Stroke, Style,
};
use crate::graphics::gradient::{self, Gradient};
use crate::graphics::mesh::{self, Mesh};
use crate::primitive::{self, Primitive};
use crate::graphics::{self, Cached, Text};
use crate::text;
use crate::triangle;
use lyon::geom::euclid;
use lyon::tessellation;
use std::borrow::Cow;
#[derive(Debug)]
pub enum Geometry {
Live { meshes: Vec<Mesh>, text: Vec<Text> },
Cached(Cache),
}
#[derive(Debug, Clone)]
pub struct Cache {
pub meshes: Option<triangle::Cache>,
pub text: Option<text::Cache>,
}
impl Cached for Geometry {
type Cache = Cache;
fn load(cache: &Self::Cache) -> Self {
Geometry::Cached(cache.clone())
}
fn cache(self, previous: Option<Self::Cache>) -> Self::Cache {
match self {
Self::Live { meshes, text } => {
if let Some(mut previous) = previous {
if let Some(cache) = &mut previous.meshes {
cache.update(meshes);
} else {
previous.meshes = triangle::Cache::new(meshes);
}
if let Some(cache) = &mut previous.text {
cache.update(text);
} else {
previous.text = text::Cache::new(text);
}
previous
} else {
Cache {
meshes: triangle::Cache::new(meshes),
text: text::Cache::new(text),
}
}
}
Self::Cached(cache) => cache,
}
}
}
/// A frame for drawing some geometry.
#[allow(missing_debug_implementations)]
pub struct Frame {
size: Size,
clip_bounds: Rectangle,
buffers: BufferStack,
primitives: Vec<Primitive>,
meshes: Vec<Mesh>,
text: Vec<Text>,
transforms: Transforms,
fill_tessellator: tessellation::FillTessellator,
stroke_tessellator: tessellation::StrokeTessellator,
@ -31,81 +82,49 @@ pub struct Frame {
impl Frame {
/// Creates a new [`Frame`] with the given [`Size`].
pub fn new(size: Size) -> Frame {
Self::with_clip(Rectangle::with_size(size))
}
/// Creates a new [`Frame`] with the given clip bounds.
pub fn with_clip(bounds: Rectangle) -> Frame {
Frame {
size,
clip_bounds: bounds,
buffers: BufferStack::new(),
primitives: Vec::new(),
meshes: Vec::new(),
text: Vec::new(),
transforms: Transforms {
previous: Vec::new(),
current: Transform(lyon::math::Transform::identity()),
current: Transform(lyon::math::Transform::translation(
bounds.x, bounds.y,
)),
},
fill_tessellator: tessellation::FillTessellator::new(),
stroke_tessellator: tessellation::StrokeTessellator::new(),
}
}
fn into_primitives(mut self) -> Vec<Primitive> {
for buffer in self.buffers.stack {
match buffer {
Buffer::Solid(buffer) => {
if !buffer.indices.is_empty() {
self.primitives.push(Primitive::Custom(
primitive::Custom::Mesh(Mesh::Solid {
buffers: mesh::Indexed {
vertices: buffer.vertices,
indices: buffer.indices,
},
size: self.size,
}),
));
}
}
Buffer::Gradient(buffer) => {
if !buffer.indices.is_empty() {
self.primitives.push(Primitive::Custom(
primitive::Custom::Mesh(Mesh::Gradient {
buffers: mesh::Indexed {
vertices: buffer.vertices,
indices: buffer.indices,
},
size: self.size,
}),
));
}
}
}
}
self.primitives
}
}
impl geometry::frame::Backend for Frame {
type Geometry = Primitive;
/// Creates a new empty [`Frame`] with the given dimensions.
///
/// The default coordinate system of a [`Frame`] has its origin at the
/// top-left corner of its bounds.
type Geometry = Geometry;
#[inline]
fn width(&self) -> f32 {
self.size.width
self.clip_bounds.width
}
#[inline]
fn height(&self) -> f32 {
self.size.height
self.clip_bounds.height
}
#[inline]
fn size(&self) -> Size {
self.size
self.clip_bounds.size()
}
#[inline]
fn center(&self) -> Point {
Point::new(self.size.width / 2.0, self.size.height / 2.0)
Point::new(self.clip_bounds.width / 2.0, self.clip_bounds.height / 2.0)
}
fn fill(&mut self, path: &Path, fill: impl Into<Fill>) {
@ -208,7 +227,7 @@ impl geometry::frame::Backend for Frame {
.expect("Stroke path");
}
fn fill_text(&mut self, text: impl Into<Text>) {
fn fill_text(&mut self, text: impl Into<geometry::Text>) {
let text = text.into();
let (scale_x, scale_y) = self.transforms.current.scale();
@ -246,18 +265,17 @@ impl geometry::frame::Backend for Frame {
height: f32::INFINITY,
};
// TODO: Honor layering!
self.primitives.push(Primitive::Text {
self.text.push(graphics::Text::Cached {
content: text.content,
bounds,
color: text.color,
size,
line_height,
line_height: line_height.to_absolute(size),
font: text.font,
horizontal_alignment: text.horizontal_alignment,
vertical_alignment: text.vertical_alignment,
shaping: text.shaping,
clip_bounds: Rectangle::with_size(Size::INFINITY),
clip_bounds: self.clip_bounds,
});
} else {
text.draw_with(|path, color| self.fill(&path, color));
@ -308,41 +326,24 @@ impl geometry::frame::Backend for Frame {
self.transforms.current = self.transforms.previous.pop().unwrap();
}
fn draft(&mut self, size: Size) -> Frame {
Frame::new(size)
fn draft(&mut self, clip_bounds: Rectangle) -> Frame {
Frame::with_clip(clip_bounds)
}
fn paste(&mut self, frame: Frame, at: Point) {
let size = frame.size();
let primitives = frame.into_primitives();
let transformation = Transformation::translate(at.x, at.y);
fn paste(&mut self, frame: Frame, _at: Point) {
self.meshes
.extend(frame.buffers.into_meshes(frame.clip_bounds));
let (text, meshes) = primitives
.into_iter()
.partition(|primitive| matches!(primitive, Primitive::Text { .. }));
self.primitives.push(Primitive::Group {
primitives: vec![
Primitive::Transform {
transformation,
content: Box::new(Primitive::Group { primitives: meshes }),
},
Primitive::Transform {
transformation,
content: Box::new(Primitive::Clip {
bounds: Rectangle::with_size(size),
content: Box::new(Primitive::Group {
primitives: text,
}),
}),
},
],
});
self.text.extend(frame.text);
}
fn into_geometry(self) -> Self::Geometry {
Primitive::Group {
primitives: self.into_primitives(),
fn into_geometry(mut self) -> Self::Geometry {
self.meshes
.extend(self.buffers.into_meshes(self.clip_bounds));
Geometry::Live {
meshes: self.meshes,
text: self.text,
}
}
}
@ -429,6 +430,34 @@ impl BufferStack {
_ => unreachable!(),
}
}
fn into_meshes(self, clip_bounds: Rectangle) -> impl Iterator<Item = Mesh> {
self.stack
.into_iter()
.filter_map(move |buffer| match buffer {
Buffer::Solid(buffer) if !buffer.indices.is_empty() => {
Some(Mesh::Solid {
buffers: mesh::Indexed {
vertices: buffer.vertices,
indices: buffer.indices,
},
clip_bounds,
transformation: Transformation::IDENTITY,
})
}
Buffer::Gradient(buffer) if !buffer.indices.is_empty() => {
Some(Mesh::Gradient {
buffers: mesh::Indexed {
vertices: buffer.vertices,
indices: buffer.indices,
},
clip_bounds,
transformation: Transformation::IDENTITY,
})
}
_ => None,
})
}
}
#[derive(Debug)]
@ -591,7 +620,9 @@ pub(super) fn dashed(path: &Path, line_dash: LineDash<'_>) -> Path {
let mut draw_line = false;
walk_along_path(
path.raw().iter().flattened(0.01),
path.raw().iter().flattened(
lyon::tessellation::StrokeOptions::DEFAULT_TOLERANCE,
),
0.0,
lyon::tessellation::StrokeOptions::DEFAULT_TOLERANCE,
&mut RepeatedPattern {

107
wgpu/src/image/cache.rs Normal file
View file

@ -0,0 +1,107 @@
use crate::core::{self, Size};
use crate::image::atlas::{self, Atlas};
use std::cell::{RefCell, RefMut};
use std::rc::Rc;
#[derive(Debug)]
pub struct Cache {
atlas: Atlas,
#[cfg(feature = "image")]
raster: crate::image::raster::Cache,
#[cfg(feature = "svg")]
vector: crate::image::vector::Cache,
}
impl Cache {
pub fn new(device: &wgpu::Device, backend: wgpu::Backend) -> Self {
Self {
atlas: Atlas::new(device, backend),
#[cfg(feature = "image")]
raster: crate::image::raster::Cache::default(),
#[cfg(feature = "svg")]
vector: crate::image::vector::Cache::default(),
}
}
pub fn layer_count(&self) -> usize {
self.atlas.layer_count()
}
#[cfg(feature = "image")]
pub fn measure_image(&mut self, handle: &core::image::Handle) -> Size<u32> {
self.raster.load(handle).dimensions()
}
#[cfg(feature = "svg")]
pub fn measure_svg(&mut self, handle: &core::svg::Handle) -> Size<u32> {
self.vector.load(handle).viewport_dimensions()
}
#[cfg(feature = "image")]
pub fn upload_raster(
&mut self,
device: &wgpu::Device,
encoder: &mut wgpu::CommandEncoder,
handle: &core::image::Handle,
) -> Option<&atlas::Entry> {
self.raster.upload(device, encoder, handle, &mut self.atlas)
}
#[cfg(feature = "svg")]
pub fn upload_vector(
&mut self,
device: &wgpu::Device,
encoder: &mut wgpu::CommandEncoder,
handle: &core::svg::Handle,
color: Option<core::Color>,
size: [f32; 2],
scale: f32,
) -> Option<&atlas::Entry> {
self.vector.upload(
device,
encoder,
handle,
color,
size,
scale,
&mut self.atlas,
)
}
pub fn create_bind_group(
&self,
device: &wgpu::Device,
layout: &wgpu::BindGroupLayout,
) -> wgpu::BindGroup {
device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some("iced_wgpu::image texture atlas bind group"),
layout,
entries: &[wgpu::BindGroupEntry {
binding: 0,
resource: wgpu::BindingResource::TextureView(self.atlas.view()),
}],
})
}
pub fn trim(&mut self) {
#[cfg(feature = "image")]
self.raster.trim(&mut self.atlas);
#[cfg(feature = "svg")]
self.vector.trim(&mut self.atlas);
}
}
#[derive(Debug, Clone)]
pub struct Shared(Rc<RefCell<Cache>>);
impl Shared {
pub fn new(cache: Cache) -> Self {
Self(Rc::new(RefCell::new(cache)))
}
pub fn lock(&self) -> RefMut<'_, Cache> {
self.0.borrow_mut()
}
}

View file

@ -1,3 +1,6 @@
pub(crate) mod cache;
pub(crate) use cache::Cache;
mod atlas;
#[cfg(feature = "image")]
@ -6,183 +9,30 @@ mod raster;
#[cfg(feature = "svg")]
mod vector;
use atlas::Atlas;
use crate::core::{Rectangle, Size, Transformation};
use crate::layer;
use crate::Buffer;
use std::cell::RefCell;
use bytemuck::{Pod, Zeroable};
use std::mem;
use bytemuck::{Pod, Zeroable};
pub use crate::graphics::Image;
#[cfg(feature = "image")]
use crate::core::image;
#[cfg(feature = "svg")]
use crate::core::svg;
#[cfg(feature = "tracing")]
use tracing::info_span;
pub type Batch = Vec<Image>;
#[derive(Debug)]
pub struct Pipeline {
#[cfg(feature = "image")]
raster_cache: RefCell<raster::Cache>,
#[cfg(feature = "svg")]
vector_cache: RefCell<vector::Cache>,
pipeline: wgpu::RenderPipeline,
nearest_sampler: wgpu::Sampler,
linear_sampler: wgpu::Sampler,
texture: wgpu::BindGroup,
texture_version: usize,
texture_atlas: Atlas,
texture_layout: wgpu::BindGroupLayout,
constant_layout: wgpu::BindGroupLayout,
cache: cache::Shared,
layers: Vec<Layer>,
prepare_layer: usize,
}
#[derive(Debug)]
struct Layer {
uniforms: wgpu::Buffer,
nearest: Data,
linear: Data,
}
impl Layer {
fn new(
device: &wgpu::Device,
constant_layout: &wgpu::BindGroupLayout,
nearest_sampler: &wgpu::Sampler,
linear_sampler: &wgpu::Sampler,
) -> Self {
let uniforms = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("iced_wgpu::image uniforms buffer"),
size: mem::size_of::<Uniforms>() as u64,
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
});
let nearest =
Data::new(device, constant_layout, nearest_sampler, &uniforms);
let linear =
Data::new(device, constant_layout, linear_sampler, &uniforms);
Self {
uniforms,
nearest,
linear,
}
}
fn prepare(
&mut self,
device: &wgpu::Device,
queue: &wgpu::Queue,
nearest_instances: &[Instance],
linear_instances: &[Instance],
transformation: Transformation,
) {
queue.write_buffer(
&self.uniforms,
0,
bytemuck::bytes_of(&Uniforms {
transform: transformation.into(),
}),
);
self.nearest.upload(device, queue, nearest_instances);
self.linear.upload(device, queue, linear_instances);
}
fn render<'a>(&'a self, render_pass: &mut wgpu::RenderPass<'a>) {
self.nearest.render(render_pass);
self.linear.render(render_pass);
}
}
#[derive(Debug)]
struct Data {
constants: wgpu::BindGroup,
instances: Buffer<Instance>,
instance_count: usize,
}
impl Data {
pub fn new(
device: &wgpu::Device,
constant_layout: &wgpu::BindGroupLayout,
sampler: &wgpu::Sampler,
uniforms: &wgpu::Buffer,
) -> Self {
let constants = device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some("iced_wgpu::image constants bind group"),
layout: constant_layout,
entries: &[
wgpu::BindGroupEntry {
binding: 0,
resource: wgpu::BindingResource::Buffer(
wgpu::BufferBinding {
buffer: uniforms,
offset: 0,
size: None,
},
),
},
wgpu::BindGroupEntry {
binding: 1,
resource: wgpu::BindingResource::Sampler(sampler),
},
],
});
let instances = Buffer::new(
device,
"iced_wgpu::image instance buffer",
Instance::INITIAL,
wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
);
Self {
constants,
instances,
instance_count: 0,
}
}
fn upload(
&mut self,
device: &wgpu::Device,
queue: &wgpu::Queue,
instances: &[Instance],
) {
self.instance_count = instances.len();
if self.instance_count == 0 {
return;
}
let _ = self.instances.resize(device, instances.len());
let _ = self.instances.write(queue, 0, instances);
}
fn render<'a>(&'a self, render_pass: &mut wgpu::RenderPass<'a>) {
if self.instance_count == 0 {
return;
}
render_pass.set_bind_group(0, &self.constants, &[]);
render_pass.set_vertex_buffer(0, self.instances.slice(..));
render_pass.draw(0..6, 0..self.instance_count as u32);
}
}
impl Pipeline {
pub fn new(
device: &wgpu::Device,
@ -265,9 +115,9 @@ impl Pipeline {
label: Some("iced_wgpu image shader"),
source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Borrowed(
concat!(
include_str!("shader/vertex.wgsl"),
include_str!("../shader/vertex.wgsl"),
"\n",
include_str!("shader/image.wgsl"),
include_str!("../shader/image.wgsl"),
),
)),
});
@ -330,126 +180,82 @@ impl Pipeline {
multiview: None,
});
let texture_atlas = Atlas::new(device, backend);
let texture = device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some("iced_wgpu::image texture atlas bind group"),
layout: &texture_layout,
entries: &[wgpu::BindGroupEntry {
binding: 0,
resource: wgpu::BindingResource::TextureView(
texture_atlas.view(),
),
}],
});
let cache = Cache::new(device, backend);
let texture = cache.create_bind_group(device, &texture_layout);
Pipeline {
#[cfg(feature = "image")]
raster_cache: RefCell::new(raster::Cache::default()),
#[cfg(feature = "svg")]
vector_cache: RefCell::new(vector::Cache::default()),
pipeline,
nearest_sampler,
linear_sampler,
texture,
texture_version: texture_atlas.layer_count(),
texture_atlas,
texture_version: cache.layer_count(),
texture_layout,
constant_layout,
cache: cache::Shared::new(cache),
layers: Vec::new(),
prepare_layer: 0,
}
}
#[cfg(feature = "image")]
pub fn dimensions(&self, handle: &image::Handle) -> Size<u32> {
let mut cache = self.raster_cache.borrow_mut();
let memory = cache.load(handle);
memory.dimensions()
}
#[cfg(feature = "svg")]
pub fn viewport_dimensions(&self, handle: &svg::Handle) -> Size<u32> {
let mut cache = self.vector_cache.borrow_mut();
let svg = cache.load(handle);
svg.viewport_dimensions()
pub fn cache(&self) -> &cache::Shared {
&self.cache
}
pub fn prepare(
&mut self,
device: &wgpu::Device,
queue: &wgpu::Queue,
encoder: &mut wgpu::CommandEncoder,
images: &[layer::Image],
belt: &mut wgpu::util::StagingBelt,
images: &Batch,
transformation: Transformation,
_scale: f32,
scale: f32,
) {
#[cfg(feature = "tracing")]
let _ = info_span!("Wgpu::Image", "PREPARE").entered();
#[cfg(feature = "tracing")]
let _ = info_span!("Wgpu::Image", "DRAW").entered();
let transformation = transformation * Transformation::scale(scale);
let nearest_instances: &mut Vec<Instance> = &mut Vec::new();
let linear_instances: &mut Vec<Instance> = &mut Vec::new();
#[cfg(feature = "image")]
let mut raster_cache = self.raster_cache.borrow_mut();
#[cfg(feature = "svg")]
let mut vector_cache = self.vector_cache.borrow_mut();
let mut cache = self.cache.lock();
for image in images {
match &image {
#[cfg(feature = "image")]
layer::Image::Raster {
Image::Raster {
handle,
filter_method,
bounds,
} => {
if let Some(atlas_entry) = raster_cache.upload(
device,
encoder,
handle,
&mut self.texture_atlas,
) {
if let Some(atlas_entry) =
cache.upload_raster(device, encoder, handle)
{
add_instances(
[bounds.x, bounds.y],
[bounds.width, bounds.height],
atlas_entry,
match filter_method {
image::FilterMethod::Nearest => {
crate::core::image::FilterMethod::Nearest => {
nearest_instances
}
image::FilterMethod::Linear => linear_instances,
crate::core::image::FilterMethod::Linear => {
linear_instances
}
},
);
}
}
#[cfg(not(feature = "image"))]
layer::Image::Raster { .. } => {}
Image::Raster { .. } => {}
#[cfg(feature = "svg")]
layer::Image::Vector {
Image::Vector {
handle,
color,
bounds,
} => {
let size = [bounds.width, bounds.height];
if let Some(atlas_entry) = vector_cache.upload(
device,
encoder,
handle,
*color,
size,
_scale,
&mut self.texture_atlas,
if let Some(atlas_entry) = cache.upload_vector(
device, encoder, handle, *color, size, scale,
) {
add_instances(
[bounds.x, bounds.y],
@ -460,7 +266,7 @@ impl Pipeline {
}
}
#[cfg(not(feature = "svg"))]
layer::Image::Vector { .. } => {}
Image::Vector { .. } => {}
}
}
@ -468,23 +274,13 @@ impl Pipeline {
return;
}
let texture_version = self.texture_atlas.layer_count();
let texture_version = cache.layer_count();
if self.texture_version != texture_version {
log::info!("Atlas has grown. Recreating bind group...");
self.texture =
device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some("iced_wgpu::image texture atlas bind group"),
layout: &self.texture_layout,
entries: &[wgpu::BindGroupEntry {
binding: 0,
resource: wgpu::BindingResource::TextureView(
self.texture_atlas.view(),
),
}],
});
cache.create_bind_group(device, &self.texture_layout);
self.texture_version = texture_version;
}
@ -501,7 +297,8 @@ impl Pipeline {
layer.prepare(
device,
queue,
encoder,
belt,
nearest_instances,
linear_instances,
transformation,
@ -533,16 +330,159 @@ impl Pipeline {
}
pub fn end_frame(&mut self) {
#[cfg(feature = "image")]
self.raster_cache.borrow_mut().trim(&mut self.texture_atlas);
#[cfg(feature = "svg")]
self.vector_cache.borrow_mut().trim(&mut self.texture_atlas);
self.cache.lock().trim();
self.prepare_layer = 0;
}
}
#[derive(Debug)]
struct Layer {
uniforms: wgpu::Buffer,
nearest: Data,
linear: Data,
}
impl Layer {
fn new(
device: &wgpu::Device,
constant_layout: &wgpu::BindGroupLayout,
nearest_sampler: &wgpu::Sampler,
linear_sampler: &wgpu::Sampler,
) -> Self {
let uniforms = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("iced_wgpu::image uniforms buffer"),
size: mem::size_of::<Uniforms>() as u64,
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
});
let nearest =
Data::new(device, constant_layout, nearest_sampler, &uniforms);
let linear =
Data::new(device, constant_layout, linear_sampler, &uniforms);
Self {
uniforms,
nearest,
linear,
}
}
fn prepare(
&mut self,
device: &wgpu::Device,
encoder: &mut wgpu::CommandEncoder,
belt: &mut wgpu::util::StagingBelt,
nearest_instances: &[Instance],
linear_instances: &[Instance],
transformation: Transformation,
) {
let uniforms = Uniforms {
transform: transformation.into(),
};
let bytes = bytemuck::bytes_of(&uniforms);
belt.write_buffer(
encoder,
&self.uniforms,
0,
(bytes.len() as u64).try_into().expect("Sized uniforms"),
device,
)
.copy_from_slice(bytes);
self.nearest
.upload(device, encoder, belt, nearest_instances);
self.linear.upload(device, encoder, belt, linear_instances);
}
fn render<'a>(&'a self, render_pass: &mut wgpu::RenderPass<'a>) {
self.nearest.render(render_pass);
self.linear.render(render_pass);
}
}
#[derive(Debug)]
struct Data {
constants: wgpu::BindGroup,
instances: Buffer<Instance>,
instance_count: usize,
}
impl Data {
pub fn new(
device: &wgpu::Device,
constant_layout: &wgpu::BindGroupLayout,
sampler: &wgpu::Sampler,
uniforms: &wgpu::Buffer,
) -> Self {
let constants = device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some("iced_wgpu::image constants bind group"),
layout: constant_layout,
entries: &[
wgpu::BindGroupEntry {
binding: 0,
resource: wgpu::BindingResource::Buffer(
wgpu::BufferBinding {
buffer: uniforms,
offset: 0,
size: None,
},
),
},
wgpu::BindGroupEntry {
binding: 1,
resource: wgpu::BindingResource::Sampler(sampler),
},
],
});
let instances = Buffer::new(
device,
"iced_wgpu::image instance buffer",
Instance::INITIAL,
wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
);
Self {
constants,
instances,
instance_count: 0,
}
}
fn upload(
&mut self,
device: &wgpu::Device,
encoder: &mut wgpu::CommandEncoder,
belt: &mut wgpu::util::StagingBelt,
instances: &[Instance],
) {
self.instance_count = instances.len();
if self.instance_count == 0 {
return;
}
let _ = self.instances.resize(device, instances.len());
let _ = self.instances.write(device, encoder, belt, 0, instances);
}
fn render<'a>(&'a self, render_pass: &mut wgpu::RenderPass<'a>) {
if self.instance_count == 0 {
return;
}
render_pass.set_bind_group(0, &self.constants, &[]);
render_pass.set_vertex_buffer(0, self.instances.slice(..));
render_pass.draw(0..6, 0..self.instance_count as u32);
}
}
#[repr(C)]
#[derive(Debug, Clone, Copy, Zeroable, Pod)]
struct Instance {

10
wgpu/src/image/null.rs Normal file
View file

@ -0,0 +1,10 @@
pub use crate::graphics::Image;
#[derive(Debug, Default)]
pub struct Batch;
impl Batch {
pub fn push(&mut self, _image: Image) {}
pub fn clear(&mut self) {}
}

View file

@ -4,7 +4,7 @@ use crate::graphics;
use crate::graphics::image::image_rs;
use crate::image::atlas::{self, Atlas};
use std::collections::{HashMap, HashSet};
use rustc_hash::{FxHashMap, FxHashSet};
/// Entry in cache corresponding to an image handle
#[derive(Debug)]
@ -38,8 +38,8 @@ impl Memory {
/// Caches image raster data
#[derive(Debug, Default)]
pub struct Cache {
map: HashMap<u64, Memory>,
hits: HashSet<u64>,
map: FxHashMap<u64, Memory>,
hits: FxHashSet<u64>,
}
impl Cache {

View file

@ -5,7 +5,7 @@ use crate::image::atlas::{self, Atlas};
use resvg::tiny_skia;
use resvg::usvg::{self, TreeTextToPath};
use std::collections::{HashMap, HashSet};
use rustc_hash::{FxHashMap, FxHashSet};
use std::fs;
/// Entry in cache corresponding to an svg handle
@ -33,10 +33,10 @@ impl Svg {
/// Caches svg vector and raster data
#[derive(Debug, Default)]
pub struct Cache {
svgs: HashMap<u64, Svg>,
rasterized: HashMap<(u64, u32, u32, ColorFilter), atlas::Entry>,
svg_hits: HashSet<u64>,
rasterized_hits: HashSet<(u64, u32, u32, ColorFilter)>,
svgs: FxHashMap<u64, Svg>,
rasterized: FxHashMap<(u64, u32, u32, ColorFilter), atlas::Entry>,
svg_hits: FxHashSet<u64>,
rasterized_hits: FxHashSet<(u64, u32, u32, ColorFilter)>,
}
type ColorFilter = Option<[u8; 4]>;

View file

@ -1,343 +1,293 @@
//! Organize rendering primitives into a flattened list of layers.
mod image;
mod pipeline;
mod text;
pub mod mesh;
pub use image::Image;
pub use mesh::Mesh;
pub use pipeline::Pipeline;
pub use text::Text;
use crate::core;
use crate::core::alignment;
use crate::core::{
Color, Font, Pixels, Point, Rectangle, Size, Transformation, Vector,
};
use crate::core::renderer;
use crate::core::{Background, Color, Point, Rectangle, Transformation};
use crate::graphics;
use crate::graphics::color;
use crate::graphics::Viewport;
use crate::graphics::layer;
use crate::graphics::text::{Editor, Paragraph};
use crate::graphics::Mesh;
use crate::image::{self, Image};
use crate::primitive::{self, Primitive};
use crate::quad::{self, Quad};
use crate::text::{self, Text};
use crate::triangle;
pub type Stack = layer::Stack<Layer>;
/// A group of primitives that should be clipped together.
#[derive(Debug)]
pub struct Layer<'a> {
/// The clipping bounds of the [`Layer`].
pub struct Layer {
pub bounds: Rectangle,
/// The quads of the [`Layer`].
pub quads: quad::Batch,
/// The triangle meshes of the [`Layer`].
pub meshes: Vec<Mesh<'a>>,
/// The text of the [`Layer`].
pub text: Vec<Text<'a>>,
/// The images of the [`Layer`].
pub images: Vec<Image>,
/// The custom pipelines of this [`Layer`].
pub pipelines: Vec<Pipeline>,
pub triangles: triangle::Batch,
pub primitives: primitive::Batch,
pub text: text::Batch,
pub images: image::Batch,
pending_meshes: Vec<Mesh>,
pending_text: Vec<Text>,
}
impl<'a> Layer<'a> {
/// Creates a new [`Layer`] with the given clipping bounds.
pub fn new(bounds: Rectangle) -> Self {
impl Layer {
pub fn draw_quad(
&mut self,
quad: renderer::Quad,
background: Background,
transformation: Transformation,
) {
let bounds = quad.bounds * transformation;
let quad = Quad {
position: [bounds.x, bounds.y],
size: [bounds.width, bounds.height],
border_color: color::pack(quad.border.color),
border_radius: quad.border.radius.into(),
border_width: quad.border.width,
shadow_color: color::pack(quad.shadow.color),
shadow_offset: quad.shadow.offset.into(),
shadow_blur_radius: quad.shadow.blur_radius,
};
self.quads.add(quad, &background);
}
pub fn draw_paragraph(
&mut self,
paragraph: &Paragraph,
position: Point,
color: Color,
clip_bounds: Rectangle,
transformation: Transformation,
) {
let paragraph = Text::Paragraph {
paragraph: paragraph.downgrade(),
position,
color,
clip_bounds,
transformation,
};
self.pending_text.push(paragraph);
}
pub fn draw_editor(
&mut self,
editor: &Editor,
position: Point,
color: Color,
clip_bounds: Rectangle,
transformation: Transformation,
) {
let editor = Text::Editor {
editor: editor.downgrade(),
position,
color,
clip_bounds,
transformation,
};
self.pending_text.push(editor);
}
pub fn draw_text(
&mut self,
text: crate::core::Text,
position: Point,
color: Color,
clip_bounds: Rectangle,
transformation: Transformation,
) {
let text = Text::Cached {
content: text.content,
bounds: Rectangle::new(position, text.bounds) * transformation,
color,
size: text.size * transformation.scale_factor(),
line_height: text.line_height.to_absolute(text.size)
* transformation.scale_factor(),
font: text.font,
horizontal_alignment: text.horizontal_alignment,
vertical_alignment: text.vertical_alignment,
shaping: text.shaping,
clip_bounds: clip_bounds * transformation,
};
self.pending_text.push(text);
}
pub fn draw_image(
&mut self,
handle: crate::core::image::Handle,
filter_method: crate::core::image::FilterMethod,
bounds: Rectangle,
transformation: Transformation,
) {
let image = Image::Raster {
handle,
filter_method,
bounds: bounds * transformation,
};
self.images.push(image);
}
pub fn draw_svg(
&mut self,
handle: crate::core::svg::Handle,
color: Option<Color>,
bounds: Rectangle,
transformation: Transformation,
) {
let svg = Image::Vector {
handle,
color,
bounds: bounds * transformation,
};
self.images.push(svg);
}
pub fn draw_mesh(
&mut self,
mut mesh: Mesh,
transformation: Transformation,
) {
match &mut mesh {
Mesh::Solid {
transformation: local_transformation,
..
}
| Mesh::Gradient {
transformation: local_transformation,
..
} => {
*local_transformation = *local_transformation * transformation;
}
}
self.pending_meshes.push(mesh);
}
pub fn draw_mesh_group(
&mut self,
meshes: Vec<Mesh>,
transformation: Transformation,
) {
self.flush_meshes();
self.triangles.push(triangle::Item::Group {
meshes,
transformation,
});
}
pub fn draw_mesh_cache(
&mut self,
cache: triangle::Cache,
transformation: Transformation,
) {
self.flush_meshes();
self.triangles.push(triangle::Item::Cached {
cache,
transformation,
});
}
pub fn draw_text_group(
&mut self,
text: Vec<Text>,
transformation: Transformation,
) {
self.flush_text();
self.text.push(text::Item::Group {
text,
transformation,
});
}
pub fn draw_text_cache(
&mut self,
cache: text::Cache,
transformation: Transformation,
) {
self.flush_text();
self.text.push(text::Item::Cached {
cache,
transformation,
});
}
pub fn draw_primitive(
&mut self,
bounds: Rectangle,
primitive: Box<dyn Primitive>,
transformation: Transformation,
) {
let bounds = bounds * transformation;
self.primitives
.push(primitive::Instance { bounds, primitive });
}
fn flush_meshes(&mut self) {
if !self.pending_meshes.is_empty() {
self.triangles.push(triangle::Item::Group {
transformation: Transformation::IDENTITY,
meshes: self.pending_meshes.drain(..).collect(),
});
}
}
fn flush_text(&mut self) {
if !self.pending_text.is_empty() {
self.text.push(text::Item::Group {
transformation: Transformation::IDENTITY,
text: self.pending_text.drain(..).collect(),
});
}
}
}
impl graphics::Layer for Layer {
fn with_bounds(bounds: Rectangle) -> Self {
Self {
bounds,
..Self::default()
}
}
fn flush(&mut self) {
self.flush_meshes();
self.flush_text();
}
fn resize(&mut self, bounds: Rectangle) {
self.bounds = bounds;
}
fn reset(&mut self) {
self.bounds = Rectangle::INFINITE;
self.quads.clear();
self.triangles.clear();
self.primitives.clear();
self.text.clear();
self.images.clear();
self.pending_meshes.clear();
self.pending_text.clear();
}
}
impl Default for Layer {
fn default() -> Self {
Self {
bounds: Rectangle::INFINITE,
quads: quad::Batch::default(),
meshes: Vec::new(),
text: Vec::new(),
images: Vec::new(),
pipelines: Vec::new(),
}
}
/// Creates a new [`Layer`] for the provided overlay text.
///
/// This can be useful for displaying debug information.
pub fn overlay(lines: &'a [impl AsRef<str>], viewport: &Viewport) -> Self {
let mut overlay =
Layer::new(Rectangle::with_size(viewport.logical_size()));
for (i, line) in lines.iter().enumerate() {
let text = text::Cached {
content: line.as_ref(),
bounds: Rectangle::new(
Point::new(11.0, 11.0 + 25.0 * i as f32),
Size::INFINITY,
),
color: Color::new(0.9, 0.9, 0.9, 1.0),
size: Pixels(20.0),
line_height: core::text::LineHeight::default(),
font: Font::MONOSPACE,
horizontal_alignment: alignment::Horizontal::Left,
vertical_alignment: alignment::Vertical::Top,
shaping: core::text::Shaping::Basic,
clip_bounds: Rectangle::with_size(Size::INFINITY),
};
overlay.text.push(Text::Cached(text.clone()));
overlay.text.push(Text::Cached(text::Cached {
bounds: text.bounds + Vector::new(-1.0, -1.0),
color: Color::BLACK,
..text
}));
}
overlay
}
/// Distributes the given [`Primitive`] and generates a list of layers based
/// on its contents.
pub fn generate(
primitives: &'a [Primitive],
viewport: &Viewport,
) -> Vec<Self> {
let first_layer =
Layer::new(Rectangle::with_size(viewport.logical_size()));
let mut layers = vec![first_layer];
for primitive in primitives {
Self::process_primitive(
&mut layers,
Transformation::IDENTITY,
primitive,
0,
);
}
layers
}
fn process_primitive(
layers: &mut Vec<Self>,
transformation: Transformation,
primitive: &'a Primitive,
current_layer: usize,
) {
match primitive {
Primitive::Paragraph {
paragraph,
position,
color,
clip_bounds,
} => {
let layer = &mut layers[current_layer];
layer.text.push(Text::Paragraph {
paragraph: paragraph.clone(),
position: *position,
color: *color,
clip_bounds: *clip_bounds,
transformation,
});
}
Primitive::Editor {
editor,
position,
color,
clip_bounds,
} => {
let layer = &mut layers[current_layer];
layer.text.push(Text::Editor {
editor: editor.clone(),
position: *position,
color: *color,
clip_bounds: *clip_bounds,
transformation,
});
}
Primitive::Text {
content,
bounds,
size,
line_height,
color,
font,
horizontal_alignment,
vertical_alignment,
shaping,
clip_bounds,
} => {
let layer = &mut layers[current_layer];
layer.text.push(Text::Cached(text::Cached {
content,
bounds: *bounds + transformation.translation(),
size: *size * transformation.scale_factor(),
line_height: *line_height,
color: *color,
font: *font,
horizontal_alignment: *horizontal_alignment,
vertical_alignment: *vertical_alignment,
shaping: *shaping,
clip_bounds: *clip_bounds * transformation,
}));
}
graphics::Primitive::RawText(raw) => {
let layer = &mut layers[current_layer];
layer.text.push(Text::Raw {
raw: raw.clone(),
transformation,
});
}
Primitive::Quad {
bounds,
background,
border,
shadow,
} => {
let layer = &mut layers[current_layer];
let bounds = *bounds * transformation;
let quad = Quad {
position: [bounds.x, bounds.y],
size: [bounds.width, bounds.height],
border_color: color::pack(border.color),
border_radius: border.radius.into(),
border_width: border.width,
shadow_color: shadow.color.into_linear(),
shadow_offset: shadow.offset.into(),
shadow_blur_radius: shadow.blur_radius,
};
layer.quads.add(quad, background);
}
Primitive::Image {
handle,
filter_method,
bounds,
} => {
let layer = &mut layers[current_layer];
layer.images.push(Image::Raster {
handle: handle.clone(),
filter_method: *filter_method,
bounds: *bounds * transformation,
});
}
Primitive::Svg {
handle,
color,
bounds,
} => {
let layer = &mut layers[current_layer];
layer.images.push(Image::Vector {
handle: handle.clone(),
color: *color,
bounds: *bounds * transformation,
});
}
Primitive::Group { primitives } => {
// TODO: Inspect a bit and regroup (?)
for primitive in primitives {
Self::process_primitive(
layers,
transformation,
primitive,
current_layer,
);
}
}
Primitive::Clip { bounds, content } => {
let layer = &mut layers[current_layer];
let translated_bounds = *bounds * transformation;
// Only draw visible content
if let Some(clip_bounds) =
layer.bounds.intersection(&translated_bounds)
{
let clip_layer = Layer::new(clip_bounds);
layers.push(clip_layer);
Self::process_primitive(
layers,
transformation,
content,
layers.len() - 1,
);
}
}
Primitive::Transform {
transformation: new_transformation,
content,
} => {
Self::process_primitive(
layers,
transformation * *new_transformation,
content,
current_layer,
);
}
Primitive::Cache { content } => {
Self::process_primitive(
layers,
transformation,
content,
current_layer,
);
}
Primitive::Custom(custom) => match custom {
primitive::Custom::Mesh(mesh) => match mesh {
graphics::Mesh::Solid { buffers, size } => {
let layer = &mut layers[current_layer];
let bounds =
Rectangle::with_size(*size) * transformation;
// Only draw visible content
if let Some(clip_bounds) =
layer.bounds.intersection(&bounds)
{
layer.meshes.push(Mesh::Solid {
transformation,
buffers,
clip_bounds,
});
}
}
graphics::Mesh::Gradient { buffers, size } => {
let layer = &mut layers[current_layer];
let bounds =
Rectangle::with_size(*size) * transformation;
// Only draw visible content
if let Some(clip_bounds) =
layer.bounds.intersection(&bounds)
{
layer.meshes.push(Mesh::Gradient {
transformation,
buffers,
clip_bounds,
});
}
}
},
primitive::Custom::Pipeline(pipeline) => {
let layer = &mut layers[current_layer];
let bounds = pipeline.bounds * transformation;
if let Some(clip_bounds) =
layer.bounds.intersection(&bounds)
{
layer.pipelines.push(Pipeline {
bounds,
viewport: clip_bounds,
primitive: pipeline.primitive.clone(),
});
}
}
},
triangles: triangle::Batch::default(),
primitives: primitive::Batch::default(),
text: text::Batch::default(),
images: image::Batch::default(),
pending_meshes: Vec::new(),
pending_text: Vec::new(),
}
}
}

View file

@ -1,30 +0,0 @@
use crate::core::image;
use crate::core::svg;
use crate::core::{Color, Rectangle};
/// A raster or vector image.
#[derive(Debug, Clone)]
pub enum Image {
/// A raster image.
Raster {
/// The handle of a raster image.
handle: image::Handle,
/// The filter method of a raster image.
filter_method: image::FilterMethod,
/// The bounds of the image.
bounds: Rectangle,
},
/// A vector image.
Vector {
/// The handle of a vector image.
handle: svg::Handle,
/// The [`Color`] filter
color: Option<Color>,
/// The bounds of the image.
bounds: Rectangle,
},
}

View file

@ -1,97 +0,0 @@
//! A collection of triangle primitives.
use crate::core::{Rectangle, Transformation};
use crate::graphics::mesh;
/// A mesh of triangles.
#[derive(Debug, Clone, Copy)]
pub enum Mesh<'a> {
/// A mesh of triangles with a solid color.
Solid {
/// The [`Transformation`] for the vertices of the [`Mesh`].
transformation: Transformation,
/// The vertex and index buffers of the [`Mesh`].
buffers: &'a mesh::Indexed<mesh::SolidVertex2D>,
/// The clipping bounds of the [`Mesh`].
clip_bounds: Rectangle<f32>,
},
/// A mesh of triangles with a gradient color.
Gradient {
/// The [`Transformation`] for the vertices of the [`Mesh`].
transformation: Transformation,
/// The vertex and index buffers of the [`Mesh`].
buffers: &'a mesh::Indexed<mesh::GradientVertex2D>,
/// The clipping bounds of the [`Mesh`].
clip_bounds: Rectangle<f32>,
},
}
impl Mesh<'_> {
/// Returns the origin of the [`Mesh`].
pub fn transformation(&self) -> Transformation {
match self {
Self::Solid { transformation, .. }
| Self::Gradient { transformation, .. } => *transformation,
}
}
/// Returns the indices of the [`Mesh`].
pub fn indices(&self) -> &[u32] {
match self {
Self::Solid { buffers, .. } => &buffers.indices,
Self::Gradient { buffers, .. } => &buffers.indices,
}
}
/// Returns the clip bounds of the [`Mesh`].
pub fn clip_bounds(&self) -> Rectangle<f32> {
match self {
Self::Solid { clip_bounds, .. }
| Self::Gradient { clip_bounds, .. } => *clip_bounds,
}
}
}
/// The result of counting the attributes of a set of meshes.
#[derive(Debug, Clone, Copy, Default)]
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,
}
/// Returns the number of total vertices & total indices of all [`Mesh`]es.
pub fn attribute_count_of<'a>(meshes: &'a [Mesh<'a>]) -> AttributeCount {
meshes
.iter()
.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();
}
}
count
})
}

View file

@ -1,17 +0,0 @@
use crate::core::Rectangle;
use crate::primitive::pipeline::Primitive;
use std::sync::Arc;
#[derive(Clone, Debug)]
/// A custom primitive which can be used to render primitives associated with a custom pipeline.
pub struct Pipeline {
/// The bounds of the [`Pipeline`].
pub bounds: Rectangle,
/// The viewport of the [`Pipeline`].
pub viewport: Rectangle,
/// The [`Primitive`] to render.
pub primitive: Arc<dyn Primitive>,
}

View file

@ -1,70 +0,0 @@
use crate::core::alignment;
use crate::core::text;
use crate::core::{Color, Font, Pixels, Point, Rectangle, Transformation};
use crate::graphics;
use crate::graphics::text::editor;
use crate::graphics::text::paragraph;
/// A text primitive.
#[derive(Debug, Clone)]
pub enum Text<'a> {
/// A paragraph.
#[allow(missing_docs)]
Paragraph {
paragraph: paragraph::Weak,
position: Point,
color: Color,
clip_bounds: Rectangle,
transformation: Transformation,
},
/// An editor.
#[allow(missing_docs)]
Editor {
editor: editor::Weak,
position: Point,
color: Color,
clip_bounds: Rectangle,
transformation: Transformation,
},
/// Some cached text.
Cached(Cached<'a>),
/// Some raw text.
#[allow(missing_docs)]
Raw {
raw: graphics::text::Raw,
transformation: Transformation,
},
}
#[derive(Debug, Clone)]
pub struct Cached<'a> {
/// The content of the [`Text`].
pub content: &'a str,
/// The layout bounds of the [`Text`].
pub bounds: Rectangle,
/// The color of the [`Text`], in __linear RGB_.
pub color: Color,
/// The size of the [`Text`] in logical pixels.
pub size: Pixels,
/// The line height of the [`Text`].
pub line_height: text::LineHeight,
/// The font of the [`Text`].
pub font: Font,
/// The horizontal alignment of the [`Text`].
pub horizontal_alignment: alignment::Horizontal,
/// The vertical alignment of the [`Text`].
pub vertical_alignment: alignment::Vertical,
/// The shaping strategy of the text.
pub shaping: text::Shaping,
/// The clip bounds of the text.
pub clip_bounds: Rectangle,
}

View file

@ -20,15 +20,8 @@
#![doc(
html_logo_url = "https://raw.githubusercontent.com/iced-rs/iced/9ab6923e943f784985e9ef9ca28b10278297225d/docs/logo.svg"
)]
#![forbid(rust_2018_idioms)]
#![deny(
missing_debug_implementations,
missing_docs,
unsafe_code,
unused_results,
rustdoc::broken_intra_doc_links
)]
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
#![allow(missing_docs)]
pub mod layer;
pub mod primitive;
pub mod settings;
@ -37,13 +30,21 @@ pub mod window;
#[cfg(feature = "geometry")]
pub mod geometry;
mod backend;
mod buffer;
mod color;
mod engine;
mod quad;
mod text;
mod triangle;
#[cfg(any(feature = "image", feature = "svg"))]
#[path = "image/mod.rs"]
mod image;
#[cfg(not(any(feature = "image", feature = "svg")))]
#[path = "image/null.rs"]
mod image;
use buffer::Buffer;
pub use iced_graphics as graphics;
@ -51,16 +52,538 @@ pub use iced_graphics::core;
pub use wgpu;
pub use backend::Backend;
pub use engine::Engine;
pub use layer::Layer;
pub use primitive::Primitive;
pub use settings::Settings;
#[cfg(any(feature = "image", feature = "svg"))]
mod image;
#[cfg(feature = "geometry")]
pub use geometry::Geometry;
use crate::core::{
Background, Color, Font, Pixels, Point, Rectangle, Size, Transformation,
};
use crate::graphics::text::{Editor, Paragraph};
use crate::graphics::Viewport;
/// A [`wgpu`] graphics renderer for [`iced`].
///
/// [`wgpu`]: https://github.com/gfx-rs/wgpu-rs
/// [`iced`]: https://github.com/iced-rs/iced
pub type Renderer = iced_graphics::Renderer<Backend>;
#[allow(missing_debug_implementations)]
pub struct Renderer {
default_font: Font,
default_text_size: Pixels,
layers: layer::Stack,
triangle_storage: triangle::Storage,
text_storage: text::Storage,
// TODO: Centralize all the image feature handling
#[cfg(any(feature = "svg", feature = "image"))]
image_cache: image::cache::Shared,
}
impl Renderer {
pub fn new(
_engine: &Engine,
default_font: Font,
default_text_size: Pixels,
) -> Self {
Self {
default_font,
default_text_size,
layers: layer::Stack::new(),
triangle_storage: triangle::Storage::new(),
text_storage: text::Storage::new(),
#[cfg(any(feature = "svg", feature = "image"))]
image_cache: _engine.image_cache().clone(),
}
}
pub fn present<T: AsRef<str>>(
&mut self,
engine: &mut Engine,
device: &wgpu::Device,
queue: &wgpu::Queue,
encoder: &mut wgpu::CommandEncoder,
clear_color: Option<Color>,
format: wgpu::TextureFormat,
frame: &wgpu::TextureView,
viewport: &Viewport,
overlay: &[T],
) {
self.draw_overlay(overlay, viewport);
self.prepare(engine, device, queue, format, encoder, viewport);
self.render(engine, encoder, frame, clear_color, viewport);
self.triangle_storage.trim();
self.text_storage.trim();
}
fn prepare(
&mut self,
engine: &mut Engine,
device: &wgpu::Device,
queue: &wgpu::Queue,
_format: wgpu::TextureFormat,
encoder: &mut wgpu::CommandEncoder,
viewport: &Viewport,
) {
let scale_factor = viewport.scale_factor() as f32;
for layer in self.layers.iter_mut() {
if !layer.quads.is_empty() {
engine.quad_pipeline.prepare(
device,
encoder,
&mut engine.staging_belt,
&layer.quads,
viewport.projection(),
scale_factor,
);
}
if !layer.triangles.is_empty() {
engine.triangle_pipeline.prepare(
device,
encoder,
&mut engine.staging_belt,
&mut self.triangle_storage,
&layer.triangles,
Transformation::scale(scale_factor),
viewport.physical_size(),
);
}
if !layer.primitives.is_empty() {
for instance in &layer.primitives {
instance.primitive.prepare(
device,
queue,
engine.format,
&mut engine.primitive_storage,
&instance.bounds,
viewport,
);
}
}
if !layer.text.is_empty() {
engine.text_pipeline.prepare(
device,
queue,
encoder,
&mut self.text_storage,
&layer.text,
layer.bounds,
Transformation::scale(scale_factor),
viewport.physical_size(),
);
}
#[cfg(any(feature = "svg", feature = "image"))]
if !layer.images.is_empty() {
engine.image_pipeline.prepare(
device,
encoder,
&mut engine.staging_belt,
&layer.images,
viewport.projection(),
scale_factor,
);
}
}
}
fn render(
&mut self,
engine: &mut Engine,
encoder: &mut wgpu::CommandEncoder,
frame: &wgpu::TextureView,
clear_color: Option<Color>,
viewport: &Viewport,
) {
use std::mem::ManuallyDrop;
let mut render_pass = ManuallyDrop::new(encoder.begin_render_pass(
&wgpu::RenderPassDescriptor {
label: Some("iced_wgpu render pass"),
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
view: frame,
resolve_target: None,
ops: wgpu::Operations {
load: match clear_color {
Some(background_color) => wgpu::LoadOp::Clear({
let [r, g, b, a] =
graphics::color::pack(background_color)
.components();
wgpu::Color {
r: f64::from(r),
g: f64::from(g),
b: f64::from(b),
a: f64::from(a),
}
}),
None => wgpu::LoadOp::Load,
},
store: wgpu::StoreOp::Store,
},
})],
depth_stencil_attachment: None,
timestamp_writes: None,
occlusion_query_set: None,
},
));
let mut quad_layer = 0;
let mut mesh_layer = 0;
let mut text_layer = 0;
#[cfg(any(feature = "svg", feature = "image"))]
let mut image_layer = 0;
let scale_factor = viewport.scale_factor() as f32;
let physical_bounds = Rectangle::<f32>::from(Rectangle::with_size(
viewport.physical_size(),
));
let scale = Transformation::scale(scale_factor);
for layer in self.layers.iter() {
let Some(physical_bounds) =
physical_bounds.intersection(&(layer.bounds * scale))
else {
continue;
};
let Some(scissor_rect) = physical_bounds.snap() else {
continue;
};
if !layer.quads.is_empty() {
engine.quad_pipeline.render(
quad_layer,
scissor_rect,
&layer.quads,
&mut render_pass,
);
quad_layer += 1;
}
if !layer.triangles.is_empty() {
let _ = ManuallyDrop::into_inner(render_pass);
mesh_layer += engine.triangle_pipeline.render(
encoder,
frame,
&self.triangle_storage,
mesh_layer,
&layer.triangles,
physical_bounds,
scale,
);
render_pass = ManuallyDrop::new(encoder.begin_render_pass(
&wgpu::RenderPassDescriptor {
label: Some("iced_wgpu render pass"),
color_attachments: &[Some(
wgpu::RenderPassColorAttachment {
view: frame,
resolve_target: None,
ops: wgpu::Operations {
load: wgpu::LoadOp::Load,
store: wgpu::StoreOp::Store,
},
},
)],
depth_stencil_attachment: None,
timestamp_writes: None,
occlusion_query_set: None,
},
));
}
if !layer.primitives.is_empty() {
let _ = ManuallyDrop::into_inner(render_pass);
for instance in &layer.primitives {
if let Some(clip_bounds) = (instance.bounds * scale)
.intersection(&physical_bounds)
.and_then(Rectangle::snap)
{
instance.primitive.render(
encoder,
&engine.primitive_storage,
frame,
&clip_bounds,
);
}
}
render_pass = ManuallyDrop::new(encoder.begin_render_pass(
&wgpu::RenderPassDescriptor {
label: Some("iced_wgpu render pass"),
color_attachments: &[Some(
wgpu::RenderPassColorAttachment {
view: frame,
resolve_target: None,
ops: wgpu::Operations {
load: wgpu::LoadOp::Load,
store: wgpu::StoreOp::Store,
},
},
)],
depth_stencil_attachment: None,
timestamp_writes: None,
occlusion_query_set: None,
},
));
}
if !layer.text.is_empty() {
text_layer += engine.text_pipeline.render(
&self.text_storage,
text_layer,
&layer.text,
scissor_rect,
&mut render_pass,
);
}
#[cfg(any(feature = "svg", feature = "image"))]
if !layer.images.is_empty() {
engine.image_pipeline.render(
image_layer,
scissor_rect,
&mut render_pass,
);
image_layer += 1;
}
}
let _ = ManuallyDrop::into_inner(render_pass);
}
fn draw_overlay(
&mut self,
overlay: &[impl AsRef<str>],
viewport: &Viewport,
) {
use crate::core::alignment;
use crate::core::text::Renderer as _;
use crate::core::Renderer as _;
use crate::core::Vector;
self.with_layer(
Rectangle::with_size(viewport.logical_size()),
|renderer| {
for (i, line) in overlay.iter().enumerate() {
let text = crate::core::Text {
content: line.as_ref().to_owned(),
bounds: viewport.logical_size(),
size: Pixels(20.0),
line_height: core::text::LineHeight::default(),
font: Font::MONOSPACE,
horizontal_alignment: alignment::Horizontal::Left,
vertical_alignment: alignment::Vertical::Top,
shaping: core::text::Shaping::Basic,
};
renderer.fill_text(
text.clone(),
Point::new(11.0, 11.0 + 25.0 * i as f32),
Color::new(0.9, 0.9, 0.9, 1.0),
Rectangle::with_size(Size::INFINITY),
);
renderer.fill_text(
text,
Point::new(11.0, 11.0 + 25.0 * i as f32)
+ Vector::new(-1.0, -1.0),
Color::BLACK,
Rectangle::with_size(Size::INFINITY),
);
}
},
);
}
}
impl core::Renderer for Renderer {
fn start_layer(&mut self, bounds: Rectangle) {
self.layers.push_clip(bounds);
}
fn end_layer(&mut self) {
self.layers.pop_clip();
}
fn start_transformation(&mut self, transformation: Transformation) {
self.layers.push_transformation(transformation);
}
fn end_transformation(&mut self) {
self.layers.pop_transformation();
}
fn fill_quad(
&mut self,
quad: core::renderer::Quad,
background: impl Into<Background>,
) {
let (layer, transformation) = self.layers.current_mut();
layer.draw_quad(quad, background.into(), transformation);
}
fn clear(&mut self) {
self.layers.clear();
}
}
impl core::text::Renderer for Renderer {
type Font = Font;
type Paragraph = Paragraph;
type Editor = Editor;
const ICON_FONT: Font = Font::with_name("Iced-Icons");
const CHECKMARK_ICON: char = '\u{f00c}';
const ARROW_DOWN_ICON: char = '\u{e800}';
fn default_font(&self) -> Self::Font {
self.default_font
}
fn default_size(&self) -> Pixels {
self.default_text_size
}
fn fill_paragraph(
&mut self,
text: &Self::Paragraph,
position: Point,
color: Color,
clip_bounds: Rectangle,
) {
let (layer, transformation) = self.layers.current_mut();
layer.draw_paragraph(
text,
position,
color,
clip_bounds,
transformation,
);
}
fn fill_editor(
&mut self,
editor: &Self::Editor,
position: Point,
color: Color,
clip_bounds: Rectangle,
) {
let (layer, transformation) = self.layers.current_mut();
layer.draw_editor(editor, position, color, clip_bounds, transformation);
}
fn fill_text(
&mut self,
text: core::Text,
position: Point,
color: Color,
clip_bounds: Rectangle,
) {
let (layer, transformation) = self.layers.current_mut();
layer.draw_text(text, position, color, clip_bounds, transformation);
}
}
#[cfg(feature = "image")]
impl core::image::Renderer for Renderer {
type Handle = core::image::Handle;
fn measure_image(&self, handle: &Self::Handle) -> Size<u32> {
self.image_cache.lock().measure_image(handle)
}
fn draw_image(
&mut self,
handle: Self::Handle,
filter_method: core::image::FilterMethod,
bounds: Rectangle,
) {
let (layer, transformation) = self.layers.current_mut();
layer.draw_image(handle, filter_method, bounds, transformation);
}
}
#[cfg(feature = "svg")]
impl core::svg::Renderer for Renderer {
fn measure_svg(&self, handle: &core::svg::Handle) -> Size<u32> {
self.image_cache.lock().measure_svg(handle)
}
fn draw_svg(
&mut self,
handle: core::svg::Handle,
color_filter: Option<Color>,
bounds: Rectangle,
) {
let (layer, transformation) = self.layers.current_mut();
layer.draw_svg(handle, color_filter, bounds, transformation);
}
}
impl graphics::mesh::Renderer for Renderer {
fn draw_mesh(&mut self, mesh: graphics::Mesh) {
let (layer, transformation) = self.layers.current_mut();
layer.draw_mesh(mesh, transformation);
}
}
#[cfg(feature = "geometry")]
impl graphics::geometry::Renderer for Renderer {
type Geometry = Geometry;
type Frame = geometry::Frame;
fn new_frame(&self, size: Size) -> Self::Frame {
geometry::Frame::new(size)
}
fn draw_geometry(&mut self, geometry: Self::Geometry) {
let (layer, transformation) = self.layers.current_mut();
match geometry {
Geometry::Live { meshes, text } => {
layer.draw_mesh_group(meshes, transformation);
layer.draw_text_group(text, transformation);
}
Geometry::Cached(cache) => {
if let Some(meshes) = cache.meshes {
layer.draw_mesh_cache(meshes, transformation);
}
if let Some(text) = cache.text {
layer.draw_text_cache(text, transformation);
}
}
}
}
}
impl primitive::Renderer for Renderer {
fn draw_primitive(&mut self, bounds: Rectangle, primitive: impl Primitive) {
let (layer, transformation) = self.layers.current_mut();
layer.draw_primitive(bounds, Box::new(primitive), transformation);
}
}
impl graphics::compositor::Default for crate::Renderer {
type Compositor = window::Compositor;
}

View file

@ -1,38 +1,95 @@
//! Draw using different graphical primitives.
pub mod pipeline;
pub use pipeline::Pipeline;
use crate::core::Rectangle;
use crate::graphics::{Damage, Mesh};
//! Draw custom primitives.
use crate::core::{self, Rectangle};
use crate::graphics::Viewport;
use rustc_hash::FxHashMap;
use std::any::{Any, TypeId};
use std::fmt::Debug;
/// The graphical primitives supported by `iced_wgpu`.
pub type Primitive = crate::graphics::Primitive<Custom>;
/// A batch of primitives.
pub type Batch = Vec<Instance>;
/// The custom primitives supported by `iced_wgpu`.
#[derive(Debug, Clone, PartialEq)]
pub enum Custom {
/// A mesh primitive.
Mesh(Mesh),
/// A custom pipeline primitive.
Pipeline(Pipeline),
/// A set of methods which allows a [`Primitive`] to be rendered.
pub trait Primitive: Debug + Send + Sync + 'static {
/// Processes the [`Primitive`], allowing for GPU buffer allocation.
fn prepare(
&self,
device: &wgpu::Device,
queue: &wgpu::Queue,
format: wgpu::TextureFormat,
storage: &mut Storage,
bounds: &Rectangle,
viewport: &Viewport,
);
/// Renders the [`Primitive`].
fn render(
&self,
encoder: &mut wgpu::CommandEncoder,
storage: &Storage,
target: &wgpu::TextureView,
clip_bounds: &Rectangle<u32>,
);
}
impl Damage for Custom {
fn bounds(&self) -> Rectangle {
match self {
Self::Mesh(mesh) => mesh.bounds(),
Self::Pipeline(pipeline) => pipeline.bounds,
#[derive(Debug)]
/// An instance of a specific [`Primitive`].
pub struct Instance {
/// The bounds of the [`Instance`].
pub bounds: Rectangle,
/// The [`Primitive`] to render.
pub primitive: Box<dyn Primitive>,
}
impl Instance {
/// Creates a new [`Instance`] with the given [`Primitive`].
pub fn new(bounds: Rectangle, primitive: impl Primitive) -> Self {
Instance {
bounds,
primitive: Box::new(primitive),
}
}
}
impl TryFrom<Mesh> for Custom {
type Error = &'static str;
/// A renderer than can draw custom primitives.
pub trait Renderer: core::Renderer {
/// Draws a custom primitive.
fn draw_primitive(&mut self, bounds: Rectangle, primitive: impl Primitive);
}
fn try_from(mesh: Mesh) -> Result<Self, Self::Error> {
Ok(Custom::Mesh(mesh))
/// Stores custom, user-provided types.
#[derive(Default, Debug)]
pub struct Storage {
pipelines: FxHashMap<TypeId, Box<dyn Any + Send>>,
}
impl Storage {
/// Returns `true` if `Storage` contains a type `T`.
pub fn has<T: 'static>(&self) -> bool {
self.pipelines.get(&TypeId::of::<T>()).is_some()
}
/// Inserts the data `T` in to [`Storage`].
pub fn store<T: 'static + Send>(&mut self, data: T) {
let _ = self.pipelines.insert(TypeId::of::<T>(), Box::new(data));
}
/// Returns a reference to the data with type `T` if it exists in [`Storage`].
pub fn get<T: 'static>(&self) -> Option<&T> {
self.pipelines.get(&TypeId::of::<T>()).map(|pipeline| {
pipeline
.downcast_ref::<T>()
.expect("Value with this type does not exist in Storage.")
})
}
/// Returns a mutable reference to the data with type `T` if it exists in [`Storage`].
pub fn get_mut<T: 'static>(&mut self) -> Option<&mut T> {
self.pipelines.get_mut(&TypeId::of::<T>()).map(|pipeline| {
pipeline
.downcast_mut::<T>()
.expect("Value with this type does not exist in Storage.")
})
}
}

View file

@ -1,116 +0,0 @@
//! Draw primitives using custom pipelines.
use crate::core::{self, Rectangle, Size};
use std::any::{Any, TypeId};
use std::collections::HashMap;
use std::fmt::Debug;
use std::sync::Arc;
#[derive(Clone, Debug)]
/// A custom primitive which can be used to render primitives associated with a custom pipeline.
pub struct Pipeline {
/// The bounds of the [`Pipeline`].
pub bounds: Rectangle,
/// The [`Primitive`] to render.
pub primitive: Arc<dyn Primitive>,
}
impl Pipeline {
/// Creates a new [`Pipeline`] with the given [`Primitive`].
pub fn new(bounds: Rectangle, primitive: impl Primitive) -> Self {
Pipeline {
bounds,
primitive: Arc::new(primitive),
}
}
}
impl PartialEq for Pipeline {
fn eq(&self, other: &Self) -> bool {
self.primitive.type_id() == other.primitive.type_id()
}
}
/// A set of methods which allows a [`Primitive`] to be rendered.
pub trait Primitive: Debug + Send + Sync + 'static {
/// Processes the [`Primitive`], allowing for GPU buffer allocation.
fn prepare(
&self,
format: wgpu::TextureFormat,
device: &wgpu::Device,
queue: &wgpu::Queue,
bounds: Rectangle,
target_size: Size<u32>,
scale_factor: f32,
storage: &mut Storage,
);
/// Renders the [`Primitive`].
fn render(
&self,
storage: &Storage,
target: &wgpu::TextureView,
target_size: Size<u32>,
viewport: Rectangle<u32>,
encoder: &mut wgpu::CommandEncoder,
);
}
/// A renderer than can draw custom pipeline primitives.
pub trait Renderer: core::Renderer {
/// Draws a custom pipeline primitive.
fn draw_pipeline_primitive(
&mut self,
bounds: Rectangle,
primitive: impl Primitive,
);
}
impl Renderer for crate::Renderer {
fn draw_pipeline_primitive(
&mut self,
bounds: Rectangle,
primitive: impl Primitive,
) {
self.draw_primitive(super::Primitive::Custom(super::Custom::Pipeline(
Pipeline::new(bounds, primitive),
)));
}
}
/// Stores custom, user-provided pipelines.
#[derive(Default, Debug)]
pub struct Storage {
pipelines: HashMap<TypeId, Box<dyn Any + Send>>,
}
impl Storage {
/// Returns `true` if `Storage` contains a pipeline with type `T`.
pub fn has<T: 'static>(&self) -> bool {
self.pipelines.get(&TypeId::of::<T>()).is_some()
}
/// Inserts the pipeline `T` in to [`Storage`].
pub fn store<T: 'static + Send>(&mut self, pipeline: T) {
let _ = self.pipelines.insert(TypeId::of::<T>(), Box::new(pipeline));
}
/// Returns a reference to pipeline with type `T` if it exists in [`Storage`].
pub fn get<T: 'static>(&self) -> Option<&T> {
self.pipelines.get(&TypeId::of::<T>()).map(|pipeline| {
pipeline
.downcast_ref::<T>()
.expect("Pipeline with this type does not exist in Storage.")
})
}
/// Returns a mutable reference to pipeline `T` if it exists in [`Storage`].
pub fn get_mut<T: 'static>(&mut self) -> Option<&mut T> {
self.pipelines.get_mut(&TypeId::of::<T>()).map(|pipeline| {
pipeline
.downcast_mut::<T>()
.expect("Pipeline with this type does not exist in Storage.")
})
}
}

View file

@ -12,11 +12,37 @@ use bytemuck::{Pod, Zeroable};
use std::mem;
#[cfg(feature = "tracing")]
use tracing::info_span;
const INITIAL_INSTANCES: usize = 2_000;
/// The properties of a quad.
#[derive(Clone, Copy, Debug, Pod, Zeroable)]
#[repr(C)]
pub struct Quad {
/// The position of the [`Quad`].
pub position: [f32; 2],
/// The size of the [`Quad`].
pub size: [f32; 2],
/// The border color of the [`Quad`], in __linear RGB__.
pub border_color: color::Packed,
/// The border radii of the [`Quad`].
pub border_radius: [f32; 4],
/// The border width of the [`Quad`].
pub border_width: f32,
/// The shadow color of the [`Quad`].
pub shadow_color: color::Packed,
/// The shadow offset of the [`Quad`].
pub shadow_offset: [f32; 2],
/// The shadow blur radius of the [`Quad`].
pub shadow_blur_radius: f32,
}
#[derive(Debug)]
pub struct Pipeline {
solid: solid::Pipeline,
@ -57,7 +83,8 @@ impl Pipeline {
pub fn prepare(
&mut self,
device: &wgpu::Device,
queue: &wgpu::Queue,
encoder: &mut wgpu::CommandEncoder,
belt: &mut wgpu::util::StagingBelt,
quads: &Batch,
transformation: Transformation,
scale: f32,
@ -67,7 +94,7 @@ impl Pipeline {
}
let layer = &mut self.layers[self.prepare_layer];
layer.prepare(device, queue, quads, transformation, scale);
layer.prepare(device, encoder, belt, quads, transformation, scale);
self.prepare_layer += 1;
}
@ -123,7 +150,7 @@ impl Pipeline {
}
#[derive(Debug)]
struct Layer {
pub struct Layer {
constants: wgpu::BindGroup,
constants_buffer: wgpu::Buffer,
solid: solid::Layer,
@ -162,56 +189,46 @@ impl Layer {
pub fn prepare(
&mut self,
device: &wgpu::Device,
queue: &wgpu::Queue,
encoder: &mut wgpu::CommandEncoder,
belt: &mut wgpu::util::StagingBelt,
quads: &Batch,
transformation: Transformation,
scale: f32,
) {
#[cfg(feature = "tracing")]
let _ = info_span!("Wgpu::Quad", "PREPARE").entered();
self.update(device, encoder, belt, transformation, scale);
if !quads.solids.is_empty() {
self.solid.prepare(device, encoder, belt, &quads.solids);
}
if !quads.gradients.is_empty() {
self.gradient
.prepare(device, encoder, belt, &quads.gradients);
}
}
pub fn update(
&mut self,
device: &wgpu::Device,
encoder: &mut wgpu::CommandEncoder,
belt: &mut wgpu::util::StagingBelt,
transformation: Transformation,
scale: f32,
) {
let uniforms = Uniforms::new(transformation, scale);
let bytes = bytemuck::bytes_of(&uniforms);
queue.write_buffer(
belt.write_buffer(
encoder,
&self.constants_buffer,
0,
bytemuck::bytes_of(&uniforms),
);
self.solid.prepare(device, queue, &quads.solids);
self.gradient.prepare(device, queue, &quads.gradients);
(bytes.len() as u64).try_into().expect("Sized uniforms"),
device,
)
.copy_from_slice(bytes);
}
}
/// The properties of a quad.
#[derive(Clone, Copy, Debug, Pod, Zeroable)]
#[repr(C)]
pub struct Quad {
/// The position of the [`Quad`].
pub position: [f32; 2],
/// The size of the [`Quad`].
pub size: [f32; 2],
/// The border color of the [`Quad`], in __linear RGB__.
pub border_color: color::Packed,
/// The border radii of the [`Quad`].
pub border_radius: [f32; 4],
/// The border width of the [`Quad`].
pub border_width: f32,
/// The shadow color of the [`Quad`].
pub shadow_color: [f32; 4],
/// The shadow offset of the [`Quad`].
pub shadow_offset: [f32; 2],
/// The shadow blur radius of the [`Quad`].
pub shadow_blur_radius: f32,
}
/// A group of [`Quad`]s rendered together.
#[derive(Default, Debug)]
pub struct Batch {
@ -221,10 +238,13 @@ pub struct Batch {
/// The gradient quads of the [`Layer`].
gradients: Vec<Gradient>,
/// The quad order of the [`Layer`]; stored as a tuple of the quad type & its count.
order: Vec<(Kind, usize)>,
/// The quad order of the [`Layer`].
order: Order,
}
/// The quad order of a [`Layer`]; stored as a tuple of the quad type & its count.
type Order = Vec<(Kind, usize)>;
impl Batch {
/// Returns true if there are no quads of any type in [`Quads`].
pub fn is_empty(&self) -> bool {
@ -264,6 +284,12 @@ impl Batch {
}
}
}
pub fn clear(&mut self) {
self.solids.clear();
self.gradients.clear();
self.order.clear();
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]

View file

@ -46,11 +46,12 @@ impl Layer {
pub fn prepare(
&mut self,
device: &wgpu::Device,
queue: &wgpu::Queue,
encoder: &mut wgpu::CommandEncoder,
belt: &mut wgpu::util::StagingBelt,
instances: &[Gradient],
) {
let _ = self.instances.resize(device, instances.len());
let _ = self.instances.write(queue, 0, instances);
let _ = self.instances.write(device, encoder, belt, 0, instances);
self.instance_count = instances.len();
}

View file

@ -40,11 +40,12 @@ impl Layer {
pub fn prepare(
&mut self,
device: &wgpu::Device,
queue: &wgpu::Queue,
encoder: &mut wgpu::CommandEncoder,
belt: &mut wgpu::util::StagingBelt,
instances: &[Solid],
) {
let _ = self.instances.resize(device, instances.len());
let _ = self.instances.write(queue, 0, instances);
let _ = self.instances.write(device, encoder, belt, 0, instances);
self.instance_count = instances.len();
}

View file

@ -2,18 +2,18 @@
use crate::core::{Font, Pixels};
use crate::graphics::{self, Antialiasing};
/// The settings of a [`Backend`].
/// The settings of a [`Renderer`].
///
/// [`Backend`]: crate::Backend
/// [`Renderer`]: crate::Renderer
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Settings {
/// The present mode of the [`Backend`].
/// The present mode of the [`Renderer`].
///
/// [`Backend`]: crate::Backend
/// [`Renderer`]: crate::Renderer
pub present_mode: wgpu::PresentMode,
/// The internal graphics backend to use.
pub internal_backend: wgpu::Backends,
/// The graphics backends to use.
pub backends: wgpu::Backends,
/// The default [`Font`] to use.
pub default_font: Font,
@ -33,7 +33,7 @@ impl Default for Settings {
fn default() -> Settings {
Settings {
present_mode: wgpu::PresentMode::AutoVsync,
internal_backend: wgpu::Backends::all(),
backends: wgpu::Backends::all(),
default_font: Font::default(),
default_text_size: Pixels(16.0),
antialiasing: None,

View file

@ -1,22 +1,14 @@
var<private> positions: array<vec2<f32>, 6> = array<vec2<f32>, 6>(
vec2<f32>(-1.0, 1.0),
vec2<f32>(-1.0, -1.0),
vec2<f32>(1.0, -1.0),
vec2<f32>(-1.0, 1.0),
vec2<f32>(1.0, 1.0),
vec2<f32>(1.0, -1.0)
);
var<private> uvs: array<vec2<f32>, 6> = array<vec2<f32>, 6>(
vec2<f32>(0.0, 0.0),
vec2<f32>(0.0, 1.0),
vec2<f32>(1.0, 0.0),
vec2<f32>(1.0, 1.0),
vec2<f32>(0.0, 0.0),
vec2<f32>(1.0, 0.0),
vec2<f32>(0.0, 1.0),
vec2<f32>(1.0, 1.0)
);
@group(0) @binding(0) var u_sampler: sampler;
@group(0) @binding(1) var<uniform> u_ratio: vec2<f32>;
@group(1) @binding(0) var u_texture: texture_2d<f32>;
struct VertexInput {
@ -30,9 +22,11 @@ struct VertexOutput {
@vertex
fn vs_main(input: VertexInput) -> VertexOutput {
let uv = uvs[input.vertex_index];
var out: VertexOutput;
out.uv = uvs[input.vertex_index];
out.position = vec4<f32>(positions[input.vertex_index], 0.0, 1.0);
out.uv = uv * u_ratio;
out.position = vec4<f32>(uv * vec2(2.0, -2.0) + vec2(-1.0, 1.0), 0.0, 1.0);
return out;
}

View file

@ -1,20 +1,193 @@
use crate::core::alignment;
use crate::core::{Rectangle, Size, Transformation};
use crate::graphics::color;
use crate::graphics::text::cache::{self, Cache};
use crate::graphics::text::cache::{self, Cache as BufferCache};
use crate::graphics::text::{font_system, to_color, Editor, Paragraph};
use crate::layer::Text;
use std::borrow::Cow;
use std::cell::RefCell;
use rustc_hash::{FxHashMap, FxHashSet};
use std::collections::hash_map;
use std::rc::Rc;
use std::sync::atomic::{self, AtomicU64};
use std::sync::Arc;
pub use crate::graphics::Text;
const COLOR_MODE: glyphon::ColorMode = if color::GAMMA_CORRECTION {
glyphon::ColorMode::Accurate
} else {
glyphon::ColorMode::Web
};
pub type Batch = Vec<Item>;
#[derive(Debug)]
pub enum Item {
Group {
transformation: Transformation,
text: Vec<Text>,
},
Cached {
transformation: Transformation,
cache: Cache,
},
}
#[derive(Debug, Clone)]
pub struct Cache {
id: Id,
text: Rc<[Text]>,
version: usize,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Id(u64);
impl Cache {
pub fn new(text: Vec<Text>) -> Option<Self> {
static NEXT_ID: AtomicU64 = AtomicU64::new(0);
if text.is_empty() {
return None;
}
Some(Self {
id: Id(NEXT_ID.fetch_add(1, atomic::Ordering::Relaxed)),
text: Rc::from(text),
version: 0,
})
}
pub fn update(&mut self, text: Vec<Text>) {
self.text = Rc::from(text);
self.version += 1;
}
}
struct Upload {
renderer: glyphon::TextRenderer,
atlas: glyphon::TextAtlas,
buffer_cache: BufferCache,
transformation: Transformation,
version: usize,
}
#[derive(Default)]
pub struct Storage {
uploads: FxHashMap<Id, Upload>,
recently_used: FxHashSet<Id>,
}
impl Storage {
pub fn new() -> Self {
Self::default()
}
fn get(&self, cache: &Cache) -> Option<&Upload> {
if cache.text.is_empty() {
return None;
}
self.uploads.get(&cache.id)
}
fn prepare(
&mut self,
device: &wgpu::Device,
queue: &wgpu::Queue,
encoder: &mut wgpu::CommandEncoder,
format: wgpu::TextureFormat,
cache: &Cache,
new_transformation: Transformation,
bounds: Rectangle,
target_size: Size<u32>,
) {
match self.uploads.entry(cache.id) {
hash_map::Entry::Occupied(entry) => {
let upload = entry.into_mut();
if !cache.text.is_empty()
&& (upload.version != cache.version
|| upload.transformation != new_transformation)
{
let _ = prepare(
device,
queue,
encoder,
&mut upload.renderer,
&mut upload.atlas,
&mut upload.buffer_cache,
&cache.text,
bounds,
new_transformation,
target_size,
);
upload.version = cache.version;
upload.transformation = new_transformation;
upload.buffer_cache.trim();
upload.atlas.trim();
}
}
hash_map::Entry::Vacant(entry) => {
let mut atlas = glyphon::TextAtlas::with_color_mode(
device, queue, format, COLOR_MODE,
);
let mut renderer = glyphon::TextRenderer::new(
&mut atlas,
device,
wgpu::MultisampleState::default(),
None,
);
let mut buffer_cache = BufferCache::new();
let _ = prepare(
device,
queue,
encoder,
&mut renderer,
&mut atlas,
&mut buffer_cache,
&cache.text,
bounds,
new_transformation,
target_size,
);
let _ = entry.insert(Upload {
renderer,
atlas,
buffer_cache,
transformation: new_transformation,
version: 0,
});
log::info!(
"New text upload: {} (total: {})",
cache.id.0,
self.uploads.len()
);
}
}
let _ = self.recently_used.insert(cache.id);
}
pub fn trim(&mut self) {
self.uploads.retain(|id, _| self.recently_used.contains(id));
self.recently_used.clear();
}
}
#[allow(missing_debug_implementations)]
pub struct Pipeline {
renderers: Vec<glyphon::TextRenderer>,
format: wgpu::TextureFormat,
atlas: glyphon::TextAtlas,
renderers: Vec<glyphon::TextRenderer>,
prepare_layer: usize,
cache: RefCell<Cache>,
cache: BufferCache,
}
impl Pipeline {
@ -24,273 +197,95 @@ impl Pipeline {
format: wgpu::TextureFormat,
) -> Self {
Pipeline {
format,
renderers: Vec::new(),
atlas: glyphon::TextAtlas::with_color_mode(
device,
queue,
format,
if color::GAMMA_CORRECTION {
glyphon::ColorMode::Accurate
} else {
glyphon::ColorMode::Web
},
device, queue, format, COLOR_MODE,
),
prepare_layer: 0,
cache: RefCell::new(Cache::new()),
cache: BufferCache::new(),
}
}
pub fn load_font(&mut self, bytes: Cow<'static, [u8]>) {
font_system()
.write()
.expect("Write font system")
.load_font(bytes);
self.cache = RefCell::new(Cache::new());
}
pub fn prepare(
&mut self,
device: &wgpu::Device,
queue: &wgpu::Queue,
sections: &[Text<'_>],
encoder: &mut wgpu::CommandEncoder,
storage: &mut Storage,
batch: &Batch,
layer_bounds: Rectangle,
scale_factor: f32,
layer_transformation: Transformation,
target_size: Size<u32>,
) {
if self.renderers.len() <= self.prepare_layer {
self.renderers.push(glyphon::TextRenderer::new(
&mut self.atlas,
device,
wgpu::MultisampleState::default(),
None,
));
}
for item in batch {
match item {
Item::Group {
transformation,
text,
} => {
if self.renderers.len() <= self.prepare_layer {
self.renderers.push(glyphon::TextRenderer::new(
&mut self.atlas,
device,
wgpu::MultisampleState::default(),
None,
));
}
let mut font_system = font_system().write().expect("Write font system");
let font_system = font_system.raw();
let renderer = &mut self.renderers[self.prepare_layer];
let cache = self.cache.get_mut();
enum Allocation {
Paragraph(Paragraph),
Editor(Editor),
Cache(cache::KeyHash),
Raw(Arc<glyphon::Buffer>),
}
let allocations: Vec<_> = sections
.iter()
.map(|section| match section {
Text::Paragraph { paragraph, .. } => {
paragraph.upgrade().map(Allocation::Paragraph)
}
Text::Editor { editor, .. } => {
editor.upgrade().map(Allocation::Editor)
}
Text::Cached(text) => {
let (key, _) = cache.allocate(
font_system,
cache::Key {
content: text.content,
size: text.size.into(),
line_height: f32::from(
text.line_height.to_absolute(text.size),
),
font: text.font,
bounds: Size {
width: text.bounds.width,
height: text.bounds.height,
},
shaping: text.shaping,
},
let renderer = &mut self.renderers[self.prepare_layer];
let result = prepare(
device,
queue,
encoder,
renderer,
&mut self.atlas,
&mut self.cache,
text,
layer_bounds * layer_transformation,
layer_transformation * *transformation,
target_size,
);
Some(Allocation::Cache(key))
match result {
Ok(()) => {
self.prepare_layer += 1;
}
Err(glyphon::PrepareError::AtlasFull) => {
// If the atlas cannot grow, then all bets are off.
// Instead of panicking, we will just pray that the result
// will be somewhat readable...
}
}
}
Text::Raw { raw, .. } => {
raw.buffer.upgrade().map(Allocation::Raw)
}
})
.collect();
let layer_bounds = layer_bounds * scale_factor;
let text_areas = sections.iter().zip(allocations.iter()).filter_map(
|(section, allocation)| {
let (
buffer,
bounds,
horizontal_alignment,
vertical_alignment,
color,
clip_bounds,
Item::Cached {
transformation,
) = match section {
Text::Paragraph {
position,
color,
clip_bounds,
transformation,
..
} => {
use crate::core::text::Paragraph as _;
let Some(Allocation::Paragraph(paragraph)) = allocation
else {
return None;
};
(
paragraph.buffer(),
Rectangle::new(*position, paragraph.min_bounds()),
paragraph.horizontal_alignment(),
paragraph.vertical_alignment(),
*color,
*clip_bounds,
*transformation,
)
}
Text::Editor {
position,
color,
clip_bounds,
transformation,
..
} => {
use crate::core::text::Editor as _;
let Some(Allocation::Editor(editor)) = allocation
else {
return None;
};
(
editor.buffer(),
Rectangle::new(*position, editor.bounds()),
alignment::Horizontal::Left,
alignment::Vertical::Top,
*color,
*clip_bounds,
*transformation,
)
}
Text::Cached(text) => {
let Some(Allocation::Cache(key)) = allocation else {
return None;
};
let entry = cache.get(key).expect("Get cached buffer");
(
&entry.buffer,
Rectangle::new(
text.bounds.position(),
entry.min_bounds,
),
text.horizontal_alignment,
text.vertical_alignment,
text.color,
text.clip_bounds,
Transformation::IDENTITY,
)
}
Text::Raw {
raw,
transformation,
} => {
let Some(Allocation::Raw(buffer)) = allocation else {
return None;
};
let (width, height) = buffer.size();
(
buffer.as_ref(),
Rectangle::new(
raw.position,
Size::new(width, height),
),
alignment::Horizontal::Left,
alignment::Vertical::Top,
raw.color,
raw.clip_bounds,
*transformation,
)
}
};
let bounds = bounds * transformation * scale_factor;
let left = match horizontal_alignment {
alignment::Horizontal::Left => bounds.x,
alignment::Horizontal::Center => {
bounds.x - bounds.width / 2.0
}
alignment::Horizontal::Right => bounds.x - bounds.width,
};
let top = match vertical_alignment {
alignment::Vertical::Top => bounds.y,
alignment::Vertical::Center => {
bounds.y - bounds.height / 2.0
}
alignment::Vertical::Bottom => bounds.y - bounds.height,
};
let clip_bounds = layer_bounds.intersection(
&(clip_bounds * transformation * scale_factor),
)?;
Some(glyphon::TextArea {
buffer,
left,
top,
scale: scale_factor * transformation.scale_factor(),
bounds: glyphon::TextBounds {
left: clip_bounds.x as i32,
top: clip_bounds.y as i32,
right: (clip_bounds.x + clip_bounds.width) as i32,
bottom: (clip_bounds.y + clip_bounds.height) as i32,
},
default_color: to_color(color),
})
},
);
let result = renderer.prepare(
device,
queue,
font_system,
&mut self.atlas,
glyphon::Resolution {
width: target_size.width,
height: target_size.height,
},
text_areas,
&mut glyphon::SwashCache::new(),
);
match result {
Ok(()) => {
self.prepare_layer += 1;
}
Err(glyphon::PrepareError::AtlasFull) => {
// If the atlas cannot grow, then all bets are off.
// Instead of panicking, we will just pray that the result
// will be somewhat readable...
cache,
} => {
storage.prepare(
device,
queue,
encoder,
self.format,
cache,
layer_transformation * *transformation,
layer_bounds * layer_transformation,
target_size,
);
}
}
}
}
pub fn render<'a>(
&'a self,
layer: usize,
storage: &'a Storage,
start: usize,
batch: &'a Batch,
bounds: Rectangle<u32>,
render_pass: &mut wgpu::RenderPass<'a>,
) {
let renderer = &self.renderers[layer];
) -> usize {
let mut layer_count = 0;
render_pass.set_scissor_rect(
bounds.x,
@ -299,15 +294,251 @@ impl Pipeline {
bounds.height,
);
renderer
.render(&self.atlas, render_pass)
.expect("Render text");
for item in batch {
match item {
Item::Group { .. } => {
let renderer = &self.renderers[start + layer_count];
renderer
.render(&self.atlas, render_pass)
.expect("Render text");
layer_count += 1;
}
Item::Cached { cache, .. } => {
if let Some(upload) = storage.get(cache) {
upload
.renderer
.render(&upload.atlas, render_pass)
.expect("Render cached text");
}
}
}
}
layer_count
}
pub fn end_frame(&mut self) {
self.atlas.trim();
self.cache.get_mut().trim();
self.cache.trim();
self.prepare_layer = 0;
}
}
fn prepare(
device: &wgpu::Device,
queue: &wgpu::Queue,
encoder: &mut wgpu::CommandEncoder,
renderer: &mut glyphon::TextRenderer,
atlas: &mut glyphon::TextAtlas,
buffer_cache: &mut BufferCache,
sections: &[Text],
layer_bounds: Rectangle,
layer_transformation: Transformation,
target_size: Size<u32>,
) -> Result<(), glyphon::PrepareError> {
let mut font_system = font_system().write().expect("Write font system");
let font_system = font_system.raw();
enum Allocation {
Paragraph(Paragraph),
Editor(Editor),
Cache(cache::KeyHash),
Raw(Arc<glyphon::Buffer>),
}
let allocations: Vec<_> = sections
.iter()
.map(|section| match section {
Text::Paragraph { paragraph, .. } => {
paragraph.upgrade().map(Allocation::Paragraph)
}
Text::Editor { editor, .. } => {
editor.upgrade().map(Allocation::Editor)
}
Text::Cached {
content,
bounds,
size,
line_height,
font,
shaping,
..
} => {
let (key, _) = buffer_cache.allocate(
font_system,
cache::Key {
content,
size: f32::from(*size),
line_height: f32::from(*line_height),
font: *font,
bounds: Size {
width: bounds.width,
height: bounds.height,
},
shaping: *shaping,
},
);
Some(Allocation::Cache(key))
}
Text::Raw { raw, .. } => raw.buffer.upgrade().map(Allocation::Raw),
})
.collect();
let text_areas = sections.iter().zip(allocations.iter()).filter_map(
|(section, allocation)| {
let (
buffer,
bounds,
horizontal_alignment,
vertical_alignment,
color,
clip_bounds,
transformation,
) = match section {
Text::Paragraph {
position,
color,
clip_bounds,
transformation,
..
} => {
use crate::core::text::Paragraph as _;
let Some(Allocation::Paragraph(paragraph)) = allocation
else {
return None;
};
(
paragraph.buffer(),
Rectangle::new(*position, paragraph.min_bounds()),
paragraph.horizontal_alignment(),
paragraph.vertical_alignment(),
*color,
*clip_bounds,
*transformation,
)
}
Text::Editor {
position,
color,
clip_bounds,
transformation,
..
} => {
use crate::core::text::Editor as _;
let Some(Allocation::Editor(editor)) = allocation else {
return None;
};
(
editor.buffer(),
Rectangle::new(*position, editor.bounds()),
alignment::Horizontal::Left,
alignment::Vertical::Top,
*color,
*clip_bounds,
*transformation,
)
}
Text::Cached {
bounds,
horizontal_alignment,
vertical_alignment,
color,
clip_bounds,
..
} => {
let Some(Allocation::Cache(key)) = allocation else {
return None;
};
let entry =
buffer_cache.get(key).expect("Get cached buffer");
(
&entry.buffer,
Rectangle::new(bounds.position(), entry.min_bounds),
*horizontal_alignment,
*vertical_alignment,
*color,
*clip_bounds,
Transformation::IDENTITY,
)
}
Text::Raw {
raw,
transformation,
} => {
let Some(Allocation::Raw(buffer)) = allocation else {
return None;
};
let (width, height) = buffer.size();
(
buffer.as_ref(),
Rectangle::new(raw.position, Size::new(width, height)),
alignment::Horizontal::Left,
alignment::Vertical::Top,
raw.color,
raw.clip_bounds,
*transformation,
)
}
};
let bounds = bounds * transformation * layer_transformation;
let left = match horizontal_alignment {
alignment::Horizontal::Left => bounds.x,
alignment::Horizontal::Center => bounds.x - bounds.width / 2.0,
alignment::Horizontal::Right => bounds.x - bounds.width,
};
let top = match vertical_alignment {
alignment::Vertical::Top => bounds.y,
alignment::Vertical::Center => bounds.y - bounds.height / 2.0,
alignment::Vertical::Bottom => bounds.y - bounds.height,
};
let clip_bounds = layer_bounds.intersection(
&(clip_bounds * transformation * layer_transformation),
)?;
Some(glyphon::TextArea {
buffer,
left,
top,
scale: transformation.scale_factor()
* layer_transformation.scale_factor(),
bounds: glyphon::TextBounds {
left: clip_bounds.x as i32,
top: clip_bounds.y as i32,
right: (clip_bounds.x + clip_bounds.width) as i32,
bottom: (clip_bounds.y + clip_bounds.height) as i32,
},
default_color: to_color(color),
})
},
);
renderer.prepare(
device,
queue,
encoder,
font_system,
atlas,
glyphon::Resolution {
width: target_size.width,
height: target_size.height,
},
text_areas,
&mut glyphon::SwashCache::new(),
)
}

View file

@ -1,14 +1,158 @@
//! Draw meshes of triangles.
mod msaa;
use crate::core::{Size, Transformation};
use crate::core::{Rectangle, Size, Transformation};
use crate::graphics::mesh::{self, Mesh};
use crate::graphics::Antialiasing;
use crate::layer::mesh::{self, Mesh};
use crate::Buffer;
use rustc_hash::{FxHashMap, FxHashSet};
use std::collections::hash_map;
use std::rc::Rc;
use std::sync::atomic::{self, AtomicU64};
const INITIAL_INDEX_COUNT: usize = 1_000;
const INITIAL_VERTEX_COUNT: usize = 1_000;
pub type Batch = Vec<Item>;
#[derive(Debug)]
pub enum Item {
Group {
transformation: Transformation,
meshes: Vec<Mesh>,
},
Cached {
transformation: Transformation,
cache: Cache,
},
}
#[derive(Debug, Clone)]
pub struct Cache {
id: Id,
batch: Rc<[Mesh]>,
version: usize,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Id(u64);
impl Cache {
pub fn new(meshes: Vec<Mesh>) -> Option<Self> {
static NEXT_ID: AtomicU64 = AtomicU64::new(0);
if meshes.is_empty() {
return None;
}
Some(Self {
id: Id(NEXT_ID.fetch_add(1, atomic::Ordering::Relaxed)),
batch: Rc::from(meshes),
version: 0,
})
}
pub fn update(&mut self, meshes: Vec<Mesh>) {
self.batch = Rc::from(meshes);
self.version += 1;
}
}
#[derive(Debug)]
struct Upload {
layer: Layer,
transformation: Transformation,
version: usize,
}
#[derive(Debug, Default)]
pub struct Storage {
uploads: FxHashMap<Id, Upload>,
recently_used: FxHashSet<Id>,
}
impl Storage {
pub fn new() -> Self {
Self::default()
}
fn get(&self, cache: &Cache) -> Option<&Upload> {
if cache.batch.is_empty() {
return None;
}
self.uploads.get(&cache.id)
}
fn prepare(
&mut self,
device: &wgpu::Device,
encoder: &mut wgpu::CommandEncoder,
belt: &mut wgpu::util::StagingBelt,
solid: &solid::Pipeline,
gradient: &gradient::Pipeline,
cache: &Cache,
new_transformation: Transformation,
) {
match self.uploads.entry(cache.id) {
hash_map::Entry::Occupied(entry) => {
let upload = entry.into_mut();
if !cache.batch.is_empty()
&& (upload.version != cache.version
|| upload.transformation != new_transformation)
{
upload.layer.prepare(
device,
encoder,
belt,
solid,
gradient,
&cache.batch,
new_transformation,
);
upload.version = cache.version;
upload.transformation = new_transformation;
}
}
hash_map::Entry::Vacant(entry) => {
let mut layer = Layer::new(device, solid, gradient);
layer.prepare(
device,
encoder,
belt,
solid,
gradient,
&cache.batch,
new_transformation,
);
let _ = entry.insert(Upload {
layer,
transformation: new_transformation,
version: 0,
});
log::info!(
"New mesh upload: {} (total: {})",
cache.id.0,
self.uploads.len()
);
}
}
let _ = self.recently_used.insert(cache.id);
}
pub fn trim(&mut self) {
self.uploads.retain(|id, _| self.recently_used.contains(id));
self.recently_used.clear();
}
}
#[derive(Debug)]
pub struct Pipeline {
blit: Option<msaa::Blit>,
@ -18,8 +162,198 @@ pub struct Pipeline {
prepare_layer: usize,
}
impl Pipeline {
pub fn new(
device: &wgpu::Device,
format: wgpu::TextureFormat,
antialiasing: Option<Antialiasing>,
) -> Pipeline {
Pipeline {
blit: antialiasing.map(|a| msaa::Blit::new(device, format, a)),
solid: solid::Pipeline::new(device, format, antialiasing),
gradient: gradient::Pipeline::new(device, format, antialiasing),
layers: Vec::new(),
prepare_layer: 0,
}
}
pub fn prepare(
&mut self,
device: &wgpu::Device,
encoder: &mut wgpu::CommandEncoder,
belt: &mut wgpu::util::StagingBelt,
storage: &mut Storage,
items: &[Item],
scale: Transformation,
target_size: Size<u32>,
) {
let projection = if let Some(blit) = &mut self.blit {
blit.prepare(device, encoder, belt, target_size) * scale
} else {
Transformation::orthographic(target_size.width, target_size.height)
* scale
};
for item in items {
match item {
Item::Group {
transformation,
meshes,
} => {
if self.layers.len() <= self.prepare_layer {
self.layers.push(Layer::new(
device,
&self.solid,
&self.gradient,
));
}
let layer = &mut self.layers[self.prepare_layer];
layer.prepare(
device,
encoder,
belt,
&self.solid,
&self.gradient,
meshes,
projection * *transformation,
);
self.prepare_layer += 1;
}
Item::Cached {
transformation,
cache,
} => {
storage.prepare(
device,
encoder,
belt,
&self.solid,
&self.gradient,
cache,
projection * *transformation,
);
}
}
}
}
pub fn render(
&mut self,
encoder: &mut wgpu::CommandEncoder,
target: &wgpu::TextureView,
storage: &Storage,
start: usize,
batch: &Batch,
bounds: Rectangle,
screen_transformation: Transformation,
) -> usize {
let mut layer_count = 0;
let items = batch.iter().filter_map(|item| match item {
Item::Group {
transformation,
meshes,
} => {
let layer = &self.layers[start + layer_count];
layer_count += 1;
Some((
layer,
meshes.as_slice(),
screen_transformation * *transformation,
))
}
Item::Cached {
transformation,
cache,
} => {
let upload = storage.get(cache)?;
Some((
&upload.layer,
&cache.batch,
screen_transformation * *transformation,
))
}
});
render(
encoder,
target,
self.blit.as_mut(),
&self.solid,
&self.gradient,
bounds,
items,
);
layer_count
}
pub fn end_frame(&mut self) {
self.prepare_layer = 0;
}
}
fn render<'a>(
encoder: &mut wgpu::CommandEncoder,
target: &wgpu::TextureView,
mut blit: Option<&mut msaa::Blit>,
solid: &solid::Pipeline,
gradient: &gradient::Pipeline,
bounds: Rectangle,
group: impl Iterator<Item = (&'a Layer, &'a [Mesh], Transformation)>,
) {
{
let (attachment, resolve_target, load) = if let Some(blit) = &mut blit {
let (attachment, resolve_target) = blit.targets();
(
attachment,
Some(resolve_target),
wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT),
)
} else {
(target, None, wgpu::LoadOp::Load)
};
let mut render_pass =
encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
label: Some("iced_wgpu.triangle.render_pass"),
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
view: attachment,
resolve_target,
ops: wgpu::Operations {
load,
store: wgpu::StoreOp::Store,
},
})],
depth_stencil_attachment: None,
timestamp_writes: None,
occlusion_query_set: None,
});
for (layer, meshes, transformation) in group {
layer.render(
solid,
gradient,
meshes,
bounds,
transformation,
&mut render_pass,
);
}
}
if let Some(blit) = blit {
blit.draw(encoder, target);
}
}
#[derive(Debug)]
struct Layer {
pub struct Layer {
index_buffer: Buffer<u32>,
index_strides: Vec<u32>,
solid: solid::Layer,
@ -48,10 +382,11 @@ impl Layer {
fn prepare(
&mut self,
device: &wgpu::Device,
queue: &wgpu::Queue,
encoder: &mut wgpu::CommandEncoder,
belt: &mut wgpu::util::StagingBelt,
solid: &solid::Pipeline,
gradient: &gradient::Pipeline,
meshes: &[Mesh<'_>],
meshes: &[Mesh],
transformation: Transformation,
) {
// Count the total amount of vertices & indices we need to handle
@ -103,33 +438,47 @@ impl Layer {
let uniforms =
Uniforms::new(transformation * mesh.transformation());
index_offset +=
self.index_buffer.write(queue, index_offset, indices);
index_offset += self.index_buffer.write(
device,
encoder,
belt,
index_offset,
indices,
);
self.index_strides.push(indices.len() as u32);
match mesh {
Mesh::Solid { buffers, .. } => {
solid_vertex_offset += self.solid.vertices.write(
queue,
device,
encoder,
belt,
solid_vertex_offset,
&buffers.vertices,
);
solid_uniform_offset += self.solid.uniforms.write(
queue,
device,
encoder,
belt,
solid_uniform_offset,
&[uniforms],
);
}
Mesh::Gradient { buffers, .. } => {
gradient_vertex_offset += self.gradient.vertices.write(
queue,
device,
encoder,
belt,
gradient_vertex_offset,
&buffers.vertices,
);
gradient_uniform_offset += self.gradient.uniforms.write(
queue,
device,
encoder,
belt,
gradient_uniform_offset,
&[uniforms],
);
@ -142,8 +491,9 @@ impl Layer {
&'a self,
solid: &'a solid::Pipeline,
gradient: &'a gradient::Pipeline,
meshes: &[Mesh<'_>],
scale_factor: f32,
meshes: &[Mesh],
bounds: Rectangle,
transformation: Transformation,
render_pass: &mut wgpu::RenderPass<'a>,
) {
let mut num_solids = 0;
@ -151,11 +501,12 @@ impl Layer {
let mut last_is_solid = None;
for (index, mesh) in meshes.iter().enumerate() {
let clip_bounds = (mesh.clip_bounds() * scale_factor).snap();
if clip_bounds.width < 1 || clip_bounds.height < 1 {
let Some(clip_bounds) = bounds
.intersection(&(mesh.clip_bounds() * transformation))
.and_then(Rectangle::snap)
else {
continue;
}
};
render_pass.set_scissor_rect(
clip_bounds.x,
@ -219,117 +570,6 @@ impl Layer {
}
}
impl Pipeline {
pub fn new(
device: &wgpu::Device,
format: wgpu::TextureFormat,
antialiasing: Option<Antialiasing>,
) -> Pipeline {
Pipeline {
blit: antialiasing.map(|a| msaa::Blit::new(device, format, a)),
solid: solid::Pipeline::new(device, format, antialiasing),
gradient: gradient::Pipeline::new(device, format, antialiasing),
layers: Vec::new(),
prepare_layer: 0,
}
}
pub fn prepare(
&mut self,
device: &wgpu::Device,
queue: &wgpu::Queue,
meshes: &[Mesh<'_>],
transformation: Transformation,
) {
#[cfg(feature = "tracing")]
let _ = tracing::info_span!("Wgpu::Triangle", "PREPARE").entered();
if self.layers.len() <= self.prepare_layer {
self.layers
.push(Layer::new(device, &self.solid, &self.gradient));
}
let layer = &mut self.layers[self.prepare_layer];
layer.prepare(
device,
queue,
&self.solid,
&self.gradient,
meshes,
transformation,
);
self.prepare_layer += 1;
}
pub fn render(
&mut self,
device: &wgpu::Device,
encoder: &mut wgpu::CommandEncoder,
target: &wgpu::TextureView,
layer: usize,
target_size: Size<u32>,
meshes: &[Mesh<'_>],
scale_factor: f32,
) {
#[cfg(feature = "tracing")]
let _ = tracing::info_span!("Wgpu::Triangle", "DRAW").entered();
{
let (attachment, resolve_target, load) = if let Some(blit) =
&mut self.blit
{
let (attachment, resolve_target) =
blit.targets(device, target_size.width, target_size.height);
(
attachment,
Some(resolve_target),
wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT),
)
} else {
(target, None, wgpu::LoadOp::Load)
};
let mut render_pass =
encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
label: Some("iced_wgpu.triangle.render_pass"),
color_attachments: &[Some(
wgpu::RenderPassColorAttachment {
view: attachment,
resolve_target,
ops: wgpu::Operations {
load,
store: wgpu::StoreOp::Store,
},
},
)],
depth_stencil_attachment: None,
timestamp_writes: None,
occlusion_query_set: None,
});
let layer = &mut self.layers[layer];
layer.render(
&self.solid,
&self.gradient,
meshes,
scale_factor,
&mut render_pass,
);
}
if let Some(blit) = &mut self.blit {
blit.draw(encoder, target);
}
}
pub fn end_frame(&mut self) {
self.prepare_layer = 0;
}
}
fn fragment_target(
texture_format: wgpu::TextureFormat,
) -> wgpu::ColorTargetState {

Some files were not shown because too many files have changed in this diff Show more