Merge branch 'master' into remove-vertex-indexing

This commit is contained in:
Héctor Ramón Jiménez 2024-01-19 20:41:52 +01:00
commit 1781068e1c
No known key found for this signature in database
GPG key ID: 7CC46565708259A7
240 changed files with 12776 additions and 3395 deletions

View file

@ -32,7 +32,6 @@ glyphon.workspace = true
guillotiere.workspace = true
log.workspace = true
once_cell.workspace = true
raw-window-handle.workspace = true
wgpu.workspace = true
lyon.workspace = true

View file

@ -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);
}

View file

@ -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);

View file

@ -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();
Point::new(transformed.x, transformed.y)
};
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);
// TODO: Use vectorial text instead of primitive
self.primitives.push(Primitive::Text {
content: text.content,
bounds: Rectangle {
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)
}
};
(position, size, line_height)
};
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`].

View file

@ -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)]

View file

@ -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()
}

View file

@ -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(),
});
}
}
},
}
}

View file

@ -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,
},

View file

@ -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>,
}

View file

@ -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,
}

View file

@ -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

View file

@ -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,
}
}
}

View file

@ -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.")
})
}
}

View file

@ -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),
})
},
);

View file

@ -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];

View file

@ -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);

View file

@ -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)?;
Ok((
compositor,
Renderer::new(
backend,
settings.default_font,
settings.default_text_size,
),
))
compatible_window: W,
) -> Result<Self, Error> {
new(settings, compatible_window)
}
fn create_surface<W: HasRawWindowHandle + HasRawDisplayHandle>(
fn create_renderer(&self) -> Self::Renderer {
Renderer::new(
self.create_backend(),
self.settings.default_font,
self.settings.default_text_size,
)
}
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,