Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 14 additions & 1 deletion core/src/renderer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,26 @@ mod null;
#[cfg(debug_assertions)]
pub use null::Null;

use crate::{Background, BorderRadius, Color, Rectangle, Vector};
use crate::layout;
use crate::{Background, BorderRadius, Color, Element, Rectangle, Vector};

/// A component that can be used by widgets to draw themselves on a screen.
pub trait Renderer: Sized {
/// The supported theme of the [`Renderer`].
type Theme;

/// Lays out the elements of a user interface.
///
/// You should override this if you need to perform any operations before or
/// after layouting. For instance, trimming the measurements cache.
fn layout<Message>(
&mut self,
element: &Element<'_, Message, Self>,
limits: &layout::Limits,
) -> layout::Node {
element.as_widget().layout(self, limits)
}

/// Draws the primitives recorded in the given closure in a new layer.
///
/// The layer will clip its contents to the provided `bounds`.
Expand Down
7 changes: 7 additions & 0 deletions graphics/src/backend.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,13 @@ use std::borrow::Cow;
pub trait Backend {
/// The custom kind of primitives this [`Backend`] supports.
type Primitive;

/// Trims the measurements cache.
///
/// This method is currently necessary to properly trim the text cache in
/// `iced_wgpu` and `iced_glow` because of limitations in the text rendering
/// pipeline. It will be removed in the future.
fn trim_measurements(&mut self) {}
}

/// A graphics backend that supports text rendering.
Expand Down
15 changes: 14 additions & 1 deletion graphics/src/renderer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,13 @@ use crate::backend::{self, Backend};
use crate::Primitive;

use iced_core::image;
use iced_core::layout;
use iced_core::renderer;
use iced_core::svg;
use iced_core::text::{self, Text};
use iced_core::{Background, Color, Font, Point, Rectangle, Size, Vector};
use iced_core::{
Background, Color, Element, Font, Point, Rectangle, Size, Vector,
};

use std::borrow::Cow;
use std::marker::PhantomData;
Expand Down Expand Up @@ -85,6 +88,16 @@ impl<B: Backend, T> Renderer<B, T> {
impl<B: Backend, T> iced_core::Renderer for Renderer<B, T> {
type Theme = T;

fn layout<Message>(
&mut self,
element: &Element<'_, Message, Self>,
limits: &layout::Limits,
) -> layout::Node {
self.backend.trim_measurements();

element.as_widget().layout(self, limits)
}

fn with_layer(&mut self, bounds: Rectangle, f: impl FnOnce(&mut Self)) {
let current = self.start_layer();

Expand Down
13 changes: 6 additions & 7 deletions runtime/src/user_interface.rs
Original file line number Diff line number Diff line change
Expand Up @@ -95,9 +95,8 @@ where
let Cache { mut state } = cache;
state.diff(root.as_widget());

let base = root
.as_widget()
.layout(renderer, &layout::Limits::new(Size::ZERO, bounds));
let base =
renderer.layout(&root, &layout::Limits::new(Size::ZERO, bounds));

UserInterface {
root,
Expand Down Expand Up @@ -227,8 +226,8 @@ where
if shell.is_layout_invalid() {
let _ = ManuallyDrop::into_inner(manual_overlay);

self.base = self.root.as_widget().layout(
renderer,
self.base = renderer.layout(
&self.root,
&layout::Limits::new(Size::ZERO, self.bounds),
);

Expand Down Expand Up @@ -323,8 +322,8 @@ where
}

shell.revalidate_layout(|| {
self.base = self.root.as_widget().layout(
renderer,
self.base = renderer.layout(
&self.root,
&layout::Limits::new(Size::ZERO, self.bounds),
);

Expand Down
4 changes: 4 additions & 0 deletions wgpu/src/backend.rs
Original file line number Diff line number Diff line change
Expand Up @@ -337,6 +337,10 @@ 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 {
Expand Down
73 changes: 54 additions & 19 deletions wgpu/src/text.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,10 @@ impl Pipeline {
let renderer = &mut self.renderers[self.prepare_layer];
let cache = self.cache.get_mut();

if self.prepare_layer == 0 {
cache.trim(Purpose::Drawing);
}

let keys: Vec<_> = sections
.iter()
.map(|section| {
Expand All @@ -100,6 +104,7 @@ impl Pipeline {
},
shaping: section.shaping,
},
Purpose::Drawing,
);

key
Expand Down Expand Up @@ -224,11 +229,14 @@ 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,
Expand All @@ -238,11 +246,11 @@ impl Pipeline {
bounds: Size,
shaping: Shaping,
) -> Size {
let mut measurement_cache = self.cache.borrow_mut();
let mut cache = self.cache.borrow_mut();

let line_height = f32::from(line_height.to_absolute(Pixels(size)));

let (_, entry) = measurement_cache.allocate(
let (_, entry) = cache.allocate(
&mut self.font_system.borrow_mut(),
Key {
content,
Expand All @@ -252,6 +260,7 @@ impl Pipeline {
bounds,
shaping,
},
Purpose::Measuring,
);

entry.bounds
Expand All @@ -268,11 +277,11 @@ impl Pipeline {
point: Point,
_nearest_only: bool,
) -> Option<Hit> {
let mut measurement_cache = self.cache.borrow_mut();
let mut cache = self.cache.borrow_mut();

let line_height = f32::from(line_height.to_absolute(Pixels(size)));

let (_, entry) = measurement_cache.allocate(
let (_, entry) = cache.allocate(
&mut self.font_system.borrow_mut(),
Key {
content,
Expand All @@ -282,6 +291,7 @@ impl Pipeline {
bounds,
shaping,
},
Purpose::Measuring,
);

let cursor = entry.buffer.hit(point.x, point.y)?;
Expand Down Expand Up @@ -348,8 +358,9 @@ fn to_shaping(shaping: Shaping) -> glyphon::Shaping {

struct Cache {
entries: FxHashMap<KeyHash, Entry>,
measurements: FxHashMap<KeyHash, KeyHash>,
recently_used: FxHashSet<KeyHash>,
aliases: FxHashMap<KeyHash, KeyHash>,
recently_measured: FxHashSet<KeyHash>,
recently_drawn: FxHashSet<KeyHash>,
hasher: HashBuilder,
}

Expand All @@ -358,6 +369,12 @@ struct Entry {
bounds: Size,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Purpose {
Measuring,
Drawing,
}

#[cfg(not(target_arch = "wasm32"))]
type HashBuilder = twox_hash::RandomXxHashBuilder64;

Expand All @@ -368,8 +385,9 @@ impl Cache {
fn new() -> Self {
Self {
entries: FxHashMap::default(),
measurements: FxHashMap::default(),
recently_used: FxHashSet::default(),
aliases: FxHashMap::default(),
recently_measured: FxHashSet::default(),
recently_drawn: FxHashSet::default(),
hasher: HashBuilder::default(),
}
}
Expand All @@ -382,11 +400,17 @@ impl Cache {
&mut self,
font_system: &mut glyphon::FontSystem,
key: Key<'_>,
purpose: Purpose,
) -> (KeyHash, &mut Entry) {
let hash = key.hash(self.hasher.build_hasher());

if let Some(hash) = self.measurements.get(&hash) {
let _ = self.recently_used.insert(*hash);
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());
}
Expand Down Expand Up @@ -421,26 +445,37 @@ impl Cache {
},
] {
if key.bounds != bounds {
let _ = self.measurements.insert(
let _ = self.aliases.insert(
Key { bounds, ..key }.hash(self.hasher.build_hasher()),
hash,
);
}
}
}

let _ = self.recently_used.insert(hash);
let _ = recently_used.insert(hash);

(hash, self.entries.get_mut(&hash).unwrap())
}

fn trim(&mut self) {
self.entries
.retain(|key, _| self.recently_used.contains(key));
self.measurements
.retain(|_, value| self.recently_used.contains(value));
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)
});

self.recently_used.clear();
match purpose {
Purpose::Measuring => {
self.recently_measured.clear();
}
Purpose::Drawing => {
self.recently_drawn.clear();
}
}
}
}

Expand Down