summaryrefslogtreecommitdiffstats
path: root/wgpu/src
diff options
context:
space:
mode:
Diffstat (limited to 'wgpu/src')
-rw-r--r--wgpu/src/backend.rs76
-rw-r--r--wgpu/src/color.rs4
-rw-r--r--wgpu/src/geometry.rs154
-rw-r--r--wgpu/src/image.rs129
-rw-r--r--wgpu/src/image/vector.rs26
-rw-r--r--wgpu/src/layer.rs64
-rw-r--r--wgpu/src/layer/image.rs3
-rw-r--r--wgpu/src/layer/pipeline.rs17
-rw-r--r--wgpu/src/layer/text.rs23
-rw-r--r--wgpu/src/lib.rs2
-rw-r--r--wgpu/src/primitive.rs9
-rw-r--r--wgpu/src/primitive/pipeline.rs116
-rw-r--r--wgpu/src/text.rs97
-rw-r--r--wgpu/src/triangle.rs7
-rw-r--r--wgpu/src/triangle/msaa.rs4
-rw-r--r--wgpu/src/window/compositor.rs70
16 files changed, 623 insertions, 178 deletions
diff --git a/wgpu/src/backend.rs b/wgpu/src/backend.rs
index 65c63f19..25134d68 100644
--- a/wgpu/src/backend.rs
+++ b/wgpu/src/backend.rs
@@ -1,8 +1,8 @@
use crate::core::{Color, Size};
-use crate::graphics;
use crate::graphics::backend;
use crate::graphics::color;
use crate::graphics::{Transformation, Viewport};
+use crate::primitive::pipeline;
use crate::primitive::{self, Primitive};
use crate::quad;
use crate::text;
@@ -26,6 +26,7 @@ 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,
@@ -51,6 +52,7 @@ impl Backend {
quad_pipeline,
text_pipeline,
triangle_pipeline,
+ pipeline_storage: pipeline::Storage::default(),
#[cfg(any(feature = "image", feature = "svg"))]
image_pipeline,
@@ -67,6 +69,7 @@ impl Backend {
queue: &wgpu::Queue,
encoder: &mut wgpu::CommandEncoder,
clear_color: Option<Color>,
+ format: wgpu::TextureFormat,
frame: &wgpu::TextureView,
primitives: &[Primitive],
viewport: &Viewport,
@@ -89,6 +92,7 @@ impl Backend {
self.prepare(
device,
queue,
+ format,
encoder,
scale_factor,
target_size,
@@ -118,6 +122,7 @@ impl Backend {
&mut self,
device: &wgpu::Device,
queue: &wgpu::Queue,
+ format: wgpu::TextureFormat,
_encoder: &mut wgpu::CommandEncoder,
scale_factor: f32,
target_size: Size<u32>,
@@ -180,6 +185,20 @@ impl Backend {
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,
+ );
+ }
+ }
}
}
@@ -203,7 +222,7 @@ impl Backend {
let mut render_pass = ManuallyDrop::new(encoder.begin_render_pass(
&wgpu::RenderPassDescriptor {
- label: Some("iced_wgpu::quad render pass"),
+ label: Some("iced_wgpu render pass"),
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
view: target,
resolve_target: None,
@@ -222,10 +241,12 @@ impl Backend {
}),
None => wgpu::LoadOp::Load,
},
- store: true,
+ store: wgpu::StoreOp::Store,
},
})],
depth_stencil_attachment: None,
+ timestamp_writes: None,
+ occlusion_query_set: None,
},
));
@@ -264,18 +285,20 @@ impl Backend {
render_pass = ManuallyDrop::new(encoder.begin_render_pass(
&wgpu::RenderPassDescriptor {
- label: Some("iced_wgpu::quad render pass"),
+ label: Some("iced_wgpu render pass"),
color_attachments: &[Some(
wgpu::RenderPassColorAttachment {
view: target,
resolve_target: None,
ops: wgpu::Operations {
load: wgpu::LoadOp::Load,
- store: true,
+ store: wgpu::StoreOp::Store,
},
},
)],
depth_stencil_attachment: None,
+ timestamp_writes: None,
+ occlusion_query_set: None,
},
));
}
@@ -299,6 +322,45 @@ impl Backend {
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);
@@ -310,10 +372,6 @@ impl crate::graphics::Backend for Backend {
}
impl backend::Text for Backend {
- fn font_system(&self) -> &graphics::text::FontSystem {
- self.text_pipeline.font_system()
- }
-
fn load_font(&mut self, font: Cow<'static, [u8]>) {
self.text_pipeline.load_font(font);
}
diff --git a/wgpu/src/color.rs b/wgpu/src/color.rs
index 20827e3c..4598b0a6 100644
--- a/wgpu/src/color.rs
+++ b/wgpu/src/color.rs
@@ -143,10 +143,12 @@ pub fn convert(
resolve_target: None,
ops: wgpu::Operations {
load: wgpu::LoadOp::Load,
- store: true,
+ store: wgpu::StoreOp::Store,
},
})],
depth_stencil_attachment: None,
+ timestamp_writes: None,
+ occlusion_query_set: None,
});
pass.set_pipeline(&pipeline);
diff --git a/wgpu/src/geometry.rs b/wgpu/src/geometry.rs
index 655362b7..4d7f443e 100644
--- a/wgpu/src/geometry.rs
+++ b/wgpu/src/geometry.rs
@@ -1,5 +1,6 @@
//! Build and draw geometry.
-use crate::core::{Point, Rectangle, Size, Vector};
+use crate::core::text::LineHeight;
+use crate::core::{Pixels, Point, Rectangle, Size, Vector};
use crate::graphics::color;
use crate::graphics::geometry::fill::{self, Fill};
use crate::graphics::geometry::{
@@ -115,19 +116,31 @@ struct Transforms {
}
#[derive(Debug, Clone, Copy)]
-struct Transform {
- raw: lyon::math::Transform,
- is_identity: bool,
-}
+struct Transform(lyon::math::Transform);
impl Transform {
- /// Transforms the given [Point] by the transformation matrix.
- fn transform_point(&self, point: &mut Point) {
+ fn is_identity(&self) -> bool {
+ self.0 == lyon::math::Transform::identity()
+ }
+
+ fn is_scale_translation(&self) -> bool {
+ self.0.m12.abs() < 2.0 * f32::EPSILON
+ && self.0.m21.abs() < 2.0 * f32::EPSILON
+ }
+
+ fn scale(&self) -> (f32, f32) {
+ (self.0.m11, self.0.m22)
+ }
+
+ fn transform_point(&self, point: Point) -> Point {
let transformed = self
- .raw
+ .0
.transform_point(euclid::Point2D::new(point.x, point.y));
- point.x = transformed.x;
- point.y = transformed.y;
+
+ Point {
+ x: transformed.x,
+ y: transformed.y,
+ }
}
fn transform_style(&self, style: Style) -> Style {
@@ -142,8 +155,8 @@ impl Transform {
fn transform_gradient(&self, mut gradient: Gradient) -> Gradient {
match &mut gradient {
Gradient::Linear(linear) => {
- self.transform_point(&mut linear.start);
- self.transform_point(&mut linear.end);
+ linear.start = self.transform_point(linear.start);
+ linear.end = self.transform_point(linear.end);
}
}
@@ -163,10 +176,7 @@ impl Frame {
primitives: Vec::new(),
transforms: Transforms {
previous: Vec::new(),
- current: Transform {
- raw: lyon::math::Transform::identity(),
- is_identity: true,
- },
+ current: Transform(lyon::math::Transform::identity()),
},
fill_tessellator: tessellation::FillTessellator::new(),
stroke_tessellator: tessellation::StrokeTessellator::new(),
@@ -209,14 +219,14 @@ impl Frame {
let options = tessellation::FillOptions::default()
.with_fill_rule(into_fill_rule(rule));
- if self.transforms.current.is_identity {
+ if self.transforms.current.is_identity() {
self.fill_tessellator.tessellate_path(
path.raw(),
&options,
buffer.as_mut(),
)
} else {
- let path = path.transform(&self.transforms.current.raw);
+ let path = path.transform(&self.transforms.current.0);
self.fill_tessellator.tessellate_path(
path.raw(),
@@ -241,13 +251,14 @@ impl Frame {
.buffers
.get_fill(&self.transforms.current.transform_style(style));
- let top_left =
- self.transforms.current.raw.transform_point(
- lyon::math::Point::new(top_left.x, top_left.y),
- );
+ let top_left = self
+ .transforms
+ .current
+ .0
+ .transform_point(lyon::math::Point::new(top_left.x, top_left.y));
let size =
- self.transforms.current.raw.transform_vector(
+ self.transforms.current.0.transform_vector(
lyon::math::Vector::new(size.width, size.height),
);
@@ -284,14 +295,14 @@ impl Frame {
Cow::Owned(dashed(path, stroke.line_dash))
};
- if self.transforms.current.is_identity {
+ if self.transforms.current.is_identity() {
self.stroke_tessellator.tessellate_path(
path.raw(),
&options,
buffer.as_mut(),
)
} else {
- let path = path.transform(&self.transforms.current.raw);
+ let path = path.transform(&self.transforms.current.0);
self.stroke_tessellator.tessellate_path(
path.raw(),
@@ -318,33 +329,57 @@ impl Frame {
pub fn fill_text(&mut self, text: impl Into<Text>) {
let text = text.into();
- let position = if self.transforms.current.is_identity {
- text.position
- } else {
- let transformed = self.transforms.current.raw.transform_point(
- lyon::math::Point::new(text.position.x, text.position.y),
- );
+ let (scale_x, scale_y) = self.transforms.current.scale();
+
+ if self.transforms.current.is_scale_translation()
+ && scale_x == scale_y
+ && scale_x > 0.0
+ && scale_y > 0.0
+ {
+ let (position, size, line_height) =
+ if self.transforms.current.is_identity() {
+ (text.position, text.size, text.line_height)
+ } else {
+ let position =
+ self.transforms.current.transform_point(text.position);
+
+ let size = Pixels(text.size.0 * scale_y);
+
+ let line_height = match text.line_height {
+ LineHeight::Absolute(size) => {
+ LineHeight::Absolute(Pixels(size.0 * scale_y))
+ }
+ LineHeight::Relative(factor) => {
+ LineHeight::Relative(factor)
+ }
+ };
- Point::new(transformed.x, transformed.y)
- };
+ (position, size, line_height)
+ };
- // TODO: Use vectorial text instead of primitive
- self.primitives.push(Primitive::Text {
- content: text.content,
- bounds: Rectangle {
+ let bounds = Rectangle {
x: position.x,
y: position.y,
width: f32::INFINITY,
height: f32::INFINITY,
- },
- color: text.color,
- size: text.size,
- line_height: text.line_height,
- font: text.font,
- horizontal_alignment: text.horizontal_alignment,
- vertical_alignment: text.vertical_alignment,
- shaping: text.shaping,
- });
+ };
+
+ // TODO: Honor layering!
+ self.primitives.push(Primitive::Text {
+ content: text.content,
+ bounds,
+ color: text.color,
+ size,
+ line_height,
+ font: text.font,
+ horizontal_alignment: text.horizontal_alignment,
+ vertical_alignment: text.vertical_alignment,
+ shaping: text.shaping,
+ clip_bounds: Rectangle::with_size(Size::INFINITY),
+ });
+ } else {
+ text.draw_with(|path, color| self.fill(&path, color));
+ }
}
/// Stores the current transform of the [`Frame`] and executes the given
@@ -420,26 +455,24 @@ impl Frame {
/// Applies a translation to the current transform of the [`Frame`].
#[inline]
pub fn translate(&mut self, translation: Vector) {
- self.transforms.current.raw = self
- .transforms
- .current
- .raw
- .pre_translate(lyon::math::Vector::new(
- translation.x,
- translation.y,
- ));
- self.transforms.current.is_identity = false;
+ self.transforms.current.0 =
+ self.transforms
+ .current
+ .0
+ .pre_translate(lyon::math::Vector::new(
+ translation.x,
+ translation.y,
+ ));
}
/// Applies a rotation in radians to the current transform of the [`Frame`].
#[inline]
pub fn rotate(&mut self, angle: f32) {
- self.transforms.current.raw = self
+ self.transforms.current.0 = self
.transforms
.current
- .raw
+ .0
.pre_rotate(lyon::math::Angle::radians(angle));
- self.transforms.current.is_identity = false;
}
/// Applies a uniform scaling to the current transform of the [`Frame`].
@@ -455,9 +488,8 @@ impl Frame {
pub fn scale_nonuniform(&mut self, scale: impl Into<Vector>) {
let scale = scale.into();
- self.transforms.current.raw =
- self.transforms.current.raw.pre_scale(scale.x, scale.y);
- self.transforms.current.is_identity = false;
+ self.transforms.current.0 =
+ self.transforms.current.0.pre_scale(scale.x, scale.y);
}
/// Produces the [`Primitive`] representing everything drawn on the [`Frame`].
diff --git a/wgpu/src/image.rs b/wgpu/src/image.rs
index 36c1e228..1e5d3ee0 100644
--- a/wgpu/src/image.rs
+++ b/wgpu/src/image.rs
@@ -35,7 +35,8 @@ pub struct Pipeline {
vector_cache: RefCell<vector::Cache>,
pipeline: wgpu::RenderPipeline,
- sampler: wgpu::Sampler,
+ nearest_sampler: wgpu::Sampler,
+ linear_sampler: wgpu::Sampler,
texture: wgpu::BindGroup,
texture_version: usize,
texture_atlas: Atlas,
@@ -49,16 +50,16 @@ pub struct Pipeline {
#[derive(Debug)]
struct Layer {
uniforms: wgpu::Buffer,
- constants: wgpu::BindGroup,
- instances: Buffer<Instance>,
- instance_count: usize,
+ nearest: Data,
+ linear: Data,
}
impl Layer {
fn new(
device: &wgpu::Device,
constant_layout: &wgpu::BindGroupLayout,
- sampler: &wgpu::Sampler,
+ nearest_sampler: &wgpu::Sampler,
+ linear_sampler: &wgpu::Sampler,
) -> Self {
let uniforms = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("iced_wgpu::image uniforms buffer"),
@@ -67,6 +68,59 @@ impl Layer {
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,
@@ -75,7 +129,7 @@ impl Layer {
binding: 0,
resource: wgpu::BindingResource::Buffer(
wgpu::BufferBinding {
- buffer: &uniforms,
+ buffer: uniforms,
offset: 0,
size: None,
},
@@ -96,28 +150,18 @@ impl Layer {
);
Self {
- uniforms,
constants,
instances,
instance_count: 0,
}
}
- fn prepare(
+ fn upload(
&mut self,
device: &wgpu::Device,
queue: &wgpu::Queue,
instances: &[Instance],
- transformation: Transformation,
) {
- queue.write_buffer(
- &self.uniforms,
- 0,
- bytemuck::bytes_of(&Uniforms {
- transform: transformation.into(),
- }),
- );
-
let _ = self.instances.resize(device, instances.len());
let _ = self.instances.write(queue, 0, instances);
@@ -134,12 +178,22 @@ impl Layer {
impl Pipeline {
pub fn new(device: &wgpu::Device, format: wgpu::TextureFormat) -> Self {
- let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
+ let nearest_sampler = device.create_sampler(&wgpu::SamplerDescriptor {
+ address_mode_u: wgpu::AddressMode::ClampToEdge,
+ address_mode_v: wgpu::AddressMode::ClampToEdge,
+ address_mode_w: wgpu::AddressMode::ClampToEdge,
+ min_filter: wgpu::FilterMode::Nearest,
+ mag_filter: wgpu::FilterMode::Nearest,
+ mipmap_filter: wgpu::FilterMode::Nearest,
+ ..Default::default()
+ });
+
+ let linear_sampler = device.create_sampler(&wgpu::SamplerDescriptor {
address_mode_u: wgpu::AddressMode::ClampToEdge,
address_mode_v: wgpu::AddressMode::ClampToEdge,
address_mode_w: wgpu::AddressMode::ClampToEdge,
- mag_filter: wgpu::FilterMode::Linear,
min_filter: wgpu::FilterMode::Linear,
+ mag_filter: wgpu::FilterMode::Linear,
mipmap_filter: wgpu::FilterMode::Linear,
..Default::default()
});
@@ -286,7 +340,8 @@ impl Pipeline {
vector_cache: RefCell::new(vector::Cache::default()),
pipeline,
- sampler,
+ nearest_sampler,
+ linear_sampler,
texture,
texture_version: texture_atlas.layer_count(),
texture_atlas,
@@ -329,7 +384,8 @@ impl Pipeline {
#[cfg(feature = "tracing")]
let _ = info_span!("Wgpu::Image", "DRAW").entered();
- let instances: &mut Vec<Instance> = &mut Vec::new();
+ 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();
@@ -340,7 +396,11 @@ impl Pipeline {
for image in images {
match &image {
#[cfg(feature = "image")]
- layer::Image::Raster { handle, bounds } => {
+ layer::Image::Raster {
+ handle,
+ filter_method,
+ bounds,
+ } => {
if let Some(atlas_entry) = raster_cache.upload(
device,
encoder,
@@ -351,7 +411,12 @@ impl Pipeline {
[bounds.x, bounds.y],
[bounds.width, bounds.height],
atlas_entry,
- instances,
+ match filter_method {
+ image::FilterMethod::Nearest => {
+ nearest_instances
+ }
+ image::FilterMethod::Linear => linear_instances,
+ },
);
}
}
@@ -379,7 +444,7 @@ impl Pipeline {
[bounds.x, bounds.y],
size,
atlas_entry,
- instances,
+ nearest_instances,
);
}
}
@@ -388,7 +453,7 @@ impl Pipeline {
}
}
- if instances.is_empty() {
+ if nearest_instances.is_empty() && linear_instances.is_empty() {
return;
}
@@ -416,12 +481,20 @@ impl Pipeline {
self.layers.push(Layer::new(
device,
&self.constant_layout,
- &self.sampler,
+ &self.nearest_sampler,
+ &self.linear_sampler,
));
}
let layer = &mut self.layers[self.prepare_layer];
- layer.prepare(device, queue, instances, transformation);
+
+ layer.prepare(
+ device,
+ queue,
+ nearest_instances,
+ linear_instances,
+ transformation,
+ );
self.prepare_layer += 1;
}
@@ -470,7 +543,7 @@ struct Instance {
}
impl Instance {
- pub const INITIAL: usize = 1_000;
+ pub const INITIAL: usize = 20;
}
#[repr(C)]
diff --git a/wgpu/src/image/vector.rs b/wgpu/src/image/vector.rs
index 6582bb82..d9be50d7 100644
--- a/wgpu/src/image/vector.rs
+++ b/wgpu/src/image/vector.rs
@@ -1,9 +1,10 @@
use crate::core::svg;
use crate::core::{Color, Size};
+use crate::graphics::text;
use crate::image::atlas::{self, Atlas};
use resvg::tiny_skia;
-use resvg::usvg;
+use resvg::usvg::{self, TreeTextToPath};
use std::collections::{HashMap, HashSet};
use std::fs;
@@ -49,15 +50,15 @@ impl Cache {
return self.svgs.get(&handle.id()).unwrap();
}
- let svg = match handle.data() {
- svg::Data::Path(path) => {
- let tree = fs::read_to_string(path).ok().and_then(|contents| {
+ let mut svg = match handle.data() {
+ svg::Data::Path(path) => fs::read_to_string(path)
+ .ok()
+ .and_then(|contents| {
usvg::Tree::from_str(&contents, &usvg::Options::default())
.ok()
- });
-
- tree.map(Svg::Loaded).unwrap_or(Svg::NotFound)
- }
+ })
+ .map(Svg::Loaded)
+ .unwrap_or(Svg::NotFound),
svg::Data::Bytes(bytes) => {
match usvg::Tree::from_data(bytes, &usvg::Options::default()) {
Ok(tree) => Svg::Loaded(tree),
@@ -66,6 +67,15 @@ impl Cache {
}
};
+ if let Svg::Loaded(svg) = &mut svg {
+ if svg.has_text_nodes() {
+ let mut font_system =
+ text::font_system().write().expect("Write font system");
+
+ svg.convert_text(font_system.raw().db_mut());
+ }
+ }
+
let _ = self.svgs.insert(handle.id(), svg);
self.svgs.get(&handle.id()).unwrap()
}
diff --git a/wgpu/src/layer.rs b/wgpu/src/layer.rs
index d20dbe66..4ad12a88 100644
--- a/wgpu/src/layer.rs
+++ b/wgpu/src/layer.rs
@@ -1,11 +1,13 @@
//! 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;
@@ -34,6 +36,9 @@ pub struct Layer<'a> {
/// The images of the [`Layer`].
pub images: Vec<Image>,
+
+ /// The custom pipelines of this [`Layer`].
+ pub pipelines: Vec<Pipeline>,
}
impl<'a> Layer<'a> {
@@ -45,6 +50,7 @@ impl<'a> Layer<'a> {
meshes: Vec::new(),
text: Vec::new(),
images: Vec::new(),
+ pipelines: Vec::new(),
}
}
@@ -69,6 +75,7 @@ impl<'a> Layer<'a> {
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()));
@@ -117,13 +124,30 @@ impl<'a> Layer<'a> {
paragraph,
position,
color,
+ clip_bounds,
} => {
let layer = &mut layers[current_layer];
- layer.text.push(Text::Managed {
+ layer.text.push(Text::Paragraph {
paragraph: paragraph.clone(),
position: *position + translation,
color: *color,
+ clip_bounds: *clip_bounds + translation,
+ });
+ }
+ Primitive::Editor {
+ editor,
+ position,
+ color,
+ clip_bounds,
+ } => {
+ let layer = &mut layers[current_layer];
+
+ layer.text.push(Text::Editor {
+ editor: editor.clone(),
+ position: *position + translation,
+ color: *color,
+ clip_bounds: *clip_bounds + translation,
});
}
Primitive::Text {
@@ -136,6 +160,7 @@ impl<'a> Layer<'a> {
horizontal_alignment,
vertical_alignment,
shaping,
+ clip_bounds,
} => {
let layer = &mut layers[current_layer];
@@ -149,6 +174,22 @@ impl<'a> Layer<'a> {
horizontal_alignment: *horizontal_alignment,
vertical_alignment: *vertical_alignment,
shaping: *shaping,
+ clip_bounds: *clip_bounds + translation,
+ }));
+ }
+ graphics::Primitive::RawText(graphics::text::Raw {
+ buffer,
+ position,
+ color,
+ clip_bounds,
+ }) => {
+ let layer = &mut layers[current_layer];
+
+ layer.text.push(Text::Raw(graphics::text::Raw {
+ buffer: buffer.clone(),
+ position: *position + translation,
+ color: *color,
+ clip_bounds: *clip_bounds + translation,
}));
}
Primitive::Quad {
@@ -173,11 +214,16 @@ impl<'a> Layer<'a> {
layer.quads.add(quad, background);
}
- Primitive::Image { handle, bounds } => {
+ 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 + translation,
});
}
@@ -290,6 +336,20 @@ impl<'a> Layer<'a> {
}
}
},
+ primitive::Custom::Pipeline(pipeline) => {
+ let layer = &mut layers[current_layer];
+ let bounds = pipeline.bounds + translation;
+
+ if let Some(clip_bounds) =
+ layer.bounds.intersection(&bounds)
+ {
+ layer.pipelines.push(Pipeline {
+ bounds,
+ viewport: clip_bounds,
+ primitive: pipeline.primitive.clone(),
+ });
+ }
+ }
},
}
}
diff --git a/wgpu/src/layer/image.rs b/wgpu/src/layer/image.rs
index 0de589f8..facbe192 100644
--- a/wgpu/src/layer/image.rs
+++ b/wgpu/src/layer/image.rs
@@ -10,6 +10,9 @@ pub enum Image {
/// 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,
},
diff --git a/wgpu/src/layer/pipeline.rs b/wgpu/src/layer/pipeline.rs
new file mode 100644
index 00000000..6dfe6750
--- /dev/null
+++ b/wgpu/src/layer/pipeline.rs
@@ -0,0 +1,17 @@
+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>,
+}
diff --git a/wgpu/src/layer/text.rs b/wgpu/src/layer/text.rs
index b61615d6..37ee5247 100644
--- a/wgpu/src/layer/text.rs
+++ b/wgpu/src/layer/text.rs
@@ -1,17 +1,33 @@
use crate::core::alignment;
use crate::core::text;
use crate::core::{Color, Font, Pixels, Point, Rectangle};
+use crate::graphics;
+use crate::graphics::text::editor;
use crate::graphics::text::paragraph;
-/// A paragraph of text.
+/// A text primitive.
#[derive(Debug, Clone)]
pub enum Text<'a> {
- Managed {
+ /// A paragraph.
+ #[allow(missing_docs)]
+ Paragraph {
paragraph: paragraph::Weak,
position: Point,
color: Color,
+ clip_bounds: Rectangle,
},
+ /// An editor.
+ #[allow(missing_docs)]
+ Editor {
+ editor: editor::Weak,
+ position: Point,
+ color: Color,
+ clip_bounds: Rectangle,
+ },
+ /// Some cached text.
Cached(Cached<'a>),
+ /// Some raw text.
+ Raw(graphics::text::Raw),
}
#[derive(Debug, Clone)]
@@ -42,4 +58,7 @@ pub struct Cached<'a> {
/// The shaping strategy of the text.
pub shaping: text::Shaping,
+
+ /// The clip bounds of the text.
+ pub clip_bounds: Rectangle,
}
diff --git a/wgpu/src/lib.rs b/wgpu/src/lib.rs
index 6d26723e..424dfeb3 100644
--- a/wgpu/src/lib.rs
+++ b/wgpu/src/lib.rs
@@ -23,7 +23,7 @@
#![forbid(rust_2018_idioms)]
#![deny(
missing_debug_implementations,
- //missing_docs,
+ missing_docs,
unsafe_code,
unused_results,
rustdoc::broken_intra_doc_links
diff --git a/wgpu/src/primitive.rs b/wgpu/src/primitive.rs
index 8dbf3008..fff927ea 100644
--- a/wgpu/src/primitive.rs
+++ b/wgpu/src/primitive.rs
@@ -1,7 +1,13 @@
//! Draw using different graphical primitives.
+pub mod pipeline;
+
+pub use pipeline::Pipeline;
+
use crate::core::Rectangle;
use crate::graphics::{Damage, Mesh};
+use std::fmt::Debug;
+
/// The graphical primitives supported by `iced_wgpu`.
pub type Primitive = crate::graphics::Primitive<Custom>;
@@ -10,12 +16,15 @@ pub type Primitive = crate::graphics::Primitive<Custom>;
pub enum Custom {
/// A mesh primitive.
Mesh(Mesh),
+ /// A custom pipeline primitive.
+ Pipeline(Pipeline),
}
impl Damage for Custom {
fn bounds(&self) -> Rectangle {
match self {
Self::Mesh(mesh) => mesh.bounds(),
+ Self::Pipeline(pipeline) => pipeline.bounds,
}
}
}
diff --git a/wgpu/src/primitive/pipeline.rs b/wgpu/src/primitive/pipeline.rs
new file mode 100644
index 00000000..c8e45458
--- /dev/null
+++ b/wgpu/src/primitive/pipeline.rs
@@ -0,0 +1,116 @@
+//! Draw primitives using custom pipelines.
+use crate::core::{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: crate::core::Renderer {
+ /// Draws a custom pipeline primitive.
+ fn draw_pipeline_primitive(
+ &mut self,
+ bounds: Rectangle,
+ primitive: impl Primitive,
+ );
+}
+
+impl<Theme> Renderer for crate::Renderer<Theme> {
+ 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.")
+ })
+ }
+}
diff --git a/wgpu/src/text.rs b/wgpu/src/text.rs
index 2a530cad..dca09cb8 100644
--- a/wgpu/src/text.rs
+++ b/wgpu/src/text.rs
@@ -2,15 +2,15 @@ use crate::core::alignment;
use crate::core::{Rectangle, Size};
use crate::graphics::color;
use crate::graphics::text::cache::{self, Cache};
-use crate::graphics::text::{FontSystem, Paragraph};
+use crate::graphics::text::{font_system, to_color, Editor, Paragraph};
use crate::layer::Text;
use std::borrow::Cow;
use std::cell::RefCell;
+use std::sync::Arc;
#[allow(missing_debug_implementations)]
pub struct Pipeline {
- font_system: FontSystem,
renderers: Vec<glyphon::TextRenderer>,
atlas: glyphon::TextAtlas,
prepare_layer: usize,
@@ -24,7 +24,6 @@ impl Pipeline {
format: wgpu::TextureFormat,
) -> Self {
Pipeline {
- font_system: FontSystem::new(),
renderers: Vec::new(),
atlas: glyphon::TextAtlas::with_color_mode(
device,
@@ -41,12 +40,11 @@ impl Pipeline {
}
}
- pub fn font_system(&self) -> &FontSystem {
- &self.font_system
- }
-
pub fn load_font(&mut self, bytes: Cow<'static, [u8]>) {
- self.font_system.load_font(bytes);
+ font_system()
+ .write()
+ .expect("Write font system")
+ .load_font(bytes);
self.cache = RefCell::new(Cache::new());
}
@@ -69,21 +67,28 @@ impl Pipeline {
));
}
- let font_system = self.font_system.get_mut();
+ 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::Managed { paragraph, .. } => {
+ 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,
@@ -104,6 +109,7 @@ impl Pipeline {
Some(Allocation::Cache(key))
}
+ Text::Raw(text) => text.buffer.upgrade().map(Allocation::Raw),
})
.collect();
@@ -117,9 +123,13 @@ impl Pipeline {
horizontal_alignment,
vertical_alignment,
color,
+ clip_bounds,
) = match section {
- Text::Managed {
- position, color, ..
+ Text::Paragraph {
+ position,
+ color,
+ clip_bounds,
+ ..
} => {
use crate::core::text::Paragraph as _;
@@ -134,6 +144,29 @@ impl Pipeline {
paragraph.horizontal_alignment(),
paragraph.vertical_alignment(),
*color,
+ *clip_bounds,
+ )
+ }
+ Text::Editor {
+ position,
+ color,
+ clip_bounds,
+ ..
+ } => {
+ 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,
)
}
Text::Cached(text) => {
@@ -152,6 +185,26 @@ impl Pipeline {
text.horizontal_alignment,
text.vertical_alignment,
text.color,
+ text.clip_bounds,
+ )
+ }
+ Text::Raw(text) => {
+ let Some(Allocation::Raw(buffer)) = allocation else {
+ return None;
+ };
+
+ let (width, height) = buffer.size();
+
+ (
+ buffer.as_ref(),
+ Rectangle::new(
+ text.position,
+ Size::new(width, height),
+ ),
+ alignment::Horizontal::Left,
+ alignment::Vertical::Top,
+ text.color,
+ text.clip_bounds,
)
}
};
@@ -174,13 +227,8 @@ impl Pipeline {
alignment::Vertical::Bottom => bounds.y - bounds.height,
};
- let section_bounds = Rectangle {
- x: left,
- y: top,
- ..bounds
- };
-
- let clip_bounds = layer_bounds.intersection(&section_bounds)?;
+ let clip_bounds =
+ layer_bounds.intersection(&(clip_bounds * scale_factor))?;
Some(glyphon::TextArea {
buffer,
@@ -193,16 +241,7 @@ impl Pipeline {
right: (clip_bounds.x + clip_bounds.width) as i32,
bottom: (clip_bounds.y + clip_bounds.height) as i32,
},
- default_color: {
- let [r, g, b, a] = color::pack(color).components();
-
- glyphon::Color::rgba(
- (r * 255.0) as u8,
- (g * 255.0) as u8,
- (b * 255.0) as u8,
- (a * 255.0) as u8,
- )
- },
+ default_color: to_color(color),
})
},
);
diff --git a/wgpu/src/triangle.rs b/wgpu/src/triangle.rs
index 644c9f84..69270a73 100644
--- a/wgpu/src/triangle.rs
+++ b/wgpu/src/triangle.rs
@@ -300,10 +300,15 @@ impl Pipeline {
wgpu::RenderPassColorAttachment {
view: attachment,
resolve_target,
- ops: wgpu::Operations { load, store: true },
+ 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];
diff --git a/wgpu/src/triangle/msaa.rs b/wgpu/src/triangle/msaa.rs
index 320b5b12..14abd20b 100644
--- a/wgpu/src/triangle/msaa.rs
+++ b/wgpu/src/triangle/msaa.rs
@@ -167,10 +167,12 @@ impl Blit {
resolve_target: None,
ops: wgpu::Operations {
load: wgpu::LoadOp::Load,
- store: true,
+ store: wgpu::StoreOp::Store,
},
})],
depth_stencil_attachment: None,
+ timestamp_writes: None,
+ occlusion_query_set: None,
});
render_pass.set_pipeline(&self.pipeline);
diff --git a/wgpu/src/window/compositor.rs b/wgpu/src/window/compositor.rs
index 1ddbe5fe..31cf3819 100644
--- a/wgpu/src/window/compositor.rs
+++ b/wgpu/src/window/compositor.rs
@@ -6,8 +6,6 @@ use crate::graphics::compositor;
use crate::graphics::{Error, Viewport};
use crate::{Backend, Primitive, Renderer, Settings};
-use raw_window_handle::{HasRawDisplayHandle, HasRawWindowHandle};
-
use std::marker::PhantomData;
/// A window graphics backend for iced powered by `wgpu`.
@@ -26,9 +24,9 @@ impl<Theme> Compositor<Theme> {
/// Requests a new [`Compositor`] with the given [`Settings`].
///
/// Returns `None` if no compatible graphics adapter could be found.
- pub async fn request<W: HasRawWindowHandle + HasRawDisplayHandle>(
+ pub async fn request<W: compositor::Window>(
settings: Settings,
- compatible_window: Option<&W>,
+ compatible_window: Option<W>,
) -> Option<Self> {
let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
backends: settings.internal_backend,
@@ -41,14 +39,15 @@ impl<Theme> Compositor<Theme> {
if log::max_level() >= log::LevelFilter::Info {
let available_adapters: Vec<_> = instance
.enumerate_adapters(settings.internal_backend)
- .map(|adapter| adapter.get_info())
+ .iter()
+ .map(wgpu::Adapter::get_info)
.collect();
log::info!("Available adapters: {available_adapters:#?}");
}
#[allow(unsafe_code)]
let compatible_surface = compatible_window
- .and_then(|window| unsafe { instance.create_surface(window).ok() });
+ .and_then(|window| instance.create_surface(window).ok());
let adapter = instance
.request_adapter(&wgpu::RequestAdapterOptions {
@@ -100,14 +99,14 @@ impl<Theme> Compositor<Theme> {
let (device, queue) =
loop {
- let limits = limits.next()?;
+ let required_limits = limits.next()?;
let device = adapter.request_device(
&wgpu::DeviceDescriptor {
label: Some(
"iced_wgpu::window::compositor device descriptor",
),
- features: wgpu::Features::empty(),
- limits,
+ required_features: wgpu::Features::empty(),
+ required_limits,
},
None,
).await.ok();
@@ -136,26 +135,24 @@ impl<Theme> Compositor<Theme> {
/// Creates a [`Compositor`] and its [`Backend`] for the given [`Settings`] and
/// window.
-pub fn new<Theme, W: HasRawWindowHandle + HasRawDisplayHandle>(
+pub fn new<W: compositor::Window, Theme>(
settings: Settings,
- compatible_window: Option<&W>,
-) -> Result<(Compositor<Theme>, Backend), Error> {
+ compatible_window: W,
+) -> Result<Compositor<Theme>, Error> {
let compositor = futures::executor::block_on(Compositor::request(
settings,
- compatible_window,
+ Some(compatible_window),
))
.ok_or(Error::GraphicsAdapterNotFound)?;
- let backend = compositor.create_backend();
-
- Ok((compositor, backend))
+ Ok(compositor)
}
/// Presents the given primitives with the given [`Compositor`] and [`Backend`].
pub fn present<Theme, T: AsRef<str>>(
compositor: &mut Compositor<Theme>,
backend: &mut Backend,
- surface: &mut wgpu::Surface,
+ surface: &mut wgpu::Surface<'static>,
primitives: &[Primitive],
viewport: &Viewport,
background_color: Color,
@@ -178,6 +175,7 @@ pub fn present<Theme, T: AsRef<str>>(
&compositor.queue,
&mut encoder,
Some(background_color),
+ frame.texture.format(),
view,
primitives,
viewport,
@@ -208,32 +206,32 @@ pub fn present<Theme, T: AsRef<str>>(
impl<Theme> graphics::Compositor for Compositor<Theme> {
type Settings = Settings;
type Renderer = Renderer<Theme>;
- type Surface = wgpu::Surface;
+ type Surface = wgpu::Surface<'static>;
- fn new<W: HasRawWindowHandle + HasRawDisplayHandle>(
+ fn new<W: compositor::Window>(
settings: Self::Settings,
- compatible_window: Option<&W>,
- ) -> Result<(Self, Self::Renderer), Error> {
- let (compositor, backend) = new(settings, compatible_window)?;
+ compatible_window: W,
+ ) -> Result<Self, Error> {
+ new(settings, compatible_window)
+ }
- Ok((
- compositor,
- Renderer::new(
- backend,
- settings.default_font,
- settings.default_text_size,
- ),
- ))
+ fn create_renderer(&self) -> Self::Renderer {
+ Renderer::new(
+ self.create_backend(),
+ self.settings.default_font,
+ self.settings.default_text_size,
+ )
}
- fn create_surface<W: HasRawWindowHandle + HasRawDisplayHandle>(
+ fn create_surface<W: compositor::Window>(
&mut self,
- window: &W,
+ window: W,
width: u32,
height: u32,
- ) -> wgpu::Surface {
- #[allow(unsafe_code)]
- let mut surface = unsafe { self.instance.create_surface(window) }
+ ) -> Self::Surface {
+ let mut surface = self
+ .instance
+ .create_surface(window)
.expect("Create surface");
self.configure_surface(&mut surface, width, height);
@@ -257,6 +255,7 @@ impl<Theme> graphics::Compositor for Compositor<Theme> {
height,
alpha_mode: wgpu::CompositeAlphaMode::Auto,
view_formats: vec![],
+ desired_maximum_frame_latency: 2,
},
);
}
@@ -357,6 +356,7 @@ pub fn screenshot<Theme, T: AsRef<str>>(
&compositor.queue,
&mut encoder,
Some(background_color),
+ texture.format(),
&view,
primitives,
viewport,