Merge branch 'master' into beacon

This commit is contained in:
Héctor Ramón Jiménez 2025-03-04 19:11:37 +01:00
commit 8bd5de72ea
No known key found for this signature in database
GPG key ID: 7CC46565708259A7
371 changed files with 33138 additions and 12950 deletions

View file

@ -20,9 +20,10 @@ all-features = true
[features]
geometry = ["iced_graphics/geometry", "lyon"]
image = ["iced_graphics/image"]
svg = ["resvg/text"]
svg = ["iced_graphics/svg", "resvg/text"]
web-colors = ["iced_graphics/web-colors"]
webgl = ["wgpu/webgl"]
strict-assertions = []
[dependencies]
iced_graphics.workspace = true
@ -34,7 +35,6 @@ glam.workspace = true
glyphon.workspace = true
guillotiere.workspace = true
log.workspace = true
once_cell.workspace = true
rustc-hash.workspace = true
thiserror.workspace = true
wgpu.workspace = true

View file

@ -6,14 +6,7 @@
`iced_wgpu` is a [`wgpu`] renderer for [`iced_runtime`]. For now, it is the default renderer of Iced on [native platforms].
[`wgpu`] supports most modern graphics backends: Vulkan, Metal, and DX12 (OpenGL and WebGL are still WIP). Additionally, it will support the incoming [WebGPU API].
Currently, `iced_wgpu` supports the following primitives:
- Text, which is rendered using [`wgpu_glyph`]. No shaping at all.
- Quads or rectangles, with rounded borders and a solid background color.
- Clip areas, useful to implement scrollables or hide overflowing content.
- Images and SVG, loaded from memory or the file system.
- Meshes of triangles, useful to draw geometry freely.
[`wgpu`] supports most modern graphics backends: Vulkan, Metal, DX12, OpenGL, and WebGPU.
<p align="center">
<img alt="The native target" src="../docs/graphs/native.png" width="80%">
@ -25,29 +18,3 @@ Currently, `iced_wgpu` supports the following primitives:
[native platforms]: https://github.com/gfx-rs/wgpu#supported-platforms
[WebGPU API]: https://gpuweb.github.io/gpuweb/
[`wgpu_glyph`]: https://github.com/hecrj/wgpu_glyph
## Installation
Add `iced_wgpu` as a dependency in your `Cargo.toml`:
```toml
iced_wgpu = "0.10"
```
__Iced moves fast and the `master` branch can contain breaking changes!__ If
you want to learn about a specific release, check out [the release list].
[the release list]: https://github.com/iced-rs/iced/releases
## Current limitations
The current implementation is quite naive; it uses:
- A different pipeline/shader for each primitive
- A very simplistic layer model: every `Clip` primitive will generate new layers
- _Many_ render passes instead of preparing everything upfront
- A glyph cache that is trimmed incorrectly when there are multiple layers (a [`glyph_brush`] limitation)
Some of these issues are already being worked on! If you want to help, [get in touch!]
[get in touch!]: ../CONTRIBUTING.md
[`glyph_brush`]: https://github.com/alexheretic/glyph-brush

View file

@ -108,12 +108,14 @@ pub fn convert(
layout: Some(&pipeline_layout),
vertex: wgpu::VertexState {
module: &shader,
entry_point: "vs_main",
entry_point: Some("vs_main"),
buffers: &[],
compilation_options: wgpu::PipelineCompilationOptions::default(
),
},
fragment: Some(wgpu::FragmentState {
module: &shader,
entry_point: "fs_main",
entry_point: Some("fs_main"),
targets: &[Some(wgpu::ColorTargetState {
format,
blend: Some(wgpu::BlendState {
@ -130,6 +132,8 @@ pub fn convert(
}),
write_mask: wgpu::ColorWrites::ALL,
})],
compilation_options: wgpu::PipelineCompilationOptions::default(
),
}),
primitive: wgpu::PrimitiveState {
topology: wgpu::PrimitiveTopology::TriangleList,
@ -139,6 +143,7 @@ pub fn convert(
depth_stencil: None,
multisample: wgpu::MultisampleState::default(),
multiview: None,
cache: None,
});
let texture = device.create_texture(&wgpu::TextureDescriptor {

View file

@ -1,7 +1,7 @@
//! Build and draw geometry.
use crate::core::text::LineHeight;
use crate::core::{
Pixels, Point, Radians, Rectangle, Size, Transformation, Vector,
self, Pixels, Point, Radians, Rectangle, Size, Svg, Transformation, Vector,
};
use crate::graphics::cache::{self, Cached};
use crate::graphics::color;
@ -11,7 +11,7 @@ use crate::graphics::geometry::{
};
use crate::graphics::gradient::{self, Gradient};
use crate::graphics::mesh::{self, Mesh};
use crate::graphics::{self, Text};
use crate::graphics::{Image, Text};
use crate::text;
use crate::triangle;
@ -19,16 +19,22 @@ use lyon::geom::euclid;
use lyon::tessellation;
use std::borrow::Cow;
use std::sync::Arc;
#[derive(Debug)]
pub enum Geometry {
Live { meshes: Vec<Mesh>, text: Vec<Text> },
Live {
meshes: Vec<Mesh>,
images: Vec<Image>,
text: Vec<Text>,
},
Cached(Cache),
}
#[derive(Debug, Clone)]
#[derive(Debug, Clone, Default)]
pub struct Cache {
pub meshes: Option<triangle::Cache>,
pub images: Option<Arc<[Image]>>,
pub text: Option<text::Cache>,
}
@ -45,7 +51,17 @@ impl Cached for Geometry {
previous: Option<Self::Cache>,
) -> Self::Cache {
match self {
Self::Live { meshes, text } => {
Self::Live {
meshes,
images,
text,
} => {
let images = if images.is_empty() {
None
} else {
Some(Arc::from(images))
};
if let Some(mut previous) = previous {
if let Some(cache) = &mut previous.meshes {
cache.update(meshes);
@ -59,10 +75,13 @@ impl Cached for Geometry {
previous.text = text::Cache::new(group, text);
}
previous.images = images;
previous
} else {
Cache {
meshes: triangle::Cache::new(meshes),
images,
text: text::Cache::new(group, text),
}
}
@ -78,6 +97,7 @@ pub struct Frame {
clip_bounds: Rectangle,
buffers: BufferStack,
meshes: Vec<Mesh>,
images: Vec<Image>,
text: Vec<Text>,
transforms: Transforms,
fill_tessellator: tessellation::FillTessellator,
@ -96,6 +116,7 @@ impl Frame {
clip_bounds: bounds,
buffers: BufferStack::new(),
meshes: Vec::new(),
images: Vec::new(),
text: Vec::new(),
transforms: Transforms {
previous: Vec::new(),
@ -232,6 +253,44 @@ impl geometry::frame::Backend for Frame {
.expect("Stroke path");
}
fn stroke_rectangle<'a>(
&mut self,
top_left: Point,
size: Size,
stroke: impl Into<Stroke<'a>>,
) {
let stroke = stroke.into();
let mut buffer = self
.buffers
.get_stroke(&self.transforms.current.transform_style(stroke.style));
let top_left = self
.transforms
.current
.0
.transform_point(lyon::math::Point::new(top_left.x, top_left.y));
let size =
self.transforms.current.0.transform_vector(
lyon::math::Vector::new(size.width, size.height),
);
let mut options = tessellation::StrokeOptions::default();
options.line_width = stroke.width;
options.start_cap = into_line_cap(stroke.line_cap);
options.end_cap = into_line_cap(stroke.line_cap);
options.line_join = into_line_join(stroke.line_join);
self.stroke_tessellator
.tessellate_rectangle(
&lyon::math::Box2D::new(top_left, top_left + size),
&options,
buffer.as_mut(),
)
.expect("Stroke rectangle");
}
fn fill_text(&mut self, text: impl Into<geometry::Text>) {
let text = text.into();
@ -270,7 +329,7 @@ impl geometry::frame::Backend for Frame {
height: f32::INFINITY,
};
self.text.push(graphics::Text::Cached {
self.text.push(Text::Cached {
content: text.content,
bounds,
color: text.color,
@ -335,10 +394,12 @@ impl geometry::frame::Backend for Frame {
Frame::with_clip(clip_bounds)
}
fn paste(&mut self, frame: Frame, _at: Point) {
fn paste(&mut self, frame: Frame) {
self.meshes.extend(frame.meshes);
self.meshes
.extend(frame.buffers.into_meshes(frame.clip_bounds));
self.images.extend(frame.images);
self.text.extend(frame.text);
}
@ -348,9 +409,32 @@ impl geometry::frame::Backend for Frame {
Geometry::Live {
meshes: self.meshes,
images: self.images,
text: self.text,
}
}
fn draw_image(&mut self, bounds: Rectangle, image: impl Into<core::Image>) {
let mut image = image.into();
let (bounds, external_rotation) =
self.transforms.current.transform_rectangle(bounds);
image.rotation += external_rotation;
self.images.push(Image::Raster(image, bounds));
}
fn draw_svg(&mut self, bounds: Rectangle, svg: impl Into<Svg>) {
let mut svg = svg.into();
let (bounds, external_rotation) =
self.transforms.current.transform_rectangle(bounds);
svg.rotation += external_rotation;
self.images.push(Image::Vector(svg, bounds));
}
}
enum Buffer {
@ -518,6 +602,21 @@ impl Transform {
gradient
}
fn transform_rectangle(
&self,
rectangle: Rectangle,
) -> (Rectangle, Radians) {
let top_left = self.transform_point(rectangle.position());
let top_right = self.transform_point(
rectangle.position() + Vector::new(rectangle.width, 0.0),
);
let bottom_left = self.transform_point(
rectangle.position() + Vector::new(0.0, rectangle.height),
);
Rectangle::with_vertices(top_left, top_right, bottom_left)
}
}
struct GradientVertex2DBuilder {
gradient: gradient::Packed,
@ -614,7 +713,7 @@ fn into_fill_rule(rule: fill::Rule) -> lyon::tessellation::FillRule {
pub(super) fn dashed(path: &Path, line_dash: LineDash<'_>) -> Path {
use lyon::algorithms::walk::{
walk_along_path, RepeatedPattern, WalkerEvent,
RepeatedPattern, WalkerEvent, walk_along_path,
};
use lyon::path::iterator::PathIterator;

View file

@ -9,8 +9,8 @@ mod raster;
#[cfg(feature = "svg")]
mod vector;
use crate::core::{Rectangle, Size, Transformation};
use crate::Buffer;
use crate::core::{Rectangle, Size, Transformation};
use bytemuck::{Pod, Zeroable};
@ -128,7 +128,7 @@ impl Pipeline {
layout: Some(&layout),
vertex: wgpu::VertexState {
module: &shader,
entry_point: "vs_main",
entry_point: Some("vs_main"),
buffers: &[wgpu::VertexBufferLayout {
array_stride: mem::size_of::<Instance>() as u64,
step_mode: wgpu::VertexStepMode::Instance,
@ -149,12 +149,16 @@ impl Pipeline {
6 => Float32x2,
// Layer
7 => Sint32,
// Snap
8 => Uint32,
),
}],
compilation_options:
wgpu::PipelineCompilationOptions::default(),
},
fragment: Some(wgpu::FragmentState {
module: &shader,
entry_point: "fs_main",
entry_point: Some("fs_main"),
targets: &[Some(wgpu::ColorTargetState {
format,
blend: Some(wgpu::BlendState {
@ -171,6 +175,8 @@ impl Pipeline {
}),
write_mask: wgpu::ColorWrites::ALL,
})],
compilation_options:
wgpu::PipelineCompilationOptions::default(),
}),
primitive: wgpu::PrimitiveState {
topology: wgpu::PrimitiveTopology::TriangleList,
@ -184,6 +190,7 @@ impl Pipeline {
alpha_to_coverage_enabled: false,
},
multiview: None,
cache: None,
});
Pipeline {
@ -212,31 +219,24 @@ impl Pipeline {
transformation: Transformation,
scale: f32,
) {
let transformation = transformation * Transformation::scale(scale);
let nearest_instances: &mut Vec<Instance> = &mut Vec::new();
let linear_instances: &mut Vec<Instance> = &mut Vec::new();
for image in images {
match &image {
#[cfg(feature = "image")]
Image::Raster {
handle,
filter_method,
bounds,
rotation,
opacity,
} => {
Image::Raster(image, bounds) => {
if let Some(atlas_entry) =
cache.upload_raster(device, encoder, handle)
cache.upload_raster(device, encoder, &image.handle)
{
add_instances(
[bounds.x, bounds.y],
[bounds.width, bounds.height],
f32::from(*rotation),
*opacity,
f32::from(image.rotation),
image.opacity,
image.snap,
atlas_entry,
match filter_method {
match image.filter_method {
crate::core::image::FilterMethod::Nearest => {
nearest_instances
}
@ -251,23 +251,23 @@ impl Pipeline {
Image::Raster { .. } => {}
#[cfg(feature = "svg")]
Image::Vector {
handle,
color,
bounds,
rotation,
opacity,
} => {
Image::Vector(svg, bounds) => {
let size = [bounds.width, bounds.height];
if let Some(atlas_entry) = cache.upload_vector(
device, encoder, handle, *color, size, scale,
device,
encoder,
&svg.handle,
svg.color,
size,
scale,
) {
add_instances(
[bounds.x, bounds.y],
size,
f32::from(*rotation),
*opacity,
f32::from(svg.rotation),
svg.opacity,
true,
atlas_entry,
nearest_instances,
);
@ -300,6 +300,7 @@ impl Pipeline {
nearest_instances,
linear_instances,
transformation,
scale,
);
self.prepare_layer += 1;
@ -375,9 +376,12 @@ impl Layer {
nearest_instances: &[Instance],
linear_instances: &[Instance],
transformation: Transformation,
scale_factor: f32,
) {
let uniforms = Uniforms {
transform: transformation.into(),
scale_factor,
_padding: [0.0; 3],
};
let bytes = bytemuck::bytes_of(&uniforms);
@ -492,6 +496,7 @@ struct Instance {
_position_in_atlas: [f32; 2],
_size_in_atlas: [f32; 2],
_layer: u32,
_snap: u32,
}
impl Instance {
@ -502,6 +507,10 @@ impl Instance {
#[derive(Debug, Clone, Copy, Zeroable, Pod)]
struct Uniforms {
transform: [f32; 16],
scale_factor: f32,
// Uniforms must be aligned to their largest member,
// this uses a mat4x4<f32> which aligns to 16, so align to that
_padding: [f32; 3],
}
fn add_instances(
@ -509,6 +518,7 @@ fn add_instances(
image_size: [f32; 2],
rotation: f32,
opacity: f32,
snap: bool,
entry: &atlas::Entry,
instances: &mut Vec<Instance>,
) {
@ -525,6 +535,7 @@ fn add_instances(
image_size,
rotation,
opacity,
snap,
allocation,
instances,
);
@ -554,8 +565,8 @@ fn add_instances(
];
add_instance(
position, center, size, rotation, opacity, allocation,
instances,
position, center, size, rotation, opacity, snap,
allocation, instances,
);
}
}
@ -569,6 +580,7 @@ fn add_instance(
size: [f32; 2],
rotation: f32,
opacity: f32,
snap: bool,
allocation: &atlas::Allocation,
instances: &mut Vec<Instance>,
) {
@ -591,6 +603,7 @@ fn add_instance(
(height as f32 - 1.0) / atlas::SIZE as f32,
],
_layer: layer as u32,
_snap: snap as u32,
};
instances.push(instance);

View file

@ -1,5 +1,5 @@
use crate::core::image;
use crate::core::Size;
use crate::core::image;
use crate::graphics;
use crate::graphics::image::image_rs;
use crate::image::atlas::{self, Atlas};

View file

@ -1,12 +1,12 @@
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::{self, TreeTextToPath};
use resvg::usvg;
use rustc_hash::{FxHashMap, FxHashSet};
use std::fs;
use std::sync::Arc;
/// Entry in cache corresponding to an svg handle
pub enum Svg {
@ -21,7 +21,7 @@ impl Svg {
pub fn viewport_dimensions(&self) -> Size<u32> {
match self {
Svg::Loaded(tree) => {
let size = tree.size;
let size = tree.size();
Size::new(size.width() as u32, size.height() as u32)
}
@ -38,6 +38,7 @@ pub struct Cache {
svg_hits: FxHashSet<u64>,
rasterized_hits: FxHashSet<(u64, u32, u32, ColorFilter)>,
should_trim: bool,
fontdb: Option<Arc<usvg::fontdb::Database>>,
}
type ColorFilter = Option<[u8; 4]>;
@ -45,38 +46,43 @@ type ColorFilter = Option<[u8; 4]>;
impl Cache {
/// Load svg
pub fn load(&mut self, handle: &svg::Handle) -> &Svg {
use usvg::TreeParsing;
if self.svgs.contains_key(&handle.id()) {
return self.svgs.get(&handle.id()).unwrap();
}
let mut svg = match handle.data() {
// TODO: Reuse `cosmic-text` font database
if self.fontdb.is_none() {
let mut fontdb = usvg::fontdb::Database::new();
fontdb.load_system_fonts();
self.fontdb = Some(Arc::new(fontdb));
}
let options = usvg::Options {
fontdb: self
.fontdb
.as_ref()
.expect("fontdb must be initialized")
.clone(),
..usvg::Options::default()
};
let 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()
usvg::Tree::from_str(&contents, &options).ok()
})
.map(Svg::Loaded)
.unwrap_or(Svg::NotFound),
svg::Data::Bytes(bytes) => {
match usvg::Tree::from_data(bytes, &usvg::Options::default()) {
match usvg::Tree::from_data(bytes, &options) {
Ok(tree) => Svg::Loaded(tree),
Err(_) => Svg::NotFound,
}
}
};
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());
}
}
self.should_trim = true;
let _ = self.svgs.insert(handle.id(), svg);
@ -127,7 +133,7 @@ impl Cache {
// It would be cool to be able to smooth resize the `svg` example.
let mut img = tiny_skia::Pixmap::new(width, height)?;
let tree_size = tree.size.to_int_size();
let tree_size = tree.size().to_int_size();
let target_size = if width > height {
tree_size.scale_to_width(width)
@ -147,8 +153,7 @@ impl Cache {
tiny_skia::Transform::default()
};
resvg::Tree::from_usvg(tree)
.render(transform, &mut img.as_mut());
resvg::render(tree, transform, &mut img.as_mut());
let mut rgba = img.take();

View file

@ -1,11 +1,11 @@
use crate::core::{
renderer, Background, Color, Point, Radians, Rectangle, Transformation,
self, Background, Color, Point, Rectangle, Svg, Transformation, renderer,
};
use crate::graphics;
use crate::graphics::Mesh;
use crate::graphics::color;
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};
@ -20,8 +20,8 @@ pub struct Layer {
pub quads: quad::Batch,
pub triangles: triangle::Batch,
pub primitives: primitive::Batch,
pub text: text::Batch,
pub images: image::Batch,
pub text: text::Batch,
pending_meshes: Vec<Mesh>,
pending_text: Vec<Text>,
}
@ -112,42 +112,35 @@ impl Layer {
self.pending_text.push(text);
}
pub fn draw_image(
pub fn draw_image(&mut self, image: Image, transformation: Transformation) {
match image {
Image::Raster(image, bounds) => {
self.draw_raster(image, bounds, transformation);
}
Image::Vector(svg, bounds) => {
self.draw_svg(svg, bounds, transformation);
}
}
}
pub fn draw_raster(
&mut self,
handle: crate::core::image::Handle,
filter_method: crate::core::image::FilterMethod,
image: core::Image,
bounds: Rectangle,
transformation: Transformation,
rotation: Radians,
opacity: f32,
) {
let image = Image::Raster {
handle,
filter_method,
bounds: bounds * transformation,
rotation,
opacity,
};
let image = Image::Raster(image, bounds * transformation);
self.images.push(image);
}
pub fn draw_svg(
&mut self,
handle: crate::core::svg::Handle,
color: Option<Color>,
svg: Svg,
bounds: Rectangle,
transformation: Transformation,
rotation: Radians,
opacity: f32,
) {
let svg = Image::Vector {
handle,
color,
bounds: bounds * transformation,
rotation,
opacity,
};
let svg = Image::Vector(svg, bounds * transformation);
self.images.push(svg);
}

View file

@ -63,8 +63,8 @@ pub use geometry::Geometry;
use crate::core::{
Background, Color, Font, Pixels, Point, Rectangle, Transformation,
};
use crate::graphics::text::{Editor, Paragraph};
use crate::graphics::Viewport;
use crate::graphics::text::{Editor, Paragraph};
/// A [`wgpu`] graphics renderer for [`iced`].
///
@ -142,7 +142,19 @@ impl Renderer {
self.text_viewport.update(queue, viewport.physical_size());
let physical_bounds = Rectangle::<f32>::from(Rectangle::with_size(
viewport.physical_size(),
));
for layer in self.layers.iter_mut() {
if physical_bounds
.intersection(&(layer.bounds * scale_factor))
.and_then(Rectangle::snap)
.is_none()
{
continue;
}
if !layer.quads.is_empty() {
engine.quad_pipeline.prepare(
device,
@ -179,19 +191,6 @@ impl Renderer {
}
}
if !layer.text.is_empty() {
engine.text_pipeline.prepare(
device,
queue,
&self.text_viewport,
encoder,
&mut self.text_storage,
&layer.text,
layer.bounds,
Transformation::scale(scale_factor),
);
}
#[cfg(any(feature = "svg", feature = "image"))]
if !layer.images.is_empty() {
engine.image_pipeline.prepare(
@ -204,6 +203,19 @@ impl Renderer {
scale_factor,
);
}
if !layer.text.is_empty() {
engine.text_pipeline.prepare(
device,
queue,
&self.text_viewport,
encoder,
&mut self.text_storage,
&layer.text,
layer.bounds,
Transformation::scale(scale_factor),
);
}
}
}
@ -266,7 +278,7 @@ impl Renderer {
for layer in self.layers.iter() {
let Some(physical_bounds) =
physical_bounds.intersection(&(layer.bounds * scale))
physical_bounds.intersection(&(layer.bounds * scale_factor))
else {
continue;
};
@ -356,17 +368,6 @@ impl Renderer {
));
}
if !layer.text.is_empty() {
text_layer += engine.text_pipeline.render(
&self.text_viewport,
&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(
@ -378,6 +379,17 @@ impl Renderer {
image_layer += 1;
}
if !layer.text.is_empty() {
text_layer += engine.text_pipeline.render(
&self.text_viewport,
&self.text_storage,
text_layer,
&layer.text,
scissor_rect,
&mut render_pass,
);
}
}
let _ = ManuallyDrop::into_inner(render_pass);
@ -481,23 +493,9 @@ impl core::image::Renderer for Renderer {
self.image_cache.borrow_mut().measure_image(handle)
}
fn draw_image(
&mut self,
handle: Self::Handle,
filter_method: core::image::FilterMethod,
bounds: Rectangle,
rotation: core::Radians,
opacity: f32,
) {
fn draw_image(&mut self, image: core::Image, bounds: Rectangle) {
let (layer, transformation) = self.layers.current_mut();
layer.draw_image(
handle,
filter_method,
bounds,
transformation,
rotation,
opacity,
);
layer.draw_raster(image, bounds, transformation);
}
}
@ -507,28 +505,24 @@ impl core::svg::Renderer for Renderer {
self.image_cache.borrow_mut().measure_svg(handle)
}
fn draw_svg(
&mut self,
handle: core::svg::Handle,
color_filter: Option<Color>,
bounds: Rectangle,
rotation: core::Radians,
opacity: f32,
) {
fn draw_svg(&mut self, svg: core::Svg, bounds: Rectangle) {
let (layer, transformation) = self.layers.current_mut();
layer.draw_svg(
handle,
color_filter,
bounds,
transformation,
rotation,
opacity,
);
layer.draw_svg(svg, bounds, transformation);
}
}
impl graphics::mesh::Renderer for Renderer {
fn draw_mesh(&mut self, mesh: graphics::Mesh) {
debug_assert!(
!mesh.indices().is_empty(),
"Mesh must not have empty indices"
);
debug_assert!(
mesh.indices().len() % 3 == 0,
"Mesh indices length must be a multiple of 3"
);
let (layer, transformation) = self.layers.current_mut();
layer.draw_mesh(mesh, transformation);
}
@ -547,8 +541,17 @@ impl graphics::geometry::Renderer for Renderer {
let (layer, transformation) = self.layers.current_mut();
match geometry {
Geometry::Live { meshes, text } => {
Geometry::Live {
meshes,
images,
text,
} => {
layer.draw_mesh_group(meshes, transformation);
for image in images {
layer.draw_image(image, transformation);
}
layer.draw_text_group(text, transformation);
}
Geometry::Cached(cache) => {
@ -556,6 +559,12 @@ impl graphics::geometry::Renderer for Renderer {
layer.draw_mesh_cache(meshes, transformation);
}
if let Some(images) = cache.images {
for image in images.iter().cloned() {
layer.draw_image(image, transformation);
}
}
if let Some(text) = cache.text {
layer.draw_text_cache(text, transformation);
}

View file

@ -1,6 +1,6 @@
use crate::Buffer;
use crate::graphics::gradient;
use crate::quad::{self, Quad};
use crate::Buffer;
use bytemuck::{Pod, Zeroable};
use std::ops::Range;
@ -124,7 +124,7 @@ impl Pipeline {
layout: Some(&layout),
vertex: wgpu::VertexState {
module: &shader,
entry_point: "gradient_vs_main",
entry_point: Some("gradient_vs_main"),
buffers: &[wgpu::VertexBufferLayout {
array_stride: std::mem::size_of::<Gradient>()
as u64,
@ -152,11 +152,15 @@ impl Pipeline {
9 => Float32
),
}],
compilation_options:
wgpu::PipelineCompilationOptions::default(),
},
fragment: Some(wgpu::FragmentState {
module: &shader,
entry_point: "gradient_fs_main",
entry_point: Some("gradient_fs_main"),
targets: &quad::color_target_state(format),
compilation_options:
wgpu::PipelineCompilationOptions::default(),
}),
primitive: wgpu::PrimitiveState {
topology: wgpu::PrimitiveTopology::TriangleList,
@ -170,6 +174,7 @@ impl Pipeline {
alpha_to_coverage_enabled: false,
},
multiview: None,
cache: None,
},
);
@ -188,9 +193,6 @@ impl Pipeline {
layer: &'a Layer,
range: Range<usize>,
) {
#[cfg(feature = "tracing")]
let _ = tracing::info_span!("Wgpu::Quad::Gradient", "DRAW").entered();
#[cfg(not(target_arch = "wasm32"))]
{
render_pass.set_pipeline(&self.pipeline);

View file

@ -1,6 +1,6 @@
use crate::Buffer;
use crate::graphics::color;
use crate::quad::{self, Quad};
use crate::Buffer;
use bytemuck::{Pod, Zeroable};
use std::ops::Range;
@ -89,7 +89,7 @@ impl Pipeline {
layout: Some(&layout),
vertex: wgpu::VertexState {
module: &shader,
entry_point: "solid_vs_main",
entry_point: Some("solid_vs_main"),
buffers: &[wgpu::VertexBufferLayout {
array_stride: std::mem::size_of::<Solid>() as u64,
step_mode: wgpu::VertexStepMode::Instance,
@ -114,11 +114,15 @@ impl Pipeline {
8 => Float32,
),
}],
compilation_options:
wgpu::PipelineCompilationOptions::default(),
},
fragment: Some(wgpu::FragmentState {
module: &shader,
entry_point: "solid_fs_main",
entry_point: Some("solid_fs_main"),
targets: &quad::color_target_state(format),
compilation_options:
wgpu::PipelineCompilationOptions::default(),
}),
primitive: wgpu::PrimitiveState {
topology: wgpu::PrimitiveTopology::TriangleList,
@ -132,6 +136,7 @@ impl Pipeline {
alpha_to_coverage_enabled: false,
},
multiview: None,
cache: None,
});
Self { pipeline }
@ -144,9 +149,6 @@ impl Pipeline {
layer: &'a Layer,
range: Range<usize>,
) {
#[cfg(feature = "tracing")]
let _ = tracing::info_span!("Wgpu::Quad::Solid", "DRAW").entered();
render_pass.set_pipeline(&self.pipeline);
render_pass.set_bind_group(0, constants, &[]);
render_pass.set_vertex_buffer(0, layer.instances.slice(..));

View file

@ -1,5 +1,6 @@
struct Globals {
transform: mat4x4<f32>,
scale_factor: f32,
}
@group(0) @binding(0) var<uniform> globals: Globals;
@ -16,6 +17,7 @@ struct VertexInput {
@location(5) atlas_pos: vec2<f32>,
@location(6) atlas_scale: vec2<f32>,
@location(7) layer: i32,
@location(8) snap: u32,
}
struct VertexOutput {
@ -38,7 +40,7 @@ fn vs_main(input: VertexInput) -> VertexOutput {
out.opacity = input.opacity;
// Calculate the vertex position and move the center to the origin
v_pos = round(input.pos) + v_pos * input.scale - input.center;
v_pos = input.pos + v_pos * input.scale - input.center;
// Apply the rotation around the center of the image
let cos_rot = cos(input.rotation);
@ -51,7 +53,13 @@ fn vs_main(input: VertexInput) -> VertexOutput {
);
// Calculate the final position of the vertex
out.position = globals.transform * (vec4<f32>(input.center, 0.0, 0.0) + rotate * vec4<f32>(v_pos, 0.0, 1.0));
out.position = vec4(vec2(globals.scale_factor), 1.0, 1.0) * (vec4<f32>(input.center, 0.0, 0.0) + rotate * vec4<f32>(v_pos, 0.0, 1.0));
if bool(input.snap) {
out.position = round(out.position);
}
out.position = globals.transform * out.position;
return out;
}

View file

@ -22,14 +22,14 @@ fn rounded_box_sdf(to_center: vec2<f32>, size: vec2<f32>, radius: f32) -> f32 {
return length(max(abs(to_center) - size + vec2<f32>(radius, radius), vec2<f32>(0.0, 0.0))) - radius;
}
// Based on the fragment position and the center of the quad, select one of the 4 radi.
// Based on the fragment position and the center of the quad, select one of the 4 radii.
// Order matches CSS border radius attribute:
// radi.x = top-left, radi.y = top-right, radi.z = bottom-right, radi.w = bottom-left
fn select_border_radius(radi: vec4<f32>, position: vec2<f32>, center: vec2<f32>) -> f32 {
var rx = radi.x;
var ry = radi.y;
rx = select(radi.x, radi.y, position.x > center.x);
ry = select(radi.w, radi.z, position.x > center.x);
// radii.x = top-left, radii.y = top-right, radii.z = bottom-right, radii.w = bottom-left
fn select_border_radius(radii: vec4<f32>, position: vec2<f32>, center: vec2<f32>) -> f32 {
var rx = radii.x;
var ry = radii.y;
rx = select(radii.x, radii.y, position.x > center.x);
ry = select(radii.w, radii.z, position.x > center.x);
rx = select(rx, ry, position.y > center.y);
return rx;
}

View file

@ -30,6 +30,15 @@ fn solid_vs_main(input: SolidVertexInput) -> SolidVertexOutput {
var pos: vec2<f32> = (input.pos + min(input.shadow_offset, vec2<f32>(0.0, 0.0)) - input.shadow_blur_radius) * globals.scale;
var scale: vec2<f32> = (input.scale + vec2<f32>(abs(input.shadow_offset.x), abs(input.shadow_offset.y)) + input.shadow_blur_radius * 2.0) * globals.scale;
var snap: vec2<f32> = vec2<f32>(0.0, 0.0);
if input.scale.x == 1.0 {
snap.x = round(pos.x) - pos.x;
}
if input.scale.y == 1.0 {
snap.y = round(pos.y) - pos.y;
}
var min_border_radius = min(input.scale.x, input.scale.y) * 0.5;
var border_radius: vec4<f32> = vec4<f32>(
@ -43,13 +52,13 @@ fn solid_vs_main(input: SolidVertexInput) -> SolidVertexOutput {
vec4<f32>(scale.x + 1.0, 0.0, 0.0, 0.0),
vec4<f32>(0.0, scale.y + 1.0, 0.0, 0.0),
vec4<f32>(0.0, 0.0, 1.0, 0.0),
vec4<f32>(pos - vec2<f32>(0.5, 0.5), 0.0, 1.0)
vec4<f32>(pos - vec2<f32>(0.5, 0.5) + snap, 0.0, 1.0)
);
out.position = globals.transform * transform * vec4<f32>(vertex_position(input.vertex_index), 0.0, 1.0);
out.color = input.color;
out.border_color = input.border_color;
out.pos = input.pos * globals.scale;
out.pos = input.pos * globals.scale + snap;
out.scale = input.scale * globals.scale;
out.border_radius = border_radius * globals.scale;
out.border_width = input.border_width * globals.scale;

View file

@ -3,13 +3,12 @@ use crate::core::{Rectangle, Size, Transformation};
use crate::graphics::cache;
use crate::graphics::color;
use crate::graphics::text::cache::{self as text_cache, Cache as BufferCache};
use crate::graphics::text::{font_system, to_color, Editor, Paragraph};
use crate::graphics::text::{Editor, Paragraph, font_system, to_color};
use rustc_hash::FxHashMap;
use std::collections::hash_map;
use std::rc::{self, Rc};
use std::sync::atomic::{self, AtomicU64};
use std::sync::Arc;
use std::sync::{self, Arc};
pub use crate::graphics::Text;
@ -37,7 +36,7 @@ pub enum Item {
pub struct Cache {
id: Id,
group: cache::Group,
text: Rc<[Text]>,
text: Arc<[Text]>,
version: usize,
}
@ -55,7 +54,7 @@ impl Cache {
Some(Self {
id: Id(NEXT_ID.fetch_add(1, atomic::Ordering::Relaxed)),
group,
text: Rc::from(text),
text: Arc::from(text),
version: 0,
})
}
@ -65,7 +64,7 @@ impl Cache {
return;
}
self.text = Rc::from(text);
self.text = Arc::from(text);
self.version += 1;
}
}
@ -76,8 +75,8 @@ struct Upload {
transformation: Transformation,
version: usize,
group_version: usize,
text: rc::Weak<[Text]>,
_atlas: rc::Weak<()>,
text: sync::Weak<[Text]>,
_atlas: sync::Weak<()>,
}
#[derive(Default)]
@ -90,7 +89,7 @@ struct Group {
atlas: glyphon::TextAtlas,
version: usize,
should_trim: bool,
handle: Rc<()>, // Keeps track of active uploads
handle: Arc<()>, // Keeps track of active uploads
}
impl Storage {
@ -136,7 +135,7 @@ impl Storage {
),
version: 0,
should_trim: false,
handle: Rc::new(()),
handle: Arc::new(()),
}
});
@ -167,7 +166,7 @@ impl Storage {
group.should_trim =
group.should_trim || upload.version != cache.version;
upload.text = Rc::downgrade(&cache.text);
upload.text = Arc::downgrade(&cache.text);
upload.version = cache.version;
upload.group_version = group.version;
upload.transformation = new_transformation;
@ -206,8 +205,8 @@ impl Storage {
transformation: new_transformation,
version: 0,
group_version: group.version,
text: Rc::downgrade(&cache.text),
_atlas: Rc::downgrade(&group.handle),
text: Arc::downgrade(&cache.text),
_atlas: Arc::downgrade(&group.handle),
});
group.should_trim = cache.group.is_singleton();
@ -226,7 +225,7 @@ impl Storage {
.retain(|_id, upload| upload.text.strong_count() > 0);
self.groups.retain(|id, group| {
let active_uploads = Rc::weak_count(&group.handle);
let active_uploads = Arc::weak_count(&group.handle);
if active_uploads == 0 {
log::debug!("Dropping text atlas: {id:?}");
@ -585,7 +584,13 @@ fn prepare(
(
buffer.as_ref(),
Rectangle::new(raw.position, Size::new(width, height)),
Rectangle::new(
raw.position,
Size::new(
width.unwrap_or(layer_bounds.width),
height.unwrap_or(layer_bounds.height),
),
),
alignment::Horizontal::Left,
alignment::Vertical::Top,
raw.color,

View file

@ -1,15 +1,15 @@
//! Draw meshes of triangles.
mod msaa;
use crate::core::{Rectangle, Size, Transformation};
use crate::graphics::mesh::{self, Mesh};
use crate::graphics::Antialiasing;
use crate::Buffer;
use crate::core::{Rectangle, Size, Transformation};
use crate::graphics::Antialiasing;
use crate::graphics::mesh::{self, Mesh};
use rustc_hash::FxHashMap;
use std::collections::hash_map;
use std::rc::{self, Rc};
use std::sync::atomic::{self, AtomicU64};
use std::sync::{self, Arc};
const INITIAL_INDEX_COUNT: usize = 1_000;
const INITIAL_VERTEX_COUNT: usize = 1_000;
@ -31,7 +31,7 @@ pub enum Item {
#[derive(Debug, Clone)]
pub struct Cache {
id: Id,
batch: Rc<[Mesh]>,
batch: Arc<[Mesh]>,
version: usize,
}
@ -48,13 +48,13 @@ impl Cache {
Some(Self {
id: Id(NEXT_ID.fetch_add(1, atomic::Ordering::Relaxed)),
batch: Rc::from(meshes),
batch: Arc::from(meshes),
version: 0,
})
}
pub fn update(&mut self, meshes: Vec<Mesh>) {
self.batch = Rc::from(meshes);
self.batch = Arc::from(meshes);
self.version += 1;
}
}
@ -64,7 +64,7 @@ struct Upload {
layer: Layer,
transformation: Transformation,
version: usize,
batch: rc::Weak<[Mesh]>,
batch: sync::Weak<[Mesh]>,
}
#[derive(Debug, Default)]
@ -113,7 +113,7 @@ impl Storage {
new_transformation,
);
upload.batch = Rc::downgrade(&cache.batch);
upload.batch = Arc::downgrade(&cache.batch);
upload.version = cache.version;
upload.transformation = new_transformation;
}
@ -135,7 +135,7 @@ impl Storage {
layer,
transformation: new_transformation,
version: 0,
batch: Rc::downgrade(&cache.batch),
batch: Arc::downgrade(&cache.batch),
});
log::debug!(
@ -505,6 +505,14 @@ impl Layer {
.intersection(&(mesh.clip_bounds() * transformation))
.and_then(Rectangle::snap)
else {
match mesh {
Mesh::Solid { .. } => {
num_solids += 1;
}
Mesh::Gradient { .. } => {
num_gradients += 1;
}
}
continue;
};
@ -636,10 +644,10 @@ impl Uniforms {
}
mod solid {
use crate::graphics::mesh;
use crate::graphics::Antialiasing;
use crate::triangle;
use crate::Buffer;
use crate::graphics::Antialiasing;
use crate::graphics::mesh;
use crate::triangle;
#[derive(Debug)]
pub struct Pipeline {
@ -745,7 +753,7 @@ mod solid {
layout: Some(&layout),
vertex: wgpu::VertexState {
module: &shader,
entry_point: "solid_vs_main",
entry_point: Some("solid_vs_main"),
buffers: &[wgpu::VertexBufferLayout {
array_stride: std::mem::size_of::<
mesh::SolidVertex2D,
@ -760,16 +768,21 @@ mod solid {
1 => Float32x4,
),
}],
compilation_options:
wgpu::PipelineCompilationOptions::default(),
},
fragment: Some(wgpu::FragmentState {
module: &shader,
entry_point: "solid_fs_main",
entry_point: Some("solid_fs_main"),
targets: &[Some(triangle::fragment_target(format))],
compilation_options:
wgpu::PipelineCompilationOptions::default(),
}),
primitive: triangle::primitive_state(),
depth_stencil: None,
multisample: triangle::multisample_state(antialiasing),
multiview: None,
cache: None,
},
);
@ -782,11 +795,11 @@ mod solid {
}
mod gradient {
use crate::Buffer;
use crate::graphics::Antialiasing;
use crate::graphics::color;
use crate::graphics::mesh;
use crate::graphics::Antialiasing;
use crate::triangle;
use crate::Buffer;
#[derive(Debug)]
pub struct Pipeline {
@ -913,7 +926,7 @@ mod gradient {
layout: Some(&layout),
vertex: wgpu::VertexState {
module: &shader,
entry_point: "gradient_vs_main",
entry_point: Some("gradient_vs_main"),
buffers: &[wgpu::VertexBufferLayout {
array_stride: std::mem::size_of::<
mesh::GradientVertex2D,
@ -937,16 +950,21 @@ mod gradient {
6 => Float32x4
),
}],
compilation_options:
wgpu::PipelineCompilationOptions::default(),
},
fragment: Some(wgpu::FragmentState {
module: &shader,
entry_point: "gradient_fs_main",
entry_point: Some("gradient_fs_main"),
targets: &[Some(triangle::fragment_target(format))],
compilation_options:
wgpu::PipelineCompilationOptions::default(),
}),
primitive: triangle::primitive_state(),
depth_stencil: None,
multisample: triangle::multisample_state(antialiasing),
multiview: None,
cache: None,
},
);

View file

@ -110,12 +110,14 @@ impl Blit {
layout: Some(&layout),
vertex: wgpu::VertexState {
module: &shader,
entry_point: "vs_main",
entry_point: Some("vs_main"),
buffers: &[],
compilation_options:
wgpu::PipelineCompilationOptions::default(),
},
fragment: Some(wgpu::FragmentState {
module: &shader,
entry_point: "fs_main",
entry_point: Some("fs_main"),
targets: &[Some(wgpu::ColorTargetState {
format,
blend: Some(
@ -123,6 +125,8 @@ impl Blit {
),
write_mask: wgpu::ColorWrites::ALL,
})],
compilation_options:
wgpu::PipelineCompilationOptions::default(),
}),
primitive: wgpu::PrimitiveState {
topology: wgpu::PrimitiveTopology::TriangleList,
@ -136,6 +140,7 @@ impl Blit {
alpha_to_coverage_enabled: false,
},
multiview: None,
cache: None,
});
Blit {

View file

@ -56,6 +56,11 @@ impl Compositor {
) -> Result<Self, Error> {
let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
backends: settings.backends,
flags: if cfg!(feature = "strict-assertions") {
wgpu::InstanceFlags::debugging()
} else {
wgpu::InstanceFlags::empty()
},
..Default::default()
});
@ -162,6 +167,7 @@ impl Compositor {
),
required_features: wgpu::Features::empty(),
required_limits: required_limits.clone(),
memory_hints: wgpu::MemoryHints::MemoryUsage,
},
None,
)
@ -361,7 +367,6 @@ impl graphics::Compositor for Compositor {
fn screenshot(
&mut self,
renderer: &mut Self::Renderer,
_surface: &mut Self::Surface,
viewport: &Viewport,
background_color: Color,
) -> Vec<u8> {