summaryrefslogtreecommitdiffstats
path: root/wgpu
diff options
context:
space:
mode:
authorLibravatar gigas002 <gigas002@pm.me>2024-05-08 19:16:20 +0900
committerLibravatar gigas002 <gigas002@pm.me>2024-05-08 19:16:20 +0900
commitff26fb7df43bd241253af85e0621e2cc030e9cec (patch)
tree2b3272bb178f8757169511966589fe6a106733f4 /wgpu
parent0ebe0629cef37aee5c48b9409fc36618a3a3e60d (diff)
parent477887b3870aa5fbdab96c3a06f3b930462d7842 (diff)
downloadiced-ff26fb7df43bd241253af85e0621e2cc030e9cec.tar.gz
iced-ff26fb7df43bd241253af85e0621e2cc030e9cec.tar.bz2
iced-ff26fb7df43bd241253af85e0621e2cc030e9cec.zip
Merge branch 'iced-rs-master' into viewer_content_fit
Diffstat (limited to 'wgpu')
-rw-r--r--wgpu/src/color.rs68
-rw-r--r--wgpu/src/engine.rs7
-rw-r--r--wgpu/src/geometry.rs13
-rw-r--r--wgpu/src/image/atlas.rs53
-rw-r--r--wgpu/src/image/atlas/allocator.rs4
-rw-r--r--wgpu/src/image/atlas/layer.rs8
-rw-r--r--wgpu/src/image/cache.rs43
-rw-r--r--wgpu/src/image/mod.rs91
-rw-r--r--wgpu/src/image/raster.rs17
-rw-r--r--wgpu/src/image/vector.rs8
-rw-r--r--wgpu/src/layer.rs13
-rw-r--r--wgpu/src/lib.rs42
-rw-r--r--wgpu/src/primitive.rs2
-rw-r--r--wgpu/src/settings.rs26
-rw-r--r--wgpu/src/shader/image.wgsl37
-rw-r--r--wgpu/src/text.rs178
-rw-r--r--wgpu/src/triangle.rs16
-rw-r--r--wgpu/src/window/compositor.rs26
18 files changed, 462 insertions, 190 deletions
diff --git a/wgpu/src/color.rs b/wgpu/src/color.rs
index 890f3f89..9d593d9c 100644
--- a/wgpu/src/color.rs
+++ b/wgpu/src/color.rs
@@ -1,5 +1,7 @@
use std::borrow::Cow;
+use wgpu::util::DeviceExt;
+
pub fn convert(
device: &wgpu::Device,
encoder: &mut wgpu::CommandEncoder,
@@ -15,28 +17,58 @@ pub fn convert(
..wgpu::SamplerDescriptor::default()
});
- //sampler in 0
- let sampler_layout =
+ #[derive(Debug, Clone, Copy, bytemuck::Zeroable, bytemuck::Pod)]
+ #[repr(C)]
+ struct Ratio {
+ u: f32,
+ v: f32,
+ }
+
+ let ratio = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
+ label: Some("iced-wgpu::triangle::msaa ratio"),
+ contents: bytemuck::bytes_of(&Ratio { u: 1.0, v: 1.0 }),
+ usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::UNIFORM,
+ });
+
+ let constant_layout =
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label: Some("iced_wgpu.offscreen.blit.sampler_layout"),
- entries: &[wgpu::BindGroupLayoutEntry {
- binding: 0,
- visibility: wgpu::ShaderStages::FRAGMENT,
- ty: wgpu::BindingType::Sampler(
- wgpu::SamplerBindingType::NonFiltering,
- ),
- count: None,
- }],
+ entries: &[
+ wgpu::BindGroupLayoutEntry {
+ binding: 0,
+ visibility: wgpu::ShaderStages::FRAGMENT,
+ ty: wgpu::BindingType::Sampler(
+ wgpu::SamplerBindingType::NonFiltering,
+ ),
+ count: None,
+ },
+ wgpu::BindGroupLayoutEntry {
+ binding: 1,
+ visibility: wgpu::ShaderStages::VERTEX,
+ ty: wgpu::BindingType::Buffer {
+ ty: wgpu::BufferBindingType::Uniform,
+ has_dynamic_offset: false,
+ min_binding_size: None,
+ },
+ count: None,
+ },
+ ],
});
- let sampler_bind_group =
+ let constant_bind_group =
device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some("iced_wgpu.offscreen.sampler.bind_group"),
- layout: &sampler_layout,
- entries: &[wgpu::BindGroupEntry {
- binding: 0,
- resource: wgpu::BindingResource::Sampler(&sampler),
- }],
+ layout: &constant_layout,
+ entries: &[
+ wgpu::BindGroupEntry {
+ binding: 0,
+ resource: wgpu::BindingResource::Sampler(&sampler),
+ },
+ wgpu::BindGroupEntry {
+ binding: 1,
+ resource: ratio.as_entire_binding(),
+ },
+ ],
});
let texture_layout =
@@ -59,7 +91,7 @@ pub fn convert(
let pipeline_layout =
device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: Some("iced_wgpu.offscreen.blit.pipeline_layout"),
- bind_group_layouts: &[&sampler_layout, &texture_layout],
+ bind_group_layouts: &[&constant_layout, &texture_layout],
push_constant_ranges: &[],
});
@@ -152,7 +184,7 @@ pub fn convert(
});
pass.set_pipeline(&pipeline);
- pass.set_bind_group(0, &sampler_bind_group, &[]);
+ pass.set_bind_group(0, &constant_bind_group, &[]);
pass.set_bind_group(1, &texture_bind_group, &[]);
pass.draw(0..6, 0..1);
diff --git a/wgpu/src/engine.rs b/wgpu/src/engine.rs
index 96cd6db8..782fd58c 100644
--- a/wgpu/src/engine.rs
+++ b/wgpu/src/engine.rs
@@ -59,8 +59,11 @@ impl Engine {
}
#[cfg(any(feature = "image", feature = "svg"))]
- pub fn image_cache(&self) -> &crate::image::cache::Shared {
- self.image_pipeline.cache()
+ pub fn create_image_cache(
+ &self,
+ device: &wgpu::Device,
+ ) -> crate::image::Cache {
+ self.image_pipeline.create_cache(device)
}
pub fn submit(
diff --git a/wgpu/src/geometry.rs b/wgpu/src/geometry.rs
index 60967082..f6213e1d 100644
--- a/wgpu/src/geometry.rs
+++ b/wgpu/src/geometry.rs
@@ -3,6 +3,7 @@ use crate::core::text::LineHeight;
use crate::core::{
Pixels, Point, Radians, Rectangle, Size, Transformation, Vector,
};
+use crate::graphics::cache::{self, Cached};
use crate::graphics::color;
use crate::graphics::geometry::fill::{self, Fill};
use crate::graphics::geometry::{
@@ -10,7 +11,7 @@ use crate::graphics::geometry::{
};
use crate::graphics::gradient::{self, Gradient};
use crate::graphics::mesh::{self, Mesh};
-use crate::graphics::{self, Cached, Text};
+use crate::graphics::{self, Text};
use crate::text;
use crate::triangle;
@@ -38,7 +39,11 @@ impl Cached for Geometry {
Geometry::Cached(cache.clone())
}
- fn cache(self, previous: Option<Self::Cache>) -> Self::Cache {
+ fn cache(
+ self,
+ group: cache::Group,
+ previous: Option<Self::Cache>,
+ ) -> Self::Cache {
match self {
Self::Live { meshes, text } => {
if let Some(mut previous) = previous {
@@ -51,14 +56,14 @@ impl Cached for Geometry {
if let Some(cache) = &mut previous.text {
cache.update(text);
} else {
- previous.text = text::Cache::new(text);
+ previous.text = text::Cache::new(group, text);
}
previous
} else {
Cache {
meshes: triangle::Cache::new(meshes),
- text: text::Cache::new(text),
+ text: text::Cache::new(group, text),
}
}
}
diff --git a/wgpu/src/image/atlas.rs b/wgpu/src/image/atlas.rs
index ea36e06d..a1ec0d7b 100644
--- a/wgpu/src/image/atlas.rs
+++ b/wgpu/src/image/atlas.rs
@@ -15,15 +15,23 @@ pub const SIZE: u32 = 2048;
use crate::core::Size;
use crate::graphics::color;
+use std::sync::Arc;
+
#[derive(Debug)]
pub struct Atlas {
texture: wgpu::Texture,
texture_view: wgpu::TextureView,
+ texture_bind_group: wgpu::BindGroup,
+ texture_layout: Arc<wgpu::BindGroupLayout>,
layers: Vec<Layer>,
}
impl Atlas {
- pub fn new(device: &wgpu::Device, backend: wgpu::Backend) -> Self {
+ pub fn new(
+ device: &wgpu::Device,
+ backend: wgpu::Backend,
+ texture_layout: Arc<wgpu::BindGroupLayout>,
+ ) -> Self {
let layers = match backend {
// On the GL backend we start with 2 layers, to help wgpu figure
// out that this texture is `GL_TEXTURE_2D_ARRAY` rather than `GL_TEXTURE_2D`
@@ -60,15 +68,27 @@ impl Atlas {
..Default::default()
});
+ let texture_bind_group =
+ device.create_bind_group(&wgpu::BindGroupDescriptor {
+ label: Some("iced_wgpu::image texture atlas bind group"),
+ layout: &texture_layout,
+ entries: &[wgpu::BindGroupEntry {
+ binding: 0,
+ resource: wgpu::BindingResource::TextureView(&texture_view),
+ }],
+ });
+
Atlas {
texture,
texture_view,
+ texture_bind_group,
+ texture_layout,
layers,
}
}
- pub fn view(&self) -> &wgpu::TextureView {
- &self.texture_view
+ pub fn bind_group(&self) -> &wgpu::BindGroup {
+ &self.texture_bind_group
}
pub fn layer_count(&self) -> usize {
@@ -94,7 +114,7 @@ impl Atlas {
entry
};
- log::info!("Allocated atlas entry: {entry:?}");
+ log::debug!("Allocated atlas entry: {entry:?}");
// It is a webgpu requirement that:
// BufferCopyView.layout.bytes_per_row % wgpu::COPY_BYTES_PER_ROW_ALIGNMENT == 0
@@ -147,13 +167,20 @@ impl Atlas {
}
}
- log::info!("Current atlas: {self:?}");
+ if log::log_enabled!(log::Level::Debug) {
+ log::debug!(
+ "Atlas layers: {} (busy: {}, allocations: {})",
+ self.layer_count(),
+ self.layers.iter().filter(|layer| !layer.is_empty()).count(),
+ self.layers.iter().map(Layer::allocations).sum::<usize>(),
+ );
+ }
Some(entry)
}
pub fn remove(&mut self, entry: &Entry) {
- log::info!("Removing atlas entry: {entry:?}");
+ log::debug!("Removing atlas entry: {entry:?}");
match entry {
Entry::Contiguous(allocation) => {
@@ -266,7 +293,7 @@ impl Atlas {
}
fn deallocate(&mut self, allocation: &Allocation) {
- log::info!("Deallocating atlas: {allocation:?}");
+ log::debug!("Deallocating atlas: {allocation:?}");
match allocation {
Allocation::Full { layer } => {
@@ -414,5 +441,17 @@ impl Atlas {
dimension: Some(wgpu::TextureViewDimension::D2Array),
..Default::default()
});
+
+ self.texture_bind_group =
+ device.create_bind_group(&wgpu::BindGroupDescriptor {
+ label: Some("iced_wgpu::image texture atlas bind group"),
+ layout: &self.texture_layout,
+ entries: &[wgpu::BindGroupEntry {
+ binding: 0,
+ resource: wgpu::BindingResource::TextureView(
+ &self.texture_view,
+ ),
+ }],
+ });
}
}
diff --git a/wgpu/src/image/atlas/allocator.rs b/wgpu/src/image/atlas/allocator.rs
index 204a5c26..a51ac1f5 100644
--- a/wgpu/src/image/atlas/allocator.rs
+++ b/wgpu/src/image/atlas/allocator.rs
@@ -33,6 +33,10 @@ impl Allocator {
pub fn is_empty(&self) -> bool {
self.allocations == 0
}
+
+ pub fn allocations(&self) -> usize {
+ self.allocations
+ }
}
pub struct Region {
diff --git a/wgpu/src/image/atlas/layer.rs b/wgpu/src/image/atlas/layer.rs
index cf089601..fd6788d9 100644
--- a/wgpu/src/image/atlas/layer.rs
+++ b/wgpu/src/image/atlas/layer.rs
@@ -11,4 +11,12 @@ impl Layer {
pub fn is_empty(&self) -> bool {
matches!(self, Layer::Empty)
}
+
+ pub fn allocations(&self) -> usize {
+ match self {
+ Layer::Empty => 0,
+ Layer::Busy(allocator) => allocator.allocations(),
+ Layer::Full => 1,
+ }
+ }
}
diff --git a/wgpu/src/image/cache.rs b/wgpu/src/image/cache.rs
index 32118ed6..94f7071d 100644
--- a/wgpu/src/image/cache.rs
+++ b/wgpu/src/image/cache.rs
@@ -1,8 +1,7 @@
use crate::core::{self, Size};
use crate::image::atlas::{self, Atlas};
-use std::cell::{RefCell, RefMut};
-use std::rc::Rc;
+use std::sync::Arc;
#[derive(Debug)]
pub struct Cache {
@@ -14,9 +13,13 @@ pub struct Cache {
}
impl Cache {
- pub fn new(device: &wgpu::Device, backend: wgpu::Backend) -> Self {
+ pub fn new(
+ device: &wgpu::Device,
+ backend: wgpu::Backend,
+ layout: Arc<wgpu::BindGroupLayout>,
+ ) -> Self {
Self {
- atlas: Atlas::new(device, backend),
+ atlas: Atlas::new(device, backend, layout),
#[cfg(feature = "image")]
raster: crate::image::raster::Cache::default(),
#[cfg(feature = "svg")]
@@ -24,6 +27,10 @@ impl Cache {
}
}
+ pub fn bind_group(&self) -> &wgpu::BindGroup {
+ self.atlas.bind_group()
+ }
+
pub fn layer_count(&self) -> usize {
self.atlas.layer_count()
}
@@ -69,21 +76,6 @@ impl Cache {
)
}
- pub fn create_bind_group(
- &self,
- device: &wgpu::Device,
- layout: &wgpu::BindGroupLayout,
- ) -> wgpu::BindGroup {
- device.create_bind_group(&wgpu::BindGroupDescriptor {
- label: Some("iced_wgpu::image texture atlas bind group"),
- layout,
- entries: &[wgpu::BindGroupEntry {
- binding: 0,
- resource: wgpu::BindingResource::TextureView(self.atlas.view()),
- }],
- })
- }
-
pub fn trim(&mut self) {
#[cfg(feature = "image")]
self.raster.trim(&mut self.atlas);
@@ -92,16 +84,3 @@ impl Cache {
self.vector.trim(&mut self.atlas);
}
}
-
-#[derive(Debug, Clone)]
-pub struct Shared(Rc<RefCell<Cache>>);
-
-impl Shared {
- pub fn new(cache: Cache) -> Self {
- Self(Rc::new(RefCell::new(cache)))
- }
-
- pub fn lock(&self) -> RefMut<'_, Cache> {
- self.0.borrow_mut()
- }
-}
diff --git a/wgpu/src/image/mod.rs b/wgpu/src/image/mod.rs
index 86731cbf..daa2fe16 100644
--- a/wgpu/src/image/mod.rs
+++ b/wgpu/src/image/mod.rs
@@ -13,7 +13,9 @@ use crate::core::{Rectangle, Size, Transformation};
use crate::Buffer;
use bytemuck::{Pod, Zeroable};
+
use std::mem;
+use std::sync::Arc;
pub use crate::graphics::Image;
@@ -22,13 +24,11 @@ pub type Batch = Vec<Image>;
#[derive(Debug)]
pub struct Pipeline {
pipeline: wgpu::RenderPipeline,
+ backend: wgpu::Backend,
nearest_sampler: wgpu::Sampler,
linear_sampler: wgpu::Sampler,
- texture: wgpu::BindGroup,
- texture_version: usize,
- texture_layout: wgpu::BindGroupLayout,
+ texture_layout: Arc<wgpu::BindGroupLayout>,
constant_layout: wgpu::BindGroupLayout,
- cache: cache::Shared,
layers: Vec<Layer>,
prepare_layer: usize,
}
@@ -135,14 +135,20 @@ impl Pipeline {
attributes: &wgpu::vertex_attr_array!(
// Position
0 => Float32x2,
- // Scale
+ // Center
1 => Float32x2,
- // Atlas position
+ // Scale
2 => Float32x2,
+ // Rotation
+ 3 => Float32,
+ // Opacity
+ 4 => Float32,
+ // Atlas position
+ 5 => Float32x2,
// Atlas scale
- 3 => Float32x2,
+ 6 => Float32x2,
// Layer
- 4 => Sint32,
+ 7 => Sint32,
),
}],
},
@@ -180,25 +186,20 @@ impl Pipeline {
multiview: None,
});
- let cache = Cache::new(device, backend);
- let texture = cache.create_bind_group(device, &texture_layout);
-
Pipeline {
pipeline,
+ backend,
nearest_sampler,
linear_sampler,
- texture,
- texture_version: cache.layer_count(),
- texture_layout,
+ texture_layout: Arc::new(texture_layout),
constant_layout,
- cache: cache::Shared::new(cache),
layers: Vec::new(),
prepare_layer: 0,
}
}
- pub fn cache(&self) -> &cache::Shared {
- &self.cache
+ pub fn create_cache(&self, device: &wgpu::Device) -> Cache {
+ Cache::new(device, self.backend, self.texture_layout.clone())
}
pub fn prepare(
@@ -206,6 +207,7 @@ impl Pipeline {
device: &wgpu::Device,
encoder: &mut wgpu::CommandEncoder,
belt: &mut wgpu::util::StagingBelt,
+ cache: &mut Cache,
images: &Batch,
transformation: Transformation,
scale: f32,
@@ -215,8 +217,6 @@ impl Pipeline {
let nearest_instances: &mut Vec<Instance> = &mut Vec::new();
let linear_instances: &mut Vec<Instance> = &mut Vec::new();
- let mut cache = self.cache.lock();
-
for image in images {
match &image {
#[cfg(feature = "image")]
@@ -224,6 +224,8 @@ impl Pipeline {
handle,
filter_method,
bounds,
+ rotation,
+ opacity,
} => {
if let Some(atlas_entry) =
cache.upload_raster(device, encoder, handle)
@@ -231,6 +233,8 @@ impl Pipeline {
add_instances(
[bounds.x, bounds.y],
[bounds.width, bounds.height],
+ f32::from(*rotation),
+ *opacity,
atlas_entry,
match filter_method {
crate::core::image::FilterMethod::Nearest => {
@@ -251,6 +255,8 @@ impl Pipeline {
handle,
color,
bounds,
+ rotation,
+ opacity,
} => {
let size = [bounds.width, bounds.height];
@@ -260,6 +266,8 @@ impl Pipeline {
add_instances(
[bounds.x, bounds.y],
size,
+ f32::from(*rotation),
+ *opacity,
atlas_entry,
nearest_instances,
);
@@ -274,16 +282,6 @@ impl Pipeline {
return;
}
- let texture_version = cache.layer_count();
-
- if self.texture_version != texture_version {
- log::info!("Atlas has grown. Recreating bind group...");
-
- self.texture =
- cache.create_bind_group(device, &self.texture_layout);
- self.texture_version = texture_version;
- }
-
if self.layers.len() <= self.prepare_layer {
self.layers.push(Layer::new(
device,
@@ -309,6 +307,7 @@ impl Pipeline {
pub fn render<'a>(
&'a self,
+ cache: &'a Cache,
layer: usize,
bounds: Rectangle<u32>,
render_pass: &mut wgpu::RenderPass<'a>,
@@ -323,14 +322,13 @@ impl Pipeline {
bounds.height,
);
- render_pass.set_bind_group(1, &self.texture, &[]);
+ render_pass.set_bind_group(1, cache.bind_group(), &[]);
layer.render(render_pass);
}
}
pub fn end_frame(&mut self) {
- self.cache.lock().trim();
self.prepare_layer = 0;
}
}
@@ -487,7 +485,10 @@ impl Data {
#[derive(Debug, Clone, Copy, Zeroable, Pod)]
struct Instance {
_position: [f32; 2],
+ _center: [f32; 2],
_size: [f32; 2],
+ _rotation: f32,
+ _opacity: f32,
_position_in_atlas: [f32; 2],
_size_in_atlas: [f32; 2],
_layer: u32,
@@ -506,12 +507,27 @@ struct Uniforms {
fn add_instances(
image_position: [f32; 2],
image_size: [f32; 2],
+ rotation: f32,
+ opacity: f32,
entry: &atlas::Entry,
instances: &mut Vec<Instance>,
) {
+ let center = [
+ image_position[0] + image_size[0] / 2.0,
+ image_position[1] + image_size[1] / 2.0,
+ ];
+
match entry {
atlas::Entry::Contiguous(allocation) => {
- add_instance(image_position, image_size, allocation, instances);
+ add_instance(
+ image_position,
+ center,
+ image_size,
+ rotation,
+ opacity,
+ allocation,
+ instances,
+ );
}
atlas::Entry::Fragmented { fragments, size } => {
let scaling_x = image_size[0] / size.width as f32;
@@ -537,7 +553,10 @@ fn add_instances(
fragment_height as f32 * scaling_y,
];
- add_instance(position, size, allocation, instances);
+ add_instance(
+ position, center, size, rotation, opacity, allocation,
+ instances,
+ );
}
}
}
@@ -546,7 +565,10 @@ fn add_instances(
#[inline]
fn add_instance(
position: [f32; 2],
+ center: [f32; 2],
size: [f32; 2],
+ rotation: f32,
+ opacity: f32,
allocation: &atlas::Allocation,
instances: &mut Vec<Instance>,
) {
@@ -556,7 +578,10 @@ fn add_instance(
let instance = Instance {
_position: position,
+ _center: center,
_size: size,
+ _rotation: rotation,
+ _opacity: opacity,
_position_in_atlas: [
(x as f32 + 0.5) / atlas::SIZE as f32,
(y as f32 + 0.5) / atlas::SIZE as f32,
diff --git a/wgpu/src/image/raster.rs b/wgpu/src/image/raster.rs
index 441b294f..4d3c3125 100644
--- a/wgpu/src/image/raster.rs
+++ b/wgpu/src/image/raster.rs
@@ -10,7 +10,7 @@ use rustc_hash::{FxHashMap, FxHashSet};
#[derive(Debug)]
pub enum Memory {
/// Image data on host
- Host(image_rs::ImageBuffer<image_rs::Rgba<u8>, Vec<u8>>),
+ Host(image_rs::ImageBuffer<image_rs::Rgba<u8>, image::Bytes>),
/// Storage entry
Device(atlas::Entry),
/// Image not found
@@ -38,8 +38,9 @@ impl Memory {
/// Caches image raster data
#[derive(Debug, Default)]
pub struct Cache {
- map: FxHashMap<u64, Memory>,
- hits: FxHashSet<u64>,
+ map: FxHashMap<image::Id, Memory>,
+ hits: FxHashSet<image::Id>,
+ should_trim: bool,
}
impl Cache {
@@ -50,11 +51,13 @@ impl Cache {
}
let memory = match graphics::image::load(handle) {
- Ok(image) => Memory::Host(image.to_rgba8()),
+ Ok(image) => Memory::Host(image),
Err(image_rs::error::ImageError::IoError(_)) => Memory::NotFound,
Err(_) => Memory::Invalid,
};
+ self.should_trim = true;
+
self.insert(handle, memory);
self.get(handle).unwrap()
}
@@ -86,6 +89,11 @@ impl Cache {
/// Trim cache misses from cache
pub fn trim(&mut self, atlas: &mut Atlas) {
+ // Only trim if new entries have landed in the `Cache`
+ if !self.should_trim {
+ return;
+ }
+
let hits = &self.hits;
self.map.retain(|k, memory| {
@@ -101,6 +109,7 @@ impl Cache {
});
self.hits.clear();
+ self.should_trim = false;
}
fn get(&mut self, handle: &image::Handle) -> Option<&mut Memory> {
diff --git a/wgpu/src/image/vector.rs b/wgpu/src/image/vector.rs
index d681b2e6..c6d829af 100644
--- a/wgpu/src/image/vector.rs
+++ b/wgpu/src/image/vector.rs
@@ -37,6 +37,7 @@ pub struct Cache {
rasterized: FxHashMap<(u64, u32, u32, ColorFilter), atlas::Entry>,
svg_hits: FxHashSet<u64>,
rasterized_hits: FxHashSet<(u64, u32, u32, ColorFilter)>,
+ should_trim: bool,
}
type ColorFilter = Option<[u8; 4]>;
@@ -76,6 +77,8 @@ impl Cache {
}
}
+ self.should_trim = true;
+
let _ = self.svgs.insert(handle.id(), svg);
self.svgs.get(&handle.id()).unwrap()
}
@@ -176,6 +179,10 @@ impl Cache {
/// Load svg and upload raster data
pub fn trim(&mut self, atlas: &mut Atlas) {
+ if !self.should_trim {
+ return;
+ }
+
let svg_hits = &self.svg_hits;
let rasterized_hits = &self.rasterized_hits;
@@ -191,6 +198,7 @@ impl Cache {
});
self.svg_hits.clear();
self.rasterized_hits.clear();
+ self.should_trim = false;
}
}
diff --git a/wgpu/src/layer.rs b/wgpu/src/layer.rs
index 9526c5a8..9551311d 100644
--- a/wgpu/src/layer.rs
+++ b/wgpu/src/layer.rs
@@ -1,5 +1,6 @@
-use crate::core::renderer;
-use crate::core::{Background, Color, Point, Rectangle, Transformation};
+use crate::core::{
+ renderer, Background, Color, Point, Radians, Rectangle, Transformation,
+};
use crate::graphics;
use crate::graphics::color;
use crate::graphics::layer;
@@ -117,11 +118,15 @@ impl Layer {
filter_method: crate::core::image::FilterMethod,
bounds: Rectangle,
transformation: Transformation,
+ rotation: Radians,
+ opacity: f32,
) {
let image = Image::Raster {
handle,
filter_method,
bounds: bounds * transformation,
+ rotation,
+ opacity,
};
self.images.push(image);
@@ -133,11 +138,15 @@ impl Layer {
color: Option<Color>,
bounds: Rectangle,
transformation: Transformation,
+ rotation: Radians,
+ opacity: f32,
) {
let svg = Image::Vector {
handle,
color,
bounds: bounds * transformation,
+ rotation,
+ opacity,
};
self.images.push(svg);
diff --git a/wgpu/src/lib.rs b/wgpu/src/lib.rs
index 178522de..1fbdbe9a 100644
--- a/wgpu/src/lib.rs
+++ b/wgpu/src/lib.rs
@@ -62,6 +62,7 @@ pub use geometry::Geometry;
use crate::core::{
Background, Color, Font, Pixels, Point, Rectangle, Size, Transformation,
+ Vector,
};
use crate::graphics::text::{Editor, Paragraph};
use crate::graphics::Viewport;
@@ -81,11 +82,12 @@ pub struct Renderer {
// TODO: Centralize all the image feature handling
#[cfg(any(feature = "svg", feature = "image"))]
- image_cache: image::cache::Shared,
+ image_cache: std::cell::RefCell<image::Cache>,
}
impl Renderer {
pub fn new(
+ _device: &wgpu::Device,
_engine: &Engine,
default_font: Font,
default_text_size: Pixels,
@@ -99,7 +101,9 @@ impl Renderer {
text_storage: text::Storage::new(),
#[cfg(any(feature = "svg", feature = "image"))]
- image_cache: _engine.image_cache().clone(),
+ image_cache: std::cell::RefCell::new(
+ _engine.create_image_cache(_device),
+ ),
}
}
@@ -121,6 +125,9 @@ impl Renderer {
self.triangle_storage.trim();
self.text_storage.trim();
+
+ #[cfg(any(feature = "svg", feature = "image"))]
+ self.image_cache.borrow_mut().trim();
}
fn prepare(
@@ -190,6 +197,7 @@ impl Renderer {
device,
encoder,
&mut engine.staging_belt,
+ &mut self.image_cache.borrow_mut(),
&layer.images,
viewport.projection(),
scale_factor,
@@ -245,6 +253,8 @@ impl Renderer {
#[cfg(any(feature = "svg", feature = "image"))]
let mut image_layer = 0;
+ #[cfg(any(feature = "svg", feature = "image"))]
+ let image_cache = self.image_cache.borrow();
let scale_factor = viewport.scale_factor() as f32;
let physical_bounds = Rectangle::<f32>::from(Rectangle::with_size(
@@ -358,6 +368,7 @@ impl Renderer {
#[cfg(any(feature = "svg", feature = "image"))]
if !layer.images.is_empty() {
engine.image_pipeline.render(
+ &image_cache,
image_layer,
scissor_rect,
&mut render_pass,
@@ -378,7 +389,6 @@ impl Renderer {
use crate::core::alignment;
use crate::core::text::Renderer as _;
use crate::core::Renderer as _;
- use crate::core::Vector;
self.with_layer(
Rectangle::with_size(viewport.logical_size()),
@@ -509,7 +519,7 @@ impl core::image::Renderer for Renderer {
type Handle = core::image::Handle;
fn measure_image(&self, handle: &Self::Handle) -> Size<u32> {
- self.image_cache.lock().measure_image(handle)
+ self.image_cache.borrow_mut().measure_image(handle)
}
fn draw_image(
@@ -517,16 +527,25 @@ impl core::image::Renderer for Renderer {
handle: Self::Handle,
filter_method: core::image::FilterMethod,
bounds: Rectangle,
+ rotation: core::Radians,
+ opacity: f32,
) {
let (layer, transformation) = self.layers.current_mut();
- layer.draw_image(handle, filter_method, bounds, transformation);
+ layer.draw_image(
+ handle,
+ filter_method,
+ bounds,
+ transformation,
+ rotation,
+ opacity,
+ );
}
}
#[cfg(feature = "svg")]
impl core::svg::Renderer for Renderer {
fn measure_svg(&self, handle: &core::svg::Handle) -> Size<u32> {
- self.image_cache.lock().measure_svg(handle)
+ self.image_cache.borrow_mut().measure_svg(handle)
}
fn draw_svg(
@@ -534,9 +553,18 @@ impl core::svg::Renderer for Renderer {
handle: core::svg::Handle,
color_filter: Option<Color>,
bounds: Rectangle,
+ rotation: core::Radians,
+ opacity: f32,
) {
let (layer, transformation) = self.layers.current_mut();
- layer.draw_svg(handle, color_filter, bounds, transformation);
+ layer.draw_svg(
+ handle,
+ color_filter,
+ bounds,
+ transformation,
+ rotation,
+ opacity,
+ );
}
}
diff --git a/wgpu/src/primitive.rs b/wgpu/src/primitive.rs
index 1313e752..8641f27a 100644
--- a/wgpu/src/primitive.rs
+++ b/wgpu/src/primitive.rs
@@ -67,7 +67,7 @@ pub struct Storage {
impl Storage {
/// Returns `true` if `Storage` contains a type `T`.
pub fn has<T: 'static>(&self) -> bool {
- self.pipelines.get(&TypeId::of::<T>()).is_some()
+ self.pipelines.contains_key(&TypeId::of::<T>())
}
/// Inserts the data `T` in to [`Storage`].
diff --git a/wgpu/src/settings.rs b/wgpu/src/settings.rs
index a6aea0a5..b3c3cf6a 100644
--- a/wgpu/src/settings.rs
+++ b/wgpu/src/settings.rs
@@ -51,3 +51,29 @@ impl From<graphics::Settings> for Settings {
}
}
}
+
+/// Obtains a [`wgpu::PresentMode`] from the current environment
+/// configuration, if set.
+///
+/// The value returned by this function can be changed by setting
+/// the `ICED_PRESENT_MODE` env variable. The possible values are:
+///
+/// - `vsync` → [`wgpu::PresentMode::AutoVsync`]
+/// - `no_vsync` → [`wgpu::PresentMode::AutoNoVsync`]
+/// - `immediate` → [`wgpu::PresentMode::Immediate`]
+/// - `fifo` → [`wgpu::PresentMode::Fifo`]
+/// - `fifo_relaxed` → [`wgpu::PresentMode::FifoRelaxed`]
+/// - `mailbox` → [`wgpu::PresentMode::Mailbox`]
+pub fn present_mode_from_env() -> Option<wgpu::PresentMode> {
+ let present_mode = std::env::var("ICED_PRESENT_MODE").ok()?;
+
+ match present_mode.to_lowercase().as_str() {
+ "vsync" => Some(wgpu::PresentMode::AutoVsync),
+ "no_vsync" => Some(wgpu::PresentMode::AutoNoVsync),
+ "immediate" => Some(wgpu::PresentMode::Immediate),
+ "fifo" => Some(wgpu::PresentMode::Fifo),
+ "fifo_relaxed" => Some(wgpu::PresentMode::FifoRelaxed),
+ "mailbox" => Some(wgpu::PresentMode::Mailbox),
+ _ => None,
+ }
+}
diff --git a/wgpu/src/shader/image.wgsl b/wgpu/src/shader/image.wgsl
index 7b2e5238..accefc17 100644
--- a/wgpu/src/shader/image.wgsl
+++ b/wgpu/src/shader/image.wgsl
@@ -9,40 +9,55 @@ struct Globals {
struct VertexInput {
@builtin(vertex_index) vertex_index: u32,
@location(0) pos: vec2<f32>,
- @location(1) scale: vec2<f32>,
- @location(2) atlas_pos: vec2<f32>,
- @location(3) atlas_scale: vec2<f32>,
- @location(4) layer: i32,
+ @location(1) center: vec2<f32>,
+ @location(2) scale: vec2<f32>,
+ @location(3) rotation: f32,
+ @location(4) opacity: f32,
+ @location(5) atlas_pos: vec2<f32>,
+ @location(6) atlas_scale: vec2<f32>,
+ @location(7) layer: i32,
}
struct VertexOutput {
@builtin(position) position: vec4<f32>,
@location(0) uv: vec2<f32>,
@location(1) layer: f32, // this should be an i32, but naga currently reads that as requiring interpolation.
+ @location(2) opacity: f32,
}
@vertex
fn vs_main(input: VertexInput) -> VertexOutput {
var out: VertexOutput;
- let v_pos = vertex_position(input.vertex_index);
+ // Generate a vertex position in the range [0, 1] from the vertex index.
+ var v_pos = vertex_position(input.vertex_index);
+ // Map the vertex position to the atlas texture.
out.uv = vec2<f32>(v_pos * input.atlas_scale + input.atlas_pos);
out.layer = f32(input.layer);
+ out.opacity = input.opacity;
- var transform: mat4x4<f32> = mat4x4<f32>(
- vec4<f32>(input.scale.x, 0.0, 0.0, 0.0),
- vec4<f32>(0.0, input.scale.y, 0.0, 0.0),
+ // Calculate the vertex position and move the center to the origin
+ 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);
+ let sin_rot = sin(input.rotation);
+ let rotate = mat4x4<f32>(
+ vec4<f32>(cos_rot, sin_rot, 0.0, 0.0),
+ vec4<f32>(-sin_rot, cos_rot, 0.0, 0.0),
vec4<f32>(0.0, 0.0, 1.0, 0.0),
- vec4<f32>(input.pos, 0.0, 1.0)
+ vec4<f32>(0.0, 0.0, 0.0, 1.0)
);
- out.position = globals.transform * transform * vec4<f32>(v_pos, 0.0, 1.0);
+ // 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));
return out;
}
@fragment
fn fs_main(input: VertexOutput) -> @location(0) vec4<f32> {
- return textureSample(u_texture, u_sampler, input.uv, i32(input.layer));
+ // Sample the texture at the given UV coordinate and layer.
+ return textureSample(u_texture, u_sampler, input.uv, i32(input.layer)) * vec4<f32>(1.0, 1.0, 1.0, input.opacity);
}
diff --git a/wgpu/src/text.rs b/wgpu/src/text.rs
index 0d01faca..7e683c77 100644
--- a/wgpu/src/text.rs
+++ b/wgpu/src/text.rs
@@ -1,12 +1,13 @@
use crate::core::alignment;
use crate::core::{Rectangle, Size, Transformation};
+use crate::graphics::cache;
use crate::graphics::color;
-use crate::graphics::text::cache::{self, Cache as BufferCache};
+use crate::graphics::text::cache::{self as text_cache, Cache as BufferCache};
use crate::graphics::text::{font_system, to_color, Editor, Paragraph};
-use rustc_hash::{FxHashMap, FxHashSet};
+use rustc_hash::FxHashMap;
use std::collections::hash_map;
-use std::rc::Rc;
+use std::rc::{self, Rc};
use std::sync::atomic::{self, AtomicU64};
use std::sync::Arc;
@@ -35,6 +36,7 @@ pub enum Item {
#[derive(Debug, Clone)]
pub struct Cache {
id: Id,
+ group: cache::Group,
text: Rc<[Text]>,
version: usize,
}
@@ -43,7 +45,7 @@ pub struct Cache {
pub struct Id(u64);
impl Cache {
- pub fn new(text: Vec<Text>) -> Option<Self> {
+ pub fn new(group: cache::Group, text: Vec<Text>) -> Option<Self> {
static NEXT_ID: AtomicU64 = AtomicU64::new(0);
if text.is_empty() {
@@ -52,12 +54,17 @@ impl Cache {
Some(Self {
id: Id(NEXT_ID.fetch_add(1, atomic::Ordering::Relaxed)),
+ group,
text: Rc::from(text),
version: 0,
})
}
pub fn update(&mut self, text: Vec<Text>) {
+ if self.text.is_empty() && text.is_empty() {
+ return;
+ }
+
self.text = Rc::from(text);
self.version += 1;
}
@@ -65,16 +72,25 @@ impl Cache {
struct Upload {
renderer: glyphon::TextRenderer,
- atlas: glyphon::TextAtlas,
buffer_cache: BufferCache,
transformation: Transformation,
version: usize,
+ group_version: usize,
+ text: rc::Weak<[Text]>,
+ _atlas: rc::Weak<()>,
}
#[derive(Default)]
pub struct Storage {
+ groups: FxHashMap<cache::Group, Group>,
uploads: FxHashMap<Id, Upload>,
- recently_used: FxHashSet<Id>,
+}
+
+struct Group {
+ atlas: glyphon::TextAtlas,
+ version: usize,
+ should_trim: bool,
+ handle: Rc<()>, // Keeps track of active uploads
}
impl Storage {
@@ -82,12 +98,15 @@ impl Storage {
Self::default()
}
- fn get(&self, cache: &Cache) -> Option<&Upload> {
+ fn get(&self, cache: &Cache) -> Option<(&glyphon::TextAtlas, &Upload)> {
if cache.text.is_empty() {
return None;
}
- self.uploads.get(&cache.id)
+ self.groups
+ .get(&cache.group)
+ .map(|group| &group.atlas)
+ .zip(self.uploads.get(&cache.id))
}
fn prepare(
@@ -101,41 +120,63 @@ impl Storage {
bounds: Rectangle,
target_size: Size<u32>,
) {
+ let group_count = self.groups.len();
+
+ let group = self.groups.entry(cache.group).or_insert_with(|| {
+ log::debug!(
+ "New text atlas: {:?} (total: {})",
+ cache.group,
+ group_count + 1
+ );
+
+ Group {
+ atlas: glyphon::TextAtlas::with_color_mode(
+ device, queue, format, COLOR_MODE,
+ ),
+ version: 0,
+ should_trim: false,
+ handle: Rc::new(()),
+ }
+ });
+
match self.uploads.entry(cache.id) {
hash_map::Entry::Occupied(entry) => {
let upload = entry.into_mut();
- if !cache.text.is_empty()
- && (upload.version != cache.version
- || upload.transformation != new_transformation)
+ if upload.version != cache.version
+ || upload.group_version != group.version
+ || upload.transformation != new_transformation
{
- let _ = prepare(
- device,
- queue,
- encoder,
- &mut upload.renderer,
- &mut upload.atlas,
- &mut upload.buffer_cache,
- &cache.text,
- bounds,
- new_transformation,
- target_size,
- );
+ if !cache.text.is_empty() {
+ let _ = prepare(
+ device,
+ queue,
+ encoder,
+ &mut upload.renderer,
+ &mut group.atlas,
+ &mut upload.buffer_cache,
+ &cache.text,
+ bounds,
+ new_transformation,
+ target_size,
+ );
+ }
+
+ // Only trim if glyphs have changed
+ group.should_trim =
+ group.should_trim || upload.version != cache.version;
+ upload.text = Rc::downgrade(&cache.text);
upload.version = cache.version;
+ upload.group_version = group.version;
upload.transformation = new_transformation;
upload.buffer_cache.trim();
- upload.atlas.trim();
}
}
hash_map::Entry::Vacant(entry) => {
- let mut atlas = glyphon::TextAtlas::with_color_mode(
- device, queue, format, COLOR_MODE,
- );
-
let mut renderer = glyphon::TextRenderer::new(
- &mut atlas,
+ &mut group.atlas,
device,
wgpu::MultisampleState::default(),
None,
@@ -143,41 +184,76 @@ impl Storage {
let mut buffer_cache = BufferCache::new();
- let _ = prepare(
- device,
- queue,
- encoder,
- &mut renderer,
- &mut atlas,
- &mut buffer_cache,
- &cache.text,
- bounds,
- new_transformation,
- target_size,
- );
+ if !cache.text.is_empty() {
+ let _ = prepare(
+ device,
+ queue,
+ encoder,
+ &mut renderer,
+ &mut group.atlas,
+ &mut buffer_cache,
+ &cache.text,
+ bounds,
+ new_transformation,
+ target_size,
+ );
+ }
let _ = entry.insert(Upload {
renderer,
- atlas,
buffer_cache,
transformation: new_transformation,
version: 0,
+ group_version: group.version,
+ text: Rc::downgrade(&cache.text),
+ _atlas: Rc::downgrade(&group.handle),
});
- log::info!(
+ group.should_trim = cache.group.is_singleton();
+
+ log::debug!(
"New text upload: {} (total: {})",
cache.id.0,
self.uploads.len()
);
}
}
-
- let _ = self.recently_used.insert(cache.id);
}
pub fn trim(&mut self) {
- self.uploads.retain(|id, _| self.recently_used.contains(id));
- self.recently_used.clear();
+ self.uploads
+ .retain(|_id, upload| upload.text.strong_count() > 0);
+
+ self.groups.retain(|id, group| {
+ let active_uploads = Rc::weak_count(&group.handle);
+
+ if active_uploads == 0 {
+ log::debug!("Dropping text atlas: {id:?}");
+
+ return false;
+ }
+
+ if group.should_trim {
+ log::trace!("Trimming text atlas: {id:?}");
+
+ group.atlas.trim();
+ group.should_trim = false;
+
+ // We only need to worry about glyph fighting
+ // when the atlas may be shared by multiple
+ // uploads.
+ if !id.is_singleton() {
+ log::debug!(
+ "Invalidating text atlas: {id:?} \
+ (uploads: {active_uploads})"
+ );
+
+ group.version += 1;
+ }
+ }
+
+ true
+ });
}
}
@@ -306,10 +382,10 @@ impl Pipeline {
layer_count += 1;
}
Item::Cached { cache, .. } => {
- if let Some(upload) = storage.get(cache) {
+ if let Some((atlas, upload)) = storage.get(cache) {
upload
.renderer
- .render(&upload.atlas, render_pass)
+ .render(atlas, render_pass)
.expect("Render cached text");
}
}
@@ -345,7 +421,7 @@ fn prepare(
enum Allocation {
Paragraph(Paragraph),
Editor(Editor),
- Cache(cache::KeyHash),
+ Cache(text_cache::KeyHash),
Raw(Arc<glyphon::Buffer>),
}
@@ -369,7 +445,7 @@ fn prepare(
} => {
let (key, _) = buffer_cache.allocate(
font_system,
- cache::Key {
+ text_cache::Key {
content,
size: f32::from(*size),
line_height: f32::from(*line_height),
diff --git a/wgpu/src/triangle.rs b/wgpu/src/triangle.rs
index 8470ea39..b0551f55 100644
--- a/wgpu/src/triangle.rs
+++ b/wgpu/src/triangle.rs
@@ -6,9 +6,9 @@ use crate::graphics::mesh::{self, Mesh};
use crate::graphics::Antialiasing;
use crate::Buffer;
-use rustc_hash::{FxHashMap, FxHashSet};
+use rustc_hash::FxHashMap;
use std::collections::hash_map;
-use std::rc::Rc;
+use std::rc::{self, Rc};
use std::sync::atomic::{self, AtomicU64};
const INITIAL_INDEX_COUNT: usize = 1_000;
@@ -64,12 +64,12 @@ struct Upload {
layer: Layer,
transformation: Transformation,
version: usize,
+ batch: rc::Weak<[Mesh]>,
}
#[derive(Debug, Default)]
pub struct Storage {
uploads: FxHashMap<Id, Upload>,
- recently_used: FxHashSet<Id>,
}
impl Storage {
@@ -113,6 +113,7 @@ impl Storage {
new_transformation,
);
+ upload.batch = Rc::downgrade(&cache.batch);
upload.version = cache.version;
upload.transformation = new_transformation;
}
@@ -134,22 +135,21 @@ impl Storage {
layer,
transformation: new_transformation,
version: 0,
+ batch: Rc::downgrade(&cache.batch),
});
- log::info!(
+ log::debug!(
"New mesh upload: {} (total: {})",
cache.id.0,
self.uploads.len()
);
}
}
-
- let _ = self.recently_used.insert(cache.id);
}
pub fn trim(&mut self) {
- self.uploads.retain(|id, _| self.recently_used.contains(id));
- self.recently_used.clear();
+ self.uploads
+ .retain(|_id, upload| upload.batch.strong_count() > 0);
}
}
diff --git a/wgpu/src/window/compositor.rs b/wgpu/src/window/compositor.rs
index 095afd48..2e938c77 100644
--- a/wgpu/src/window/compositor.rs
+++ b/wgpu/src/window/compositor.rs
@@ -4,7 +4,8 @@ use crate::graphics::color;
use crate::graphics::compositor;
use crate::graphics::error;
use crate::graphics::{self, Viewport};
-use crate::{Engine, Renderer, Settings};
+use crate::settings::{self, Settings};
+use crate::{Engine, Renderer};
/// A window graphics backend for iced powered by `wgpu`.
#[allow(missing_debug_implementations)]
@@ -270,15 +271,19 @@ impl graphics::Compositor for Compositor {
backend: Option<&str>,
) -> Result<Self, graphics::Error> {
match backend {
- None | Some("wgpu") => Ok(new(
- Settings {
- backends: wgpu::util::backend_bits_from_env()
- .unwrap_or(wgpu::Backends::all()),
- ..settings.into()
- },
- compatible_window,
- )
- .await?),
+ None | Some("wgpu") => {
+ let mut settings = Settings::from(settings);
+
+ if let Some(backends) = wgpu::util::backend_bits_from_env() {
+ settings.backends = backends;
+ }
+
+ if let Some(present_mode) = settings::present_mode_from_env() {
+ settings.present_mode = present_mode;
+ }
+
+ Ok(new(settings, compatible_window).await?)
+ }
Some(backend) => Err(graphics::Error::GraphicsAdapterNotFound {
backend: "wgpu",
reason: error::Reason::DidNotMatch {
@@ -290,6 +295,7 @@ impl graphics::Compositor for Compositor {
fn create_renderer(&self) -> Self::Renderer {
Renderer::new(
+ &self.device,
&self.engine,
self.settings.default_font,
self.settings.default_text_size,