Merge branch 'master' into feat/multi-window-support

This commit is contained in:
Héctor Ramón Jiménez 2023-11-29 22:28:31 +01:00
commit e09b4e24dd
No known key found for this signature in database
GPG key ID: 7CC46565708259A7
331 changed files with 12085 additions and 3976 deletions

View file

@ -1,8 +1,8 @@
use crate::core;
use crate::core::{Color, Font, Point, Size};
use crate::core::{Color, Size};
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,12 +26,10 @@ 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,
default_font: Font,
default_text_size: f32,
}
impl Backend {
@ -54,12 +52,10 @@ impl Backend {
quad_pipeline,
text_pipeline,
triangle_pipeline,
pipeline_storage: pipeline::Storage::default(),
#[cfg(any(feature = "image", feature = "svg"))]
image_pipeline,
default_font: settings.default_font,
default_text_size: settings.default_text_size,
}
}
@ -73,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,
@ -87,25 +84,22 @@ impl Backend {
let transformation = viewport.projection();
let mut layers = Layer::generate(primitives, viewport);
layers.push(Layer::overlay(overlay_text, viewport));
if !overlay_text.is_empty() {
layers.push(Layer::overlay(overlay_text, viewport));
}
self.prepare(
device,
queue,
format,
encoder,
scale_factor,
target_size,
transformation,
&layers,
);
while !self.prepare_text(
device,
queue,
scale_factor,
target_size,
&layers,
) {}
self.render(
device,
encoder,
@ -124,44 +118,14 @@ impl Backend {
self.image_pipeline.end_frame();
}
fn prepare_text(
&mut self,
device: &wgpu::Device,
queue: &wgpu::Queue,
scale_factor: f32,
target_size: Size<u32>,
layers: &[Layer<'_>],
) -> bool {
for layer in layers {
let bounds = (layer.bounds * scale_factor).snap();
if bounds.width < 1 || bounds.height < 1 {
continue;
}
if !layer.text.is_empty()
&& !self.text_pipeline.prepare(
device,
queue,
&layer.text,
layer.bounds,
scale_factor,
target_size,
)
{
return false;
}
}
true
}
fn prepare(
&mut self,
device: &wgpu::Device,
queue: &wgpu::Queue,
format: wgpu::TextureFormat,
_encoder: &mut wgpu::CommandEncoder,
scale_factor: f32,
target_size: Size<u32>,
transformation: Transformation,
layers: &[Layer<'_>],
) {
@ -210,6 +174,31 @@ impl Backend {
);
}
}
if !layer.text.is_empty() {
self.text_pipeline.prepare(
device,
queue,
&layer.text,
layer.bounds,
scale_factor,
target_size,
);
}
if !layer.pipelines.is_empty() {
for pipeline in &layer.pipelines {
pipeline.primitive.prepare(
format,
device,
queue,
pipeline.bounds,
target_size,
scale_factor,
&mut self.pipeline_storage,
);
}
}
}
}
@ -233,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,
@ -252,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,
},
));
@ -263,7 +254,7 @@ impl Backend {
let bounds = (layer.bounds * scale_factor).snap();
if bounds.width < 1 || bounds.height < 1 {
return;
continue;
}
if !layer.quads.is_empty() {
@ -294,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,
},
));
}
@ -329,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);
@ -337,67 +369,9 @@ impl Backend {
impl crate::graphics::Backend for Backend {
type Primitive = primitive::Custom;
fn trim_measurements(&mut self) {
self.text_pipeline.trim_measurements();
}
}
impl backend::Text for Backend {
const ICON_FONT: Font = Font::with_name("Iced-Icons");
const CHECKMARK_ICON: char = '\u{f00c}';
const ARROW_DOWN_ICON: char = '\u{e800}';
fn default_font(&self) -> Font {
self.default_font
}
fn default_size(&self) -> f32 {
self.default_text_size
}
fn measure(
&self,
contents: &str,
size: f32,
line_height: core::text::LineHeight,
font: Font,
bounds: Size,
shaping: core::text::Shaping,
) -> Size {
self.text_pipeline.measure(
contents,
size,
line_height,
font,
bounds,
shaping,
)
}
fn hit_test(
&self,
contents: &str,
size: f32,
line_height: core::text::LineHeight,
font: Font,
bounds: Size,
shaping: core::text::Shaping,
point: Point,
nearest_only: bool,
) -> Option<core::text::Hit> {
self.text_pipeline.hit_test(
contents,
size,
line_height,
font,
bounds,
shaping,
point,
nearest_only,
)
}
fn load_font(&mut self, font: Cow<'static, [u8]>) {
self.text_pipeline.load_font(font);
}
@ -405,14 +379,17 @@ impl backend::Text for Backend {
#[cfg(feature = "image")]
impl backend::Image for Backend {
fn dimensions(&self, handle: &core::image::Handle) -> Size<u32> {
fn dimensions(&self, handle: &crate::core::image::Handle) -> Size<u32> {
self.image_pipeline.dimensions(handle)
}
}
#[cfg(feature = "svg")]
impl backend::Svg for Backend {
fn viewport_dimensions(&self, handle: &core::svg::Handle) -> Size<u32> {
fn viewport_dimensions(
&self,
handle: &crate::core::svg::Handle,
) -> Size<u32> {
self.image_pipeline.viewport_dimensions(handle)
}
}

View file

@ -87,7 +87,7 @@ impl<T: bytemuck::Pod> Buffer<T> {
/// Clears any temporary data (i.e. offsets) from the buffer.
pub fn clear(&mut self) {
self.offsets.clear()
self.offsets.clear();
}
/// Returns the offset at `index`, if it exists.

View file

@ -12,7 +12,7 @@ pub fn convert(
let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
label: Some("iced_wgpu.offscreen.sampler"),
..Default::default()
..wgpu::SamplerDescriptor::default()
});
//sampler in 0
@ -102,10 +102,10 @@ pub fn convert(
primitive: wgpu::PrimitiveState {
topology: wgpu::PrimitiveTopology::TriangleList,
front_face: wgpu::FrontFace::Cw,
..Default::default()
..wgpu::PrimitiveState::default()
},
depth_stencil: None,
multisample: Default::default(),
multisample: wgpu::MultisampleState::default(),
multiview: None,
});
@ -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

@ -310,13 +310,11 @@ impl Frame {
/// resulting glyphs will not be rotated or scaled properly.
///
/// Additionally, all text will be rendered on top of all the layers of
/// a [`Canvas`]. Therefore, it is currently only meant to be used for
/// a `Canvas`. Therefore, it is currently only meant to be used for
/// overlays, which is the most common use case.
///
/// Support for vectorial text is planned, and should address all these
/// limitations.
///
/// [`Canvas`]: crate::widget::Canvas
pub fn fill_text(&mut self, text: impl Into<Text>) {
let text = text.into();
@ -444,11 +442,21 @@ impl Frame {
self.transforms.current.is_identity = false;
}
/// Applies a scaling to the current transform of the [`Frame`].
/// Applies a uniform scaling to the current transform of the [`Frame`].
#[inline]
pub fn scale(&mut self, scale: f32) {
pub fn scale(&mut self, scale: impl Into<f32>) {
let scale = scale.into();
self.scale_nonuniform(Vector { x: scale, y: scale });
}
/// Applies a non-uniform scaling to the current transform of the [`Frame`].
#[inline]
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, scale);
self.transforms.current.raw.pre_scale(scale.x, scale.y);
self.transforms.current.is_identity = false;
}
@ -472,7 +480,7 @@ impl Frame {
},
size: self.size,
}),
))
));
}
}
Buffer::Gradient(buffer) => {
@ -485,7 +493,7 @@ impl Frame {
},
size: self.size,
}),
))
));
}
}
}

View file

@ -37,7 +37,8 @@ pub struct Pipeline {
pipeline: wgpu::RenderPipeline,
vertices: wgpu::Buffer,
indices: wgpu::Buffer,
sampler: wgpu::Sampler,
nearest_sampler: wgpu::Sampler,
linear_sampler: wgpu::Sampler,
texture: wgpu::BindGroup,
texture_version: usize,
texture_atlas: Atlas,
@ -51,16 +52,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"),
@ -69,6 +70,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,
@ -77,7 +131,7 @@ impl Layer {
binding: 0,
resource: wgpu::BindingResource::Buffer(
wgpu::BufferBinding {
buffer: &uniforms,
buffer: uniforms,
offset: 0,
size: None,
},
@ -98,28 +152,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);
@ -142,12 +186,22 @@ impl Pipeline {
pub fn new(device: &wgpu::Device, format: wgpu::TextureFormat) -> Self {
use wgpu::util::DeviceExt;
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()
});
@ -312,7 +366,8 @@ impl Pipeline {
pipeline,
vertices,
indices,
sampler,
nearest_sampler,
linear_sampler,
texture,
texture_version: texture_atlas.layer_count(),
texture_atlas,
@ -355,7 +410,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();
@ -366,7 +422,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,
@ -377,7 +437,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,
},
);
}
}
@ -405,7 +470,7 @@ impl Pipeline {
[bounds.x, bounds.y],
size,
atlas_entry,
instances,
nearest_instances,
);
}
}
@ -414,7 +479,7 @@ impl Pipeline {
}
}
if instances.is_empty() {
if nearest_instances.is_empty() && linear_instances.is_empty() {
return;
}
@ -442,12 +507,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;
}
@ -524,7 +597,7 @@ struct Instance {
}
impl Instance {
pub const INITIAL: usize = 1_000;
pub const INITIAL: usize = 20;
}
#[repr(C)]

View file

@ -86,7 +86,7 @@ impl Atlas {
entry
};
log::info!("Allocated atlas entry: {:?}", entry);
log::info!("Allocated atlas entry: {entry:?}");
// It is a webgpu requirement that:
// BufferCopyView.layout.bytes_per_row % wgpu::COPY_BYTES_PER_ROW_ALIGNMENT == 0
@ -104,7 +104,7 @@ impl Atlas {
padded_data[offset..offset + 4 * width as usize].copy_from_slice(
&data[row * 4 * width as usize..(row + 1) * 4 * width as usize],
)
);
}
match &entry {
@ -139,13 +139,13 @@ impl Atlas {
}
}
log::info!("Current atlas: {:?}", self);
log::info!("Current atlas: {self:?}");
Some(entry)
}
pub fn remove(&mut self, entry: &Entry) {
log::info!("Removing atlas entry: {:?}", entry);
log::info!("Removing atlas entry: {entry:?}");
match entry {
Entry::Contiguous(allocation) => {
@ -237,7 +237,7 @@ impl Atlas {
}));
}
}
_ => {}
Layer::Full => {}
}
}
@ -258,7 +258,7 @@ impl Atlas {
}
fn deallocate(&mut self, allocation: &Allocation) {
log::info!("Deallocating atlas: {:?}", allocation);
log::info!("Deallocating atlas: {allocation:?}");
match allocation {
Allocation::Full { layer } => {

View file

@ -152,7 +152,7 @@ impl Cache {
let allocation =
atlas.upload(device, encoder, width, height, &rgba)?;
log::debug!("allocating {} {}x{}", id, width, height);
log::debug!("allocating {id} {width}x{height}");
let _ = self.svg_hits.insert(id);
let _ = self.rasterized_hits.insert(key);

View file

@ -1,16 +1,18 @@
//! Organize rendering primitives into a flattened list of layers.
mod image;
mod pipeline;
mod text;
pub mod mesh;
pub use image::Image;
pub use mesh::Mesh;
pub use pipeline::Pipeline;
pub use text::Text;
use crate::core;
use crate::core::alignment;
use crate::core::{Color, Font, Point, Rectangle, Size, Vector};
use crate::core::{Color, Font, Pixels, Point, Rectangle, Size, Vector};
use crate::graphics;
use crate::graphics::color;
use crate::graphics::Viewport;
@ -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(),
}
}
@ -56,14 +62,14 @@ impl<'a> Layer<'a> {
Layer::new(Rectangle::with_size(viewport.logical_size()));
for (i, line) in lines.iter().enumerate() {
let text = Text {
let text = text::Cached {
content: line.as_ref(),
bounds: Rectangle::new(
Point::new(11.0, 11.0 + 25.0 * i as f32),
Size::INFINITY,
),
color: Color::new(0.9, 0.9, 0.9, 1.0),
size: 20.0,
size: Pixels(20.0),
line_height: core::text::LineHeight::default(),
font: Font::MONOSPACE,
horizontal_alignment: alignment::Horizontal::Left,
@ -71,13 +77,13 @@ impl<'a> Layer<'a> {
shaping: core::text::Shaping::Basic,
};
overlay.text.push(text);
overlay.text.push(Text::Cached(text.clone()));
overlay.text.push(Text {
overlay.text.push(Text::Cached(text::Cached {
bounds: text.bounds + Vector::new(-1.0, -1.0),
color: Color::BLACK,
..text
});
}));
}
overlay
@ -113,6 +119,32 @@ impl<'a> Layer<'a> {
current_layer: usize,
) {
match primitive {
Primitive::Paragraph {
paragraph,
position,
color,
} => {
let layer = &mut layers[current_layer];
layer.text.push(Text::Paragraph {
paragraph: paragraph.clone(),
position: *position + translation,
color: *color,
});
}
Primitive::Editor {
editor,
position,
color,
} => {
let layer = &mut layers[current_layer];
layer.text.push(Text::Editor {
editor: editor.clone(),
position: *position + translation,
color: *color,
});
}
Primitive::Text {
content,
bounds,
@ -126,7 +158,7 @@ impl<'a> Layer<'a> {
} => {
let layer = &mut layers[current_layer];
layer.text.push(Text {
layer.text.push(Text::Cached(text::Cached {
content,
bounds: *bounds + translation,
size: *size,
@ -136,7 +168,7 @@ impl<'a> Layer<'a> {
horizontal_alignment: *horizontal_alignment,
vertical_alignment: *vertical_alignment,
shaping: *shaping,
});
}));
}
Primitive::Quad {
bounds,
@ -160,11 +192,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,
});
}
@ -189,7 +226,7 @@ impl<'a> Layer<'a> {
translation,
primitive,
current_layer,
)
);
}
}
Primitive::Clip { bounds, content } => {
@ -277,6 +314,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,10 +1,32 @@
use crate::core::alignment;
use crate::core::text;
use crate::core::{Color, Font, Rectangle};
use crate::core::{Color, Font, Pixels, Point, Rectangle};
use crate::graphics::text::editor;
use crate::graphics::text::paragraph;
/// A paragraph of text.
#[derive(Debug, Clone, Copy)]
pub struct Text<'a> {
/// A text primitive.
#[derive(Debug, Clone)]
pub enum Text<'a> {
/// A paragraph.
#[allow(missing_docs)]
Paragraph {
paragraph: paragraph::Weak,
position: Point,
color: Color,
},
/// An editor.
#[allow(missing_docs)]
Editor {
editor: editor::Weak,
position: Point,
color: Color,
},
/// A cached text.
Cached(Cached<'a>),
}
#[derive(Debug, Clone)]
pub struct Cached<'a> {
/// The content of the [`Text`].
pub content: &'a str,
@ -15,7 +37,7 @@ pub struct Text<'a> {
pub color: Color,
/// The size of the [`Text`] in logical pixels.
pub size: f32,
pub size: Pixels,
/// The line height of the [`Text`].
pub line_height: text::LineHeight,

View file

@ -1,41 +1,33 @@
//! A [`wgpu`] renderer for [`iced_native`].
//! A [`wgpu`] renderer for [Iced].
//!
//! ![The native path of the Iced ecosystem](https://github.com/iced-rs/iced/blob/0525d76ff94e828b7b21634fa94a747022001c83/docs/graphs/native.png?raw=true)
//!
//! For now, it is the default renderer of [Iced] in native platforms.
//!
//! [`wgpu`] supports most modern graphics backends: Vulkan, Metal, DX11, 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.
//! - Text, which is rendered using [`glyphon`].
//! - 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.
//!
//! [Iced]: https://github.com/iced-rs/iced
//! [`iced_native`]: https://github.com/iced-rs/iced/tree/0.9/native
//! [`wgpu`]: https://github.com/gfx-rs/wgpu-rs
//! [WebGPU API]: https://gpuweb.github.io/gpuweb/
//! [`wgpu_glyph`]: https://github.com/hecrj/wgpu_glyph
//! [`glyphon`]: https://github.com/grovesNL/glyphon
#![doc(
html_logo_url = "https://raw.githubusercontent.com/iced-rs/iced/9ab6923e943f784985e9ef9ca28b10278297225d/docs/logo.svg"
)]
#![forbid(rust_2018_idioms)]
#![deny(
missing_debug_implementations,
missing_docs,
unsafe_code,
unused_results,
clippy::extra_unused_lifetimes,
clippy::from_over_into,
clippy::needless_borrow,
clippy::new_without_default,
clippy::useless_conversion
rustdoc::broken_intra_doc_links
)]
#![forbid(rust_2018_idioms)]
#![allow(clippy::inherent_to_string, clippy::type_complexity)]
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
pub mod layer;
pub mod primitive;

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>>,
}
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>(&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

@ -1,3 +1,4 @@
use crate::graphics::color;
use crate::graphics::gradient;
use crate::quad::{self, Quad};
use crate::Buffer;
@ -78,7 +79,23 @@ impl Pipeline {
device.create_shader_module(wgpu::ShaderModuleDescriptor {
label: Some("iced_wgpu.quad.gradient.shader"),
source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Borrowed(
include_str!("../shader/quad.wgsl"),
if color::GAMMA_CORRECTION {
concat!(
include_str!("../shader/quad.wgsl"),
"\n",
include_str!("../shader/quad/gradient.wgsl"),
"\n",
include_str!("../shader/color/oklab.wgsl")
)
} else {
concat!(
include_str!("../shader/quad.wgsl"),
"\n",
include_str!("../shader/quad/gradient.wgsl"),
"\n",
include_str!("../shader/color/linear_rgb.wgsl")
)
},
)),
});

View file

@ -72,7 +72,11 @@ impl Pipeline {
device.create_shader_module(wgpu::ShaderModuleDescriptor {
label: Some("iced_wgpu.quad.solid.shader"),
source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Borrowed(
include_str!("../shader/quad.wgsl"),
concat!(
include_str!("../shader/quad.wgsl"),
"\n",
include_str!("../shader/quad/solid.wgsl"),
),
)),
});

View file

@ -1,5 +1,5 @@
//! Configure a renderer.
use crate::core::Font;
use crate::core::{Font, Pixels};
use crate::graphics::Antialiasing;
/// The settings of a [`Backend`].
@ -21,7 +21,7 @@ pub struct Settings {
/// The default size of text.
///
/// By default, it will be set to `16.0`.
pub default_text_size: f32,
pub default_text_size: Pixels,
/// The antialiasing strategy that will be used for triangle primitives.
///
@ -59,7 +59,7 @@ impl Default for Settings {
present_mode: wgpu::PresentMode::AutoVsync,
internal_backend: wgpu::Backends::all(),
default_font: Font::default(),
default_text_size: 16.0,
default_text_size: Pixels(16.0),
antialiasing: None,
}
}

View file

@ -0,0 +1,3 @@
fn interpolate_color(from_: vec4<f32>, to_: vec4<f32>, factor: f32) -> vec4<f32> {
return mix(from_, to_, factor);
}

View file

@ -0,0 +1,26 @@
const to_lms = mat3x4<f32>(
vec4<f32>(0.4121656120, 0.2118591070, 0.0883097947, 0.0),
vec4<f32>(0.5362752080, 0.6807189584, 0.2818474174, 0.0),
vec4<f32>(0.0514575653, 0.1074065790, 0.6302613616, 0.0),
);
const to_rgb = mat3x4<f32>(
vec4<f32>( 4.0767245293, -3.3072168827, 0.2307590544, 0.0),
vec4<f32>(-1.2681437731, 2.6093323231, -0.3411344290, 0.0),
vec4<f32>(-0.0041119885, -0.7034763098, 1.7068625689, 0.0),
);
fn interpolate_color(from_: vec4<f32>, to_: vec4<f32>, factor: f32) -> vec4<f32> {
// To Oklab
let lms_a = pow(from_ * to_lms, vec3<f32>(1.0 / 3.0, 1.0 / 3.0, 1.0 / 3.0));
let lms_b = pow(to_ * to_lms, vec3<f32>(1.0 / 3.0, 1.0 / 3.0, 1.0 / 3.0));
let mixed = mix(lms_a, lms_b, factor);
// Back to linear RGB
var color = to_rgb * (mixed * mixed * mixed);
// Alpha interpolation
color.a = mix(from_.a, to_.a, factor);
return color;
}

View file

@ -37,309 +37,3 @@ fn select_border_radius(radi: vec4<f32>, position: vec2<f32>, center: vec2<f32>)
rx = select(rx, ry, position.y > center.y);
return rx;
}
fn unpack_u32(color: vec2<u32>) -> vec4<f32> {
let rg: vec2<f32> = unpack2x16float(color.x);
let ba: vec2<f32> = unpack2x16float(color.y);
return vec4<f32>(rg.y, rg.x, ba.y, ba.x);
}
struct SolidVertexInput {
@location(0) v_pos: vec2<f32>,
@location(1) color: vec4<f32>,
@location(2) pos: vec2<f32>,
@location(3) scale: vec2<f32>,
@location(4) border_color: vec4<f32>,
@location(5) border_radius: vec4<f32>,
@location(6) border_width: f32,
}
struct SolidVertexOutput {
@builtin(position) position: vec4<f32>,
@location(0) color: vec4<f32>,
@location(1) border_color: vec4<f32>,
@location(2) pos: vec2<f32>,
@location(3) scale: vec2<f32>,
@location(4) border_radius: vec4<f32>,
@location(5) border_width: f32,
}
@vertex
fn solid_vs_main(input: SolidVertexInput) -> SolidVertexOutput {
var out: SolidVertexOutput;
var pos: vec2<f32> = input.pos * globals.scale;
var scale: vec2<f32> = input.scale * globals.scale;
var min_border_radius = min(input.scale.x, input.scale.y) * 0.5;
var border_radius: vec4<f32> = vec4<f32>(
min(input.border_radius.x, min_border_radius),
min(input.border_radius.y, min_border_radius),
min(input.border_radius.z, min_border_radius),
min(input.border_radius.w, min_border_radius)
);
var transform: mat4x4<f32> = mat4x4<f32>(
vec4<f32>(scale.x + 1.0, 0.0, 0.0, 0.0),
vec4<f32>(0.0, scale.y + 1.0, 0.0, 0.0),
vec4<f32>(0.0, 0.0, 1.0, 0.0),
vec4<f32>(pos - vec2<f32>(0.5, 0.5), 0.0, 1.0)
);
out.position = globals.transform * transform * vec4<f32>(input.v_pos, 0.0, 1.0);
out.color = input.color;
out.border_color = input.border_color;
out.pos = pos;
out.scale = scale;
out.border_radius = border_radius * globals.scale;
out.border_width = input.border_width * globals.scale;
return out;
}
@fragment
fn solid_fs_main(
input: SolidVertexOutput
) -> @location(0) vec4<f32> {
var mixed_color: vec4<f32> = input.color;
var border_radius = select_border_radius(
input.border_radius,
input.position.xy,
(input.pos + input.scale * 0.5).xy
);
if (input.border_width > 0.0) {
var internal_border: f32 = max(border_radius - input.border_width, 0.0);
var internal_distance: f32 = distance_alg(
input.position.xy,
input.pos + vec2<f32>(input.border_width, input.border_width),
input.scale - vec2<f32>(input.border_width * 2.0, input.border_width * 2.0),
internal_border
);
var border_mix: f32 = smoothstep(
max(internal_border - 0.5, 0.0),
internal_border + 0.5,
internal_distance
);
mixed_color = mix(input.color, input.border_color, vec4<f32>(border_mix, border_mix, border_mix, border_mix));
}
var dist: f32 = distance_alg(
vec2<f32>(input.position.x, input.position.y),
input.pos,
input.scale,
border_radius
);
var radius_alpha: f32 = 1.0 - smoothstep(
max(border_radius - 0.5, 0.0),
border_radius + 0.5,
dist
);
return vec4<f32>(mixed_color.x, mixed_color.y, mixed_color.z, mixed_color.w * radius_alpha);
}
struct GradientVertexInput {
@location(0) v_pos: vec2<f32>,
@location(1) colors_1: vec4<u32>,
@location(2) colors_2: vec4<u32>,
@location(3) colors_3: vec4<u32>,
@location(4) colors_4: vec4<u32>,
@location(5) offsets: vec4<u32>,
@location(6) direction: vec4<f32>,
@location(7) position_and_scale: vec4<f32>,
@location(8) border_color: vec4<f32>,
@location(9) border_radius: vec4<f32>,
@location(10) border_width: f32,
}
struct GradientVertexOutput {
@builtin(position) position: vec4<f32>,
@location(1) colors_1: vec4<u32>,
@location(2) colors_2: vec4<u32>,
@location(3) colors_3: vec4<u32>,
@location(4) colors_4: vec4<u32>,
@location(5) offsets: vec4<u32>,
@location(6) direction: vec4<f32>,
@location(7) position_and_scale: vec4<f32>,
@location(8) border_color: vec4<f32>,
@location(9) border_radius: vec4<f32>,
@location(10) border_width: f32,
}
@vertex
fn gradient_vs_main(input: GradientVertexInput) -> GradientVertexOutput {
var out: GradientVertexOutput;
var pos: vec2<f32> = input.position_and_scale.xy * globals.scale;
var scale: vec2<f32> = input.position_and_scale.zw * globals.scale;
var min_border_radius = min(input.position_and_scale.z, input.position_and_scale.w) * 0.5;
var border_radius: vec4<f32> = vec4<f32>(
min(input.border_radius.x, min_border_radius),
min(input.border_radius.y, min_border_radius),
min(input.border_radius.z, min_border_radius),
min(input.border_radius.w, min_border_radius)
);
var transform: mat4x4<f32> = mat4x4<f32>(
vec4<f32>(scale.x + 1.0, 0.0, 0.0, 0.0),
vec4<f32>(0.0, scale.y + 1.0, 0.0, 0.0),
vec4<f32>(0.0, 0.0, 1.0, 0.0),
vec4<f32>(pos - vec2<f32>(0.5, 0.5), 0.0, 1.0)
);
out.position = globals.transform * transform * vec4<f32>(input.v_pos, 0.0, 1.0);
out.colors_1 = input.colors_1;
out.colors_2 = input.colors_2;
out.colors_3 = input.colors_3;
out.colors_4 = input.colors_4;
out.offsets = input.offsets;
out.direction = input.direction * globals.scale;
out.position_and_scale = vec4<f32>(pos, scale);
out.border_color = input.border_color;
out.border_radius = border_radius * globals.scale;
out.border_width = input.border_width * globals.scale;
return out;
}
fn random(coords: vec2<f32>) -> f32 {
return fract(sin(dot(coords, vec2(12.9898,78.233))) * 43758.5453);
}
/// Returns the current interpolated color with a max 8-stop gradient
fn gradient(
raw_position: vec2<f32>,
direction: vec4<f32>,
colors: array<vec4<f32>, 8>,
offsets: array<f32, 8>,
last_index: i32
) -> vec4<f32> {
let start = direction.xy;
let end = direction.zw;
let v1 = end - start;
let v2 = raw_position - start;
let unit = normalize(v1);
let coord_offset = dot(unit, v2) / length(v1);
//need to store these as a var to use dynamic indexing in a loop
//this is already added to wgsl spec but not in wgpu yet
var colors_arr = colors;
var offsets_arr = offsets;
var color: vec4<f32>;
let noise_granularity: f32 = 0.3/255.0;
for (var i: i32 = 0; i < last_index; i++) {
let curr_offset = offsets_arr[i];
let next_offset = offsets_arr[i+1];
if (coord_offset <= offsets_arr[0]) {
color = colors_arr[0];
}
if (curr_offset <= coord_offset && coord_offset <= next_offset) {
color = mix(colors_arr[i], colors_arr[i+1], smoothstep(
curr_offset,
next_offset,
coord_offset,
));
}
if (coord_offset >= offsets_arr[last_index]) {
color = colors_arr[last_index];
}
}
return color + mix(-noise_granularity, noise_granularity, random(raw_position));
}
@fragment
fn gradient_fs_main(input: GradientVertexOutput) -> @location(0) vec4<f32> {
let colors = array<vec4<f32>, 8>(
unpack_u32(input.colors_1.xy),
unpack_u32(input.colors_1.zw),
unpack_u32(input.colors_2.xy),
unpack_u32(input.colors_2.zw),
unpack_u32(input.colors_3.xy),
unpack_u32(input.colors_3.zw),
unpack_u32(input.colors_4.xy),
unpack_u32(input.colors_4.zw),
);
let offsets_1: vec4<f32> = unpack_u32(input.offsets.xy);
let offsets_2: vec4<f32> = unpack_u32(input.offsets.zw);
var offsets = array<f32, 8>(
offsets_1.x,
offsets_1.y,
offsets_1.z,
offsets_1.w,
offsets_2.x,
offsets_2.y,
offsets_2.z,
offsets_2.w,
);
//TODO could just pass this in to the shader but is probably more performant to just check it here
var last_index = 7;
for (var i: i32 = 0; i <= 7; i++) {
if (offsets[i] > 1.0) {
last_index = i - 1;
break;
}
}
var mixed_color: vec4<f32> = gradient(input.position.xy, input.direction, colors, offsets, last_index);
let pos = input.position_and_scale.xy;
let scale = input.position_and_scale.zw;
var border_radius = select_border_radius(
input.border_radius,
input.position.xy,
(pos + scale * 0.5).xy
);
if (input.border_width > 0.0) {
var internal_border: f32 = max(border_radius - input.border_width, 0.0);
var internal_distance: f32 = distance_alg(
input.position.xy,
pos + vec2<f32>(input.border_width, input.border_width),
scale - vec2<f32>(input.border_width * 2.0, input.border_width * 2.0),
internal_border
);
var border_mix: f32 = smoothstep(
max(internal_border - 0.5, 0.0),
internal_border + 0.5,
internal_distance
);
mixed_color = mix(mixed_color, input.border_color, vec4<f32>(border_mix, border_mix, border_mix, border_mix));
}
var dist: f32 = distance_alg(
input.position.xy,
pos,
scale,
border_radius
);
var radius_alpha: f32 = 1.0 - smoothstep(
max(border_radius - 0.5, 0.0),
border_radius + 0.5,
dist);
return vec4<f32>(mixed_color.x, mixed_color.y, mixed_color.z, mixed_color.w * radius_alpha);
}

View file

@ -0,0 +1,205 @@
struct GradientVertexInput {
@location(0) v_pos: vec2<f32>,
@location(1) @interpolate(flat) colors_1: vec4<u32>,
@location(2) @interpolate(flat) colors_2: vec4<u32>,
@location(3) @interpolate(flat) colors_3: vec4<u32>,
@location(4) @interpolate(flat) colors_4: vec4<u32>,
@location(5) @interpolate(flat) offsets: vec4<u32>,
@location(6) direction: vec4<f32>,
@location(7) position_and_scale: vec4<f32>,
@location(8) border_color: vec4<f32>,
@location(9) border_radius: vec4<f32>,
@location(10) border_width: f32,
}
struct GradientVertexOutput {
@builtin(position) position: vec4<f32>,
@location(1) @interpolate(flat) colors_1: vec4<u32>,
@location(2) @interpolate(flat) colors_2: vec4<u32>,
@location(3) @interpolate(flat) colors_3: vec4<u32>,
@location(4) @interpolate(flat) colors_4: vec4<u32>,
@location(5) @interpolate(flat) offsets: vec4<u32>,
@location(6) direction: vec4<f32>,
@location(7) position_and_scale: vec4<f32>,
@location(8) border_color: vec4<f32>,
@location(9) border_radius: vec4<f32>,
@location(10) border_width: f32,
}
@vertex
fn gradient_vs_main(input: GradientVertexInput) -> GradientVertexOutput {
var out: GradientVertexOutput;
var pos: vec2<f32> = input.position_and_scale.xy * globals.scale;
var scale: vec2<f32> = input.position_and_scale.zw * globals.scale;
var min_border_radius = min(input.position_and_scale.z, input.position_and_scale.w) * 0.5;
var border_radius: vec4<f32> = vec4<f32>(
min(input.border_radius.x, min_border_radius),
min(input.border_radius.y, min_border_radius),
min(input.border_radius.z, min_border_radius),
min(input.border_radius.w, min_border_radius)
);
var transform: mat4x4<f32> = mat4x4<f32>(
vec4<f32>(scale.x + 1.0, 0.0, 0.0, 0.0),
vec4<f32>(0.0, scale.y + 1.0, 0.0, 0.0),
vec4<f32>(0.0, 0.0, 1.0, 0.0),
vec4<f32>(pos - vec2<f32>(0.5, 0.5), 0.0, 1.0)
);
out.position = globals.transform * transform * vec4<f32>(input.v_pos, 0.0, 1.0);
out.colors_1 = input.colors_1;
out.colors_2 = input.colors_2;
out.colors_3 = input.colors_3;
out.colors_4 = input.colors_4;
out.offsets = input.offsets;
out.direction = input.direction * globals.scale;
out.position_and_scale = vec4<f32>(pos, scale);
out.border_color = input.border_color;
out.border_radius = border_radius * globals.scale;
out.border_width = input.border_width * globals.scale;
return out;
}
fn random(coords: vec2<f32>) -> f32 {
return fract(sin(dot(coords, vec2(12.9898,78.233))) * 43758.5453);
}
/// Returns the current interpolated color with a max 8-stop gradient
fn gradient(
raw_position: vec2<f32>,
direction: vec4<f32>,
colors: array<vec4<f32>, 8>,
offsets: array<f32, 8>,
last_index: i32
) -> vec4<f32> {
let start = direction.xy;
let end = direction.zw;
let v1 = end - start;
let v2 = raw_position - start;
let unit = normalize(v1);
let coord_offset = dot(unit, v2) / length(v1);
//need to store these as a var to use dynamic indexing in a loop
//this is already added to wgsl spec but not in wgpu yet
var colors_arr = colors;
var offsets_arr = offsets;
var color: vec4<f32>;
let noise_granularity: f32 = 0.3/255.0;
for (var i: i32 = 0; i < last_index; i++) {
let curr_offset = offsets_arr[i];
let next_offset = offsets_arr[i+1];
if (coord_offset <= offsets_arr[0]) {
color = colors_arr[0];
}
if (curr_offset <= coord_offset && coord_offset <= next_offset) {
let from_ = colors_arr[i];
let to_ = colors_arr[i+1];
let factor = smoothstep(curr_offset, next_offset, coord_offset);
color = interpolate_color(from_, to_, factor);
}
if (coord_offset >= offsets_arr[last_index]) {
color = colors_arr[last_index];
}
}
return color + mix(-noise_granularity, noise_granularity, random(raw_position));
}
@fragment
fn gradient_fs_main(input: GradientVertexOutput) -> @location(0) vec4<f32> {
let colors = array<vec4<f32>, 8>(
unpack_u32(input.colors_1.xy),
unpack_u32(input.colors_1.zw),
unpack_u32(input.colors_2.xy),
unpack_u32(input.colors_2.zw),
unpack_u32(input.colors_3.xy),
unpack_u32(input.colors_3.zw),
unpack_u32(input.colors_4.xy),
unpack_u32(input.colors_4.zw),
);
let offsets_1: vec4<f32> = unpack_u32(input.offsets.xy);
let offsets_2: vec4<f32> = unpack_u32(input.offsets.zw);
var offsets = array<f32, 8>(
offsets_1.x,
offsets_1.y,
offsets_1.z,
offsets_1.w,
offsets_2.x,
offsets_2.y,
offsets_2.z,
offsets_2.w,
);
//TODO could just pass this in to the shader but is probably more performant to just check it here
var last_index = 7;
for (var i: i32 = 0; i <= 7; i++) {
if (offsets[i] > 1.0) {
last_index = i - 1;
break;
}
}
var mixed_color: vec4<f32> = gradient(input.position.xy, input.direction, colors, offsets, last_index);
let pos = input.position_and_scale.xy;
let scale = input.position_and_scale.zw;
var border_radius = select_border_radius(
input.border_radius,
input.position.xy,
(pos + scale * 0.5).xy
);
if (input.border_width > 0.0) {
var internal_border: f32 = max(border_radius - input.border_width, 0.0);
var internal_distance: f32 = distance_alg(
input.position.xy,
pos + vec2<f32>(input.border_width, input.border_width),
scale - vec2<f32>(input.border_width * 2.0, input.border_width * 2.0),
internal_border
);
var border_mix: f32 = smoothstep(
max(internal_border - 0.5, 0.0),
internal_border + 0.5,
internal_distance
);
mixed_color = mix(mixed_color, input.border_color, vec4<f32>(border_mix, border_mix, border_mix, border_mix));
}
var dist: f32 = distance_alg(
input.position.xy,
pos,
scale,
border_radius
);
var radius_alpha: f32 = 1.0 - smoothstep(
max(border_radius - 0.5, 0.0),
border_radius + 0.5,
dist);
return vec4<f32>(mixed_color.x, mixed_color.y, mixed_color.z, mixed_color.w * radius_alpha);
}
fn unpack_u32(color: vec2<u32>) -> vec4<f32> {
let rg: vec2<f32> = unpack2x16float(color.x);
let ba: vec2<f32> = unpack2x16float(color.y);
return vec4<f32>(rg.y, rg.x, ba.y, ba.x);
}

View file

@ -0,0 +1,99 @@
struct SolidVertexInput {
@location(0) v_pos: vec2<f32>,
@location(1) color: vec4<f32>,
@location(2) pos: vec2<f32>,
@location(3) scale: vec2<f32>,
@location(4) border_color: vec4<f32>,
@location(5) border_radius: vec4<f32>,
@location(6) border_width: f32,
}
struct SolidVertexOutput {
@builtin(position) position: vec4<f32>,
@location(0) color: vec4<f32>,
@location(1) border_color: vec4<f32>,
@location(2) pos: vec2<f32>,
@location(3) scale: vec2<f32>,
@location(4) border_radius: vec4<f32>,
@location(5) border_width: f32,
}
@vertex
fn solid_vs_main(input: SolidVertexInput) -> SolidVertexOutput {
var out: SolidVertexOutput;
var pos: vec2<f32> = input.pos * globals.scale;
var scale: vec2<f32> = input.scale * globals.scale;
var min_border_radius = min(input.scale.x, input.scale.y) * 0.5;
var border_radius: vec4<f32> = vec4<f32>(
min(input.border_radius.x, min_border_radius),
min(input.border_radius.y, min_border_radius),
min(input.border_radius.z, min_border_radius),
min(input.border_radius.w, min_border_radius)
);
var transform: mat4x4<f32> = mat4x4<f32>(
vec4<f32>(scale.x + 1.0, 0.0, 0.0, 0.0),
vec4<f32>(0.0, scale.y + 1.0, 0.0, 0.0),
vec4<f32>(0.0, 0.0, 1.0, 0.0),
vec4<f32>(pos - vec2<f32>(0.5, 0.5), 0.0, 1.0)
);
out.position = globals.transform * transform * vec4<f32>(input.v_pos, 0.0, 1.0);
out.color = input.color;
out.border_color = input.border_color;
out.pos = pos;
out.scale = scale;
out.border_radius = border_radius * globals.scale;
out.border_width = input.border_width * globals.scale;
return out;
}
@fragment
fn solid_fs_main(
input: SolidVertexOutput
) -> @location(0) vec4<f32> {
var mixed_color: vec4<f32> = input.color;
var border_radius = select_border_radius(
input.border_radius,
input.position.xy,
(input.pos + input.scale * 0.5).xy
);
if (input.border_width > 0.0) {
var internal_border: f32 = max(border_radius - input.border_width, 0.0);
var internal_distance: f32 = distance_alg(
input.position.xy,
input.pos + vec2<f32>(input.border_width, input.border_width),
input.scale - vec2<f32>(input.border_width * 2.0, input.border_width * 2.0),
internal_border
);
var border_mix: f32 = smoothstep(
max(internal_border - 0.5, 0.0),
internal_border + 0.5,
internal_distance
);
mixed_color = mix(input.color, input.border_color, vec4<f32>(border_mix, border_mix, border_mix, border_mix));
}
var dist: f32 = distance_alg(
vec2<f32>(input.position.x, input.position.y),
input.pos,
input.scale,
border_radius
);
var radius_alpha: f32 = 1.0 - smoothstep(
max(border_radius - 0.5, 0.0),
border_radius + 0.5,
dist
);
return vec4<f32>(mixed_color.x, mixed_color.y, mixed_color.z, mixed_color.w * radius_alpha);
}

View file

@ -3,163 +3,3 @@ struct Globals {
}
@group(0) @binding(0) var<uniform> globals: Globals;
fn unpack_u32(color: vec2<u32>) -> vec4<f32> {
let rg: vec2<f32> = unpack2x16float(color.x);
let ba: vec2<f32> = unpack2x16float(color.y);
return vec4<f32>(rg.y, rg.x, ba.y, ba.x);
}
struct SolidVertexInput {
@location(0) position: vec2<f32>,
@location(1) color: vec4<f32>,
}
struct SolidVertexOutput {
@builtin(position) position: vec4<f32>,
@location(0) color: vec4<f32>,
}
@vertex
fn solid_vs_main(input: SolidVertexInput) -> SolidVertexOutput {
var out: SolidVertexOutput;
out.color = input.color;
out.position = globals.transform * vec4<f32>(input.position, 0.0, 1.0);
return out;
}
@fragment
fn solid_fs_main(input: SolidVertexOutput) -> @location(0) vec4<f32> {
return input.color;
}
struct GradientVertexInput {
@location(0) v_pos: vec2<f32>,
@location(1) colors_1: vec4<u32>,
@location(2) colors_2: vec4<u32>,
@location(3) colors_3: vec4<u32>,
@location(4) colors_4: vec4<u32>,
@location(5) offsets: vec4<u32>,
@location(6) direction: vec4<f32>,
}
struct GradientVertexOutput {
@builtin(position) position: vec4<f32>,
@location(0) raw_position: vec2<f32>,
@location(1) colors_1: vec4<u32>,
@location(2) colors_2: vec4<u32>,
@location(3) colors_3: vec4<u32>,
@location(4) colors_4: vec4<u32>,
@location(5) offsets: vec4<u32>,
@location(6) direction: vec4<f32>,
}
@vertex
fn gradient_vs_main(input: GradientVertexInput) -> GradientVertexOutput {
var output: GradientVertexOutput;
output.position = globals.transform * vec4<f32>(input.v_pos, 0.0, 1.0);
output.raw_position = input.v_pos;
output.colors_1 = input.colors_1;
output.colors_2 = input.colors_2;
output.colors_3 = input.colors_3;
output.colors_4 = input.colors_4;
output.offsets = input.offsets;
output.direction = input.direction;
return output;
}
fn random(coords: vec2<f32>) -> f32 {
return fract(sin(dot(coords, vec2(12.9898,78.233))) * 43758.5453);
}
/// Returns the current interpolated color with a max 8-stop gradient
fn gradient(
raw_position: vec2<f32>,
direction: vec4<f32>,
colors: array<vec4<f32>, 8>,
offsets: array<f32, 8>,
last_index: i32
) -> vec4<f32> {
let start = direction.xy;
let end = direction.zw;
let v1 = end - start;
let v2 = raw_position - start;
let unit = normalize(v1);
let coord_offset = dot(unit, v2) / length(v1);
//need to store these as a var to use dynamic indexing in a loop
//this is already added to wgsl spec but not in wgpu yet
var colors_arr = colors;
var offsets_arr = offsets;
var color: vec4<f32>;
let noise_granularity: f32 = 0.3/255.0;
for (var i: i32 = 0; i < last_index; i++) {
let curr_offset = offsets_arr[i];
let next_offset = offsets_arr[i+1];
if (coord_offset <= offsets_arr[0]) {
color = colors_arr[0];
}
if (curr_offset <= coord_offset && coord_offset <= next_offset) {
color = mix(colors_arr[i], colors_arr[i+1], smoothstep(
curr_offset,
next_offset,
coord_offset,
));
}
if (coord_offset >= offsets_arr[last_index]) {
color = colors_arr[last_index];
}
}
return color + mix(-noise_granularity, noise_granularity, random(raw_position));
}
@fragment
fn gradient_fs_main(input: GradientVertexOutput) -> @location(0) vec4<f32> {
let colors = array<vec4<f32>, 8>(
unpack_u32(input.colors_1.xy),
unpack_u32(input.colors_1.zw),
unpack_u32(input.colors_2.xy),
unpack_u32(input.colors_2.zw),
unpack_u32(input.colors_3.xy),
unpack_u32(input.colors_3.zw),
unpack_u32(input.colors_4.xy),
unpack_u32(input.colors_4.zw),
);
let offsets_1: vec4<f32> = unpack_u32(input.offsets.xy);
let offsets_2: vec4<f32> = unpack_u32(input.offsets.zw);
var offsets = array<f32, 8>(
offsets_1.x,
offsets_1.y,
offsets_1.z,
offsets_1.w,
offsets_2.x,
offsets_2.y,
offsets_2.z,
offsets_2.w,
);
var last_index = 7;
for (var i: i32 = 0; i <= 7; i++) {
if (offsets[i] >= 1.0) {
last_index = i;
break;
}
}
return gradient(input.raw_position, input.direction, colors, offsets, last_index);
}

View file

@ -0,0 +1,134 @@
struct GradientVertexInput {
@location(0) v_pos: vec2<f32>,
@location(1) @interpolate(flat) colors_1: vec4<u32>,
@location(2) @interpolate(flat) colors_2: vec4<u32>,
@location(3) @interpolate(flat) colors_3: vec4<u32>,
@location(4) @interpolate(flat) colors_4: vec4<u32>,
@location(5) @interpolate(flat) offsets: vec4<u32>,
@location(6) direction: vec4<f32>,
}
struct GradientVertexOutput {
@builtin(position) position: vec4<f32>,
@location(0) raw_position: vec2<f32>,
@location(1) @interpolate(flat) colors_1: vec4<u32>,
@location(2) @interpolate(flat) colors_2: vec4<u32>,
@location(3) @interpolate(flat) colors_3: vec4<u32>,
@location(4) @interpolate(flat) colors_4: vec4<u32>,
@location(5) @interpolate(flat) offsets: vec4<u32>,
@location(6) direction: vec4<f32>,
}
@vertex
fn gradient_vs_main(input: GradientVertexInput) -> GradientVertexOutput {
var output: GradientVertexOutput;
output.position = globals.transform * vec4<f32>(input.v_pos, 0.0, 1.0);
output.raw_position = input.v_pos;
output.colors_1 = input.colors_1;
output.colors_2 = input.colors_2;
output.colors_3 = input.colors_3;
output.colors_4 = input.colors_4;
output.offsets = input.offsets;
output.direction = input.direction;
return output;
}
/// Returns the current interpolated color with a max 8-stop gradient
fn gradient(
raw_position: vec2<f32>,
direction: vec4<f32>,
colors: array<vec4<f32>, 8>,
offsets: array<f32, 8>,
last_index: i32
) -> vec4<f32> {
let start = direction.xy;
let end = direction.zw;
let v1 = end - start;
let v2 = raw_position - start;
let unit = normalize(v1);
let coord_offset = dot(unit, v2) / length(v1);
//need to store these as a var to use dynamic indexing in a loop
//this is already added to wgsl spec but not in wgpu yet
var colors_arr = colors;
var offsets_arr = offsets;
var color: vec4<f32>;
let noise_granularity: f32 = 0.3/255.0;
for (var i: i32 = 0; i < last_index; i++) {
let curr_offset = offsets_arr[i];
let next_offset = offsets_arr[i+1];
if (coord_offset <= offsets_arr[0]) {
color = colors_arr[0];
}
if (curr_offset <= coord_offset && coord_offset <= next_offset) {
let from_ = colors_arr[i];
let to_ = colors_arr[i+1];
let factor = smoothstep(curr_offset, next_offset, coord_offset);
color = interpolate_color(from_, to_, factor);
}
if (coord_offset >= offsets_arr[last_index]) {
color = colors_arr[last_index];
}
}
return color + mix(-noise_granularity, noise_granularity, random(raw_position));
}
@fragment
fn gradient_fs_main(input: GradientVertexOutput) -> @location(0) vec4<f32> {
let colors = array<vec4<f32>, 8>(
unpack_u32(input.colors_1.xy),
unpack_u32(input.colors_1.zw),
unpack_u32(input.colors_2.xy),
unpack_u32(input.colors_2.zw),
unpack_u32(input.colors_3.xy),
unpack_u32(input.colors_3.zw),
unpack_u32(input.colors_4.xy),
unpack_u32(input.colors_4.zw),
);
let offsets_1: vec4<f32> = unpack_u32(input.offsets.xy);
let offsets_2: vec4<f32> = unpack_u32(input.offsets.zw);
var offsets = array<f32, 8>(
offsets_1.x,
offsets_1.y,
offsets_1.z,
offsets_1.w,
offsets_2.x,
offsets_2.y,
offsets_2.z,
offsets_2.w,
);
var last_index = 7;
for (var i: i32 = 0; i <= 7; i++) {
if (offsets[i] >= 1.0) {
last_index = i;
break;
}
}
return gradient(input.raw_position, input.direction, colors, offsets, last_index);
}
fn unpack_u32(color: vec2<u32>) -> vec4<f32> {
let rg: vec2<f32> = unpack2x16float(color.x);
let ba: vec2<f32> = unpack2x16float(color.y);
return vec4<f32>(rg.y, rg.x, ba.y, ba.x);
}
fn random(coords: vec2<f32>) -> f32 {
return fract(sin(dot(coords, vec2(12.9898,78.233))) * 43758.5453);
}

View file

@ -0,0 +1,24 @@
struct SolidVertexInput {
@location(0) position: vec2<f32>,
@location(1) color: vec4<f32>,
}
struct SolidVertexOutput {
@builtin(position) position: vec4<f32>,
@location(0) color: vec4<f32>,
}
@vertex
fn solid_vs_main(input: SolidVertexInput) -> SolidVertexOutput {
var out: SolidVertexOutput;
out.color = input.color;
out.position = globals.transform * vec4<f32>(input.position, 0.0, 1.0);
return out;
}
@fragment
fn solid_fs_main(input: SolidVertexOutput) -> @location(0) vec4<f32> {
return input.color;
}

View file

@ -1,20 +1,15 @@
use crate::core::alignment;
use crate::core::font::{self, Font};
use crate::core::text::{Hit, LineHeight, Shaping};
use crate::core::{Pixels, Point, Rectangle, Size};
use crate::core::{Rectangle, Size};
use crate::graphics::color;
use crate::graphics::text::cache::{self, Cache};
use crate::graphics::text::{font_system, to_color, Editor, Paragraph};
use crate::layer::Text;
use rustc_hash::{FxHashMap, FxHashSet};
use std::borrow::Cow;
use std::cell::RefCell;
use std::collections::hash_map;
use std::hash::{BuildHasher, Hash, Hasher};
use std::sync::Arc;
#[allow(missing_debug_implementations)]
pub struct Pipeline {
font_system: RefCell<glyphon::FontSystem>,
renderers: Vec<glyphon::TextRenderer>,
atlas: glyphon::TextAtlas,
prepare_layer: usize,
@ -28,14 +23,8 @@ impl Pipeline {
format: wgpu::TextureFormat,
) -> Self {
Pipeline {
font_system: RefCell::new(glyphon::FontSystem::new_with_fonts(
[glyphon::fontdb::Source::Binary(Arc::new(
include_bytes!("../fonts/Iced-Icons.ttf").as_slice(),
))]
.into_iter(),
)),
renderers: Vec::new(),
atlas: glyphon::TextAtlas::new(
atlas: glyphon::TextAtlas::with_color_mode(
device,
queue,
format,
@ -51,9 +40,10 @@ impl Pipeline {
}
pub fn load_font(&mut self, bytes: Cow<'static, [u8]>) {
let _ = self.font_system.get_mut().db_mut().load_font_source(
glyphon::fontdb::Source::Binary(Arc::new(bytes.into_owned())),
);
font_system()
.write()
.expect("Write font system")
.load_font(bytes);
self.cache = RefCell::new(Cache::new());
}
@ -63,114 +53,171 @@ impl Pipeline {
device: &wgpu::Device,
queue: &wgpu::Queue,
sections: &[Text<'_>],
bounds: Rectangle,
layer_bounds: Rectangle,
scale_factor: f32,
target_size: Size<u32>,
) -> bool {
) {
if self.renderers.len() <= self.prepare_layer {
self.renderers.push(glyphon::TextRenderer::new(
&mut self.atlas,
device,
Default::default(),
wgpu::MultisampleState::default(),
None,
));
}
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();
if self.prepare_layer == 0 {
cache.trim(Purpose::Drawing);
enum Allocation {
Paragraph(Paragraph),
Editor(Editor),
Cache(cache::KeyHash),
}
let keys: Vec<_> = sections
let allocations: Vec<_> = sections
.iter()
.map(|section| {
let (key, _) = cache.allocate(
font_system,
Key {
content: section.content,
size: section.size,
line_height: f32::from(
section
.line_height
.to_absolute(Pixels(section.size)),
),
font: section.font,
bounds: Size {
width: section.bounds.width,
height: section.bounds.height,
.map(|section| match section {
Text::Paragraph { paragraph, .. } => {
paragraph.upgrade().map(Allocation::Paragraph)
}
Text::Editor { editor, .. } => {
editor.upgrade().map(Allocation::Editor)
}
Text::Cached(text) => {
let (key, _) = cache.allocate(
font_system,
cache::Key {
content: text.content,
size: text.size.into(),
line_height: f32::from(
text.line_height.to_absolute(text.size),
),
font: text.font,
bounds: Size {
width: text.bounds.width,
height: text.bounds.height,
},
shaping: text.shaping,
},
shaping: section.shaping,
},
Purpose::Drawing,
);
);
key
Some(Allocation::Cache(key))
}
})
.collect();
let bounds = bounds * scale_factor;
let layer_bounds = layer_bounds * scale_factor;
let text_areas =
sections
.iter()
.zip(keys.iter())
.filter_map(|(section, key)| {
let entry = cache.get(key).expect("Get cached buffer");
let text_areas = sections.iter().zip(allocations.iter()).filter_map(
|(section, allocation)| {
let (
buffer,
bounds,
horizontal_alignment,
vertical_alignment,
color,
) = match section {
Text::Paragraph {
position, color, ..
} => {
use crate::core::text::Paragraph as _;
let x = section.bounds.x * scale_factor;
let y = section.bounds.y * scale_factor;
let Some(Allocation::Paragraph(paragraph)) = allocation
else {
return None;
};
let max_width = entry.bounds.width * scale_factor;
let total_height = entry.bounds.height * scale_factor;
(
paragraph.buffer(),
Rectangle::new(*position, paragraph.min_bounds()),
paragraph.horizontal_alignment(),
paragraph.vertical_alignment(),
*color,
)
}
Text::Editor {
position, color, ..
} => {
use crate::core::text::Editor as _;
let left = match section.horizontal_alignment {
alignment::Horizontal::Left => x,
alignment::Horizontal::Center => x - max_width / 2.0,
alignment::Horizontal::Right => x - max_width,
};
let Some(Allocation::Editor(editor)) = allocation
else {
return None;
};
let top = match section.vertical_alignment {
alignment::Vertical::Top => y,
alignment::Vertical::Center => y - total_height / 2.0,
alignment::Vertical::Bottom => y - total_height,
};
(
editor.buffer(),
Rectangle::new(*position, editor.bounds()),
alignment::Horizontal::Left,
alignment::Vertical::Top,
*color,
)
}
Text::Cached(text) => {
let Some(Allocation::Cache(key)) = allocation else {
return None;
};
let section_bounds = Rectangle {
x: left,
y: top,
width: section.bounds.width * scale_factor,
height: section.bounds.height * scale_factor,
};
let entry = cache.get(key).expect("Get cached buffer");
let clip_bounds = bounds.intersection(&section_bounds)?;
(
&entry.buffer,
Rectangle::new(
text.bounds.position(),
entry.min_bounds,
),
text.horizontal_alignment,
text.vertical_alignment,
text.color,
)
}
};
Some(glyphon::TextArea {
buffer: &entry.buffer,
left,
top,
scale: scale_factor,
bounds: glyphon::TextBounds {
left: clip_bounds.x as i32,
top: clip_bounds.y as i32,
right: (clip_bounds.x + clip_bounds.width) as i32,
bottom: (clip_bounds.y + clip_bounds.height) as i32,
},
default_color: {
let [r, g, b, a] =
color::pack(section.color).components();
let bounds = bounds * scale_factor;
glyphon::Color::rgba(
(r * 255.0) as u8,
(g * 255.0) as u8,
(b * 255.0) as u8,
(a * 255.0) as u8,
)
},
})
});
let left = match horizontal_alignment {
alignment::Horizontal::Left => bounds.x,
alignment::Horizontal::Center => {
bounds.x - bounds.width / 2.0
}
alignment::Horizontal::Right => bounds.x - bounds.width,
};
let top = match vertical_alignment {
alignment::Vertical::Top => bounds.y,
alignment::Vertical::Center => {
bounds.y - bounds.height / 2.0
}
alignment::Vertical::Bottom => bounds.y - bounds.height,
};
let section_bounds = Rectangle {
x: left,
y: top,
..bounds
};
let clip_bounds = layer_bounds.intersection(&section_bounds)?;
Some(glyphon::TextArea {
buffer,
left,
top,
scale: scale_factor,
bounds: glyphon::TextBounds {
left: clip_bounds.x as i32,
top: clip_bounds.y as i32,
right: (clip_bounds.x + clip_bounds.width) as i32,
bottom: (clip_bounds.y + clip_bounds.height) as i32,
},
default_color: to_color(color),
})
},
);
let result = renderer.prepare(
device,
@ -188,21 +235,11 @@ impl Pipeline {
match result {
Ok(()) => {
self.prepare_layer += 1;
true
}
Err(glyphon::PrepareError::AtlasFull(content_type)) => {
self.prepare_layer = 0;
#[allow(clippy::needless_bool)]
if self.atlas.grow(device, content_type) {
false
} else {
// If the atlas cannot grow, then all bets are off.
// Instead of panicking, we will just pray that the result
// will be somewhat readable...
true
}
Err(glyphon::PrepareError::AtlasFull) => {
// If the atlas cannot grow, then all bets are off.
// Instead of panicking, we will just pray that the result
// will be somewhat readable...
}
}
}
@ -229,278 +266,8 @@ impl Pipeline {
pub fn end_frame(&mut self) {
self.atlas.trim();
self.cache.get_mut().trim();
self.prepare_layer = 0;
}
pub fn trim_measurements(&mut self) {
self.cache.get_mut().trim(Purpose::Measuring);
}
pub fn measure(
&self,
content: &str,
size: f32,
line_height: LineHeight,
font: Font,
bounds: Size,
shaping: Shaping,
) -> Size {
let mut cache = self.cache.borrow_mut();
let line_height = f32::from(line_height.to_absolute(Pixels(size)));
let (_, entry) = cache.allocate(
&mut self.font_system.borrow_mut(),
Key {
content,
size,
line_height,
font,
bounds,
shaping,
},
Purpose::Measuring,
);
entry.bounds
}
pub fn hit_test(
&self,
content: &str,
size: f32,
line_height: LineHeight,
font: Font,
bounds: Size,
shaping: Shaping,
point: Point,
_nearest_only: bool,
) -> Option<Hit> {
let mut cache = self.cache.borrow_mut();
let line_height = f32::from(line_height.to_absolute(Pixels(size)));
let (_, entry) = cache.allocate(
&mut self.font_system.borrow_mut(),
Key {
content,
size,
line_height,
font,
bounds,
shaping,
},
Purpose::Measuring,
);
let cursor = entry.buffer.hit(point.x, point.y)?;
Some(Hit::CharOffset(cursor.index))
}
}
fn measure(buffer: &glyphon::Buffer) -> Size {
let (width, total_lines) = buffer
.layout_runs()
.fold((0.0, 0usize), |(width, total_lines), run| {
(run.line_w.max(width), total_lines + 1)
});
Size::new(width, total_lines as f32 * buffer.metrics().line_height)
}
fn to_family(family: font::Family) -> glyphon::Family<'static> {
match family {
font::Family::Name(name) => glyphon::Family::Name(name),
font::Family::SansSerif => glyphon::Family::SansSerif,
font::Family::Serif => glyphon::Family::Serif,
font::Family::Cursive => glyphon::Family::Cursive,
font::Family::Fantasy => glyphon::Family::Fantasy,
font::Family::Monospace => glyphon::Family::Monospace,
}
}
fn to_weight(weight: font::Weight) -> glyphon::Weight {
match weight {
font::Weight::Thin => glyphon::Weight::THIN,
font::Weight::ExtraLight => glyphon::Weight::EXTRA_LIGHT,
font::Weight::Light => glyphon::Weight::LIGHT,
font::Weight::Normal => glyphon::Weight::NORMAL,
font::Weight::Medium => glyphon::Weight::MEDIUM,
font::Weight::Semibold => glyphon::Weight::SEMIBOLD,
font::Weight::Bold => glyphon::Weight::BOLD,
font::Weight::ExtraBold => glyphon::Weight::EXTRA_BOLD,
font::Weight::Black => glyphon::Weight::BLACK,
}
}
fn to_stretch(stretch: font::Stretch) -> glyphon::Stretch {
match stretch {
font::Stretch::UltraCondensed => glyphon::Stretch::UltraCondensed,
font::Stretch::ExtraCondensed => glyphon::Stretch::ExtraCondensed,
font::Stretch::Condensed => glyphon::Stretch::Condensed,
font::Stretch::SemiCondensed => glyphon::Stretch::SemiCondensed,
font::Stretch::Normal => glyphon::Stretch::Normal,
font::Stretch::SemiExpanded => glyphon::Stretch::SemiExpanded,
font::Stretch::Expanded => glyphon::Stretch::Expanded,
font::Stretch::ExtraExpanded => glyphon::Stretch::ExtraExpanded,
font::Stretch::UltraExpanded => glyphon::Stretch::UltraExpanded,
}
}
fn to_shaping(shaping: Shaping) -> glyphon::Shaping {
match shaping {
Shaping::Basic => glyphon::Shaping::Basic,
Shaping::Advanced => glyphon::Shaping::Advanced,
}
}
struct Cache {
entries: FxHashMap<KeyHash, Entry>,
aliases: FxHashMap<KeyHash, KeyHash>,
recently_measured: FxHashSet<KeyHash>,
recently_drawn: FxHashSet<KeyHash>,
hasher: HashBuilder,
}
struct Entry {
buffer: glyphon::Buffer,
bounds: Size,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Purpose {
Measuring,
Drawing,
}
#[cfg(not(target_arch = "wasm32"))]
type HashBuilder = twox_hash::RandomXxHashBuilder64;
#[cfg(target_arch = "wasm32")]
type HashBuilder = std::hash::BuildHasherDefault<twox_hash::XxHash64>;
impl Cache {
fn new() -> Self {
Self {
entries: FxHashMap::default(),
aliases: FxHashMap::default(),
recently_measured: FxHashSet::default(),
recently_drawn: FxHashSet::default(),
hasher: HashBuilder::default(),
}
}
fn get(&self, key: &KeyHash) -> Option<&Entry> {
self.entries.get(key)
}
fn allocate(
&mut self,
font_system: &mut glyphon::FontSystem,
key: Key<'_>,
purpose: Purpose,
) -> (KeyHash, &mut Entry) {
let hash = key.hash(self.hasher.build_hasher());
let recently_used = match purpose {
Purpose::Measuring => &mut self.recently_measured,
Purpose::Drawing => &mut self.recently_drawn,
};
if let Some(hash) = self.aliases.get(&hash) {
let _ = recently_used.insert(*hash);
return (*hash, self.entries.get_mut(hash).unwrap());
}
if let hash_map::Entry::Vacant(entry) = self.entries.entry(hash) {
let metrics = glyphon::Metrics::new(key.size, key.line_height);
let mut buffer = glyphon::Buffer::new(font_system, metrics);
buffer.set_size(
font_system,
key.bounds.width,
key.bounds.height.max(key.line_height),
);
buffer.set_text(
font_system,
key.content,
glyphon::Attrs::new()
.family(to_family(key.font.family))
.weight(to_weight(key.font.weight))
.stretch(to_stretch(key.font.stretch)),
to_shaping(key.shaping),
);
let bounds = measure(&buffer);
let _ = entry.insert(Entry { buffer, bounds });
for bounds in [
bounds,
Size {
width: key.bounds.width,
..bounds
},
] {
if key.bounds != bounds {
let _ = self.aliases.insert(
Key { bounds, ..key }.hash(self.hasher.build_hasher()),
hash,
);
}
}
}
let _ = recently_used.insert(hash);
(hash, self.entries.get_mut(&hash).unwrap())
}
fn trim(&mut self, purpose: Purpose) {
self.entries.retain(|key, _| {
self.recently_measured.contains(key)
|| self.recently_drawn.contains(key)
});
self.aliases.retain(|_, value| {
self.recently_measured.contains(value)
|| self.recently_drawn.contains(value)
});
match purpose {
Purpose::Measuring => {
self.recently_measured.clear();
}
Purpose::Drawing => {
self.recently_drawn.clear();
}
}
}
}
#[derive(Debug, Clone, Copy)]
struct Key<'a> {
content: &'a str,
size: f32,
line_height: f32,
font: Font,
bounds: Size,
shaping: Shaping,
}
impl Key<'_> {
fn hash<H: Hasher>(self, mut hasher: H) -> KeyHash {
self.content.hash(&mut hasher);
self.size.to_bits().hash(&mut hasher);
self.line_height.to_bits().hash(&mut hasher);
self.font.hash(&mut hasher);
self.bounds.width.to_bits().hash(&mut hasher);
self.bounds.height.to_bits().hash(&mut hasher);
self.shaping.hash(&mut hasher);
hasher.finish()
}
}
type KeyHash = u64;

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];
@ -329,12 +334,12 @@ impl Pipeline {
fn fragment_target(
texture_format: wgpu::TextureFormat,
) -> Option<wgpu::ColorTargetState> {
Some(wgpu::ColorTargetState {
) -> wgpu::ColorTargetState {
wgpu::ColorTargetState {
format: texture_format,
blend: Some(wgpu::BlendState::ALPHA_BLENDING),
write_mask: wgpu::ColorWrites::ALL,
})
}
}
fn primitive_state() -> wgpu::PrimitiveState {
@ -349,7 +354,7 @@ fn multisample_state(
antialiasing: Option<Antialiasing>,
) -> wgpu::MultisampleState {
wgpu::MultisampleState {
count: antialiasing.map(|a| a.sample_count()).unwrap_or(1),
count: antialiasing.map(Antialiasing::sample_count).unwrap_or(1),
mask: !0,
alpha_to_coverage_enabled: false,
}
@ -487,8 +492,10 @@ mod solid {
device.create_shader_module(wgpu::ShaderModuleDescriptor {
label: Some("iced_wgpu.triangle.solid.shader"),
source: wgpu::ShaderSource::Wgsl(
std::borrow::Cow::Borrowed(include_str!(
"shader/triangle.wgsl"
std::borrow::Cow::Borrowed(concat!(
include_str!("shader/triangle.wgsl"),
"\n",
include_str!("shader/triangle/solid.wgsl"),
)),
),
});
@ -519,7 +526,7 @@ mod solid {
fragment: Some(wgpu::FragmentState {
module: &shader,
entry_point: "solid_fs_main",
targets: &[triangle::fragment_target(format)],
targets: &[Some(triangle::fragment_target(format))],
}),
primitive: triangle::primitive_state(),
depth_stencil: None,
@ -537,6 +544,7 @@ mod solid {
}
mod gradient {
use crate::graphics::color;
use crate::graphics::mesh;
use crate::graphics::Antialiasing;
use crate::triangle;
@ -633,9 +641,31 @@ mod gradient {
device.create_shader_module(wgpu::ShaderModuleDescriptor {
label: Some("iced_wgpu.triangle.gradient.shader"),
source: wgpu::ShaderSource::Wgsl(
std::borrow::Cow::Borrowed(include_str!(
"shader/triangle.wgsl"
)),
std::borrow::Cow::Borrowed(
if color::GAMMA_CORRECTION {
concat!(
include_str!("shader/triangle.wgsl"),
"\n",
include_str!(
"shader/triangle/gradient.wgsl"
),
"\n",
include_str!("shader/color/oklab.wgsl")
)
} else {
concat!(
include_str!("shader/triangle.wgsl"),
"\n",
include_str!(
"shader/triangle/gradient.wgsl"
),
"\n",
include_str!(
"shader/color/linear_rgb.wgsl"
)
)
},
),
),
});
@ -673,7 +703,7 @@ mod gradient {
fragment: Some(wgpu::FragmentState {
module: &shader,
entry_point: "gradient_fs_main",
targets: &[triangle::fragment_target(format)],
targets: &[Some(triangle::fragment_target(format))],
}),
primitive: triangle::primitive_state(),
depth_stencil: None,

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 futures::stream::{self, StreamExt};
use raw_window_handle::{HasRawDisplayHandle, HasRawWindowHandle};
use std::marker::PhantomData;
@ -37,7 +35,7 @@ impl<Theme> Compositor<Theme> {
..Default::default()
});
log::info!("{:#?}", settings);
log::info!("{settings:#?}");
#[cfg(not(target_arch = "wasm32"))]
if log::max_level() >= log::LevelFilter::Info {
@ -45,7 +43,7 @@ impl<Theme> Compositor<Theme> {
.enumerate_adapters(settings.internal_backend)
.map(|adapter| adapter.get_info())
.collect();
log::info!("Available adapters: {:#?}", available_adapters);
log::info!("Available adapters: {available_adapters:#?}");
}
#[allow(unsafe_code)]
@ -85,7 +83,7 @@ impl<Theme> Compositor<Theme> {
})
})?;
log::info!("Selected format: {:?}", format);
log::info!("Selected format: {format:?}");
#[cfg(target_arch = "wasm32")]
let limits = [wgpu::Limits::downlevel_webgl2_defaults()
@ -95,14 +93,15 @@ impl<Theme> Compositor<Theme> {
let limits =
[wgpu::Limits::default(), wgpu::Limits::downlevel_defaults()];
let limits = limits.into_iter().map(|limits| wgpu::Limits {
let mut limits = limits.into_iter().map(|limits| wgpu::Limits {
max_bind_groups: 2,
..limits
});
let (device, queue) = stream::iter(limits)
.filter_map(|limits| async {
adapter.request_device(
let (device, queue) =
loop {
let limits = limits.next()?;
let device = adapter.request_device(
&wgpu::DeviceDescriptor {
label: Some(
"iced_wgpu::window::compositor device descriptor",
@ -111,11 +110,12 @@ impl<Theme> Compositor<Theme> {
limits,
},
None,
).await.ok()
})
.boxed()
.next()
.await?;
).await.ok();
if let Some(device) = device {
break Some(device);
}
}?;
Some(Compositor {
instance,
@ -178,6 +178,7 @@ pub fn present<Theme, T: AsRef<str>>(
&compositor.queue,
&mut encoder,
Some(background_color),
frame.texture.format(),
view,
primitives,
viewport,
@ -216,11 +217,22 @@ impl<Theme> graphics::Compositor for Compositor<Theme> {
) -> Result<(Self, Self::Renderer), Error> {
let (compositor, backend) = new(settings, compatible_window)?;
Ok((compositor, Renderer::new(backend)))
Ok((
compositor,
Renderer::new(
backend,
settings.default_font,
settings.default_text_size,
),
))
}
fn renderer(&self) -> Self::Renderer {
Renderer::new(self.create_backend())
Renderer::new(
self.create_backend(),
self.settings.default_font,
self.settings.default_text_size,
)
}
fn create_surface<W: HasRawWindowHandle + HasRawDisplayHandle>(
@ -354,6 +366,7 @@ pub fn screenshot<Theme, T: AsRef<str>>(
&compositor.queue,
&mut encoder,
Some(background_color),
texture.format(),
&view,
primitives,
viewport,