Files
iris/core/src/ui/painter.rs
T

512 lines
18 KiB
Rust

use crate::{
Axis, Len, MoveOffset, PaintId, RegionAlign, RenderedText, Size, StrongWidget, TextHandle,
TextResources, TextureHandle, UiData, UiRegion, UiRenderState, UiRsc, UiScalar, UiVec2,
WidgetId,
render::{
Drawn, GlyphPrimitive, IMAGE_BINDING, Mask, MaskIdx, MoveIdx, NOT_DRAWN, Primitive,
PrimitiveHandle, PrimitiveInst, RectPrimitive,
},
ui::render_state::Retained,
util::Vec2,
};
use std::{cell::RefCell, rc::Rc};
pub struct Painter<'a> {
pub(super) render_state: &'a mut UiRenderState,
pub(super) rsc: &'a mut dyn UiRsc,
pub(super) region: UiRegion,
pub(super) mask: MaskIdx,
pub(super) move_slot: MoveIdx,
pub(super) child_move_slot: Option<MoveIdx>,
pub(super) own_mask: MaskIdx,
pub(super) textures: Vec<TextureHandle>,
pub(super) paints: Vec<PaintId>,
pub(super) primitives: Vec<PrimitiveHandle>,
pub(super) recycle: std::iter::Peekable<std::vec::IntoIter<PrimitiveHandle>>,
pub(super) children: Vec<WidgetId>,
pub(super) size_dependencies: Vec<WidgetId>,
pub(super) size: Option<Size>,
/// Whether a retained child's length on each axis is still valid. A
/// child's length may change when the parent's orthogonal extent changes
/// (most importantly, wrapped text gets taller when it gets narrower),
/// but not merely because a content-sized parent grew along that same
/// axis around one of its siblings.
pub(super) reuse_child_sizes: [bool; 2],
pub layer: usize,
pub(super) id: WidgetId,
}
pub struct DrawResult<'p, 'a> {
painter: &'p mut Painter<'a>,
child: WidgetId,
}
impl DrawResult<'_, '_> {
pub fn size(self) -> Size {
if !self.painter.size_dependencies.contains(&self.child) {
self.painter.size_dependencies.push(self.child);
}
self.painter.render_state.active[&self.child].size
}
}
impl<'a> Painter<'a> {
pub fn set_size(&mut self, size: Size) {
assert!(
self.size.replace(size).is_none(),
"a widget set its size more than once during one draw"
);
}
fn primitive_at<P: Primitive>(&mut self, primitive: P, region: UiRegion) {
self.write_primitive(primitive, region, Drawn::Yes);
}
/// **Consumed strictly in order, and one mismatch ends recycling for
/// the rest of the draw.** A widget's `draw` is a function of its own
/// state, so a redraw writes the same sequence of primitives in the
/// same order in the overwhelmingly common case; searching the
/// remainder for a match would turn an O(1) step into an O(primitives)
/// one to rescue a case that means the widget's content changed shape
/// anyway. Stopping is also what keeps the invariant simple: every
/// handle from `recycled` on is untouched and gets freed together.
fn take_recycled(&mut self, binding: u32, drawn: Drawn) -> Option<PrimitiveHandle> {
let h = self.recycle.peek()?;
let drawn_matches = (h.pos == NOT_DRAWN) == (drawn == Drawn::No);
if h.binding != binding || h.layer != self.layer || !drawn_matches {
return None;
}
self.recycle.next()
}
fn write_primitive<P: Primitive>(
&mut self,
primitive: P,
region: UiRegion,
drawn: Drawn,
) -> u32 {
let inst = PrimitiveInst {
id: self.id,
primitive,
region,
mask_idx: self.mask,
move_idx: self.move_slot,
};
let h = match self.take_recycled(P::BINDING, drawn) {
Some(h) => {
self.render_state.primitives.recycle(&h, inst);
h
}
None => self.render_state.write_primitive(self.layer, drawn, inst),
};
if self.mask != MaskIdx::NONE {
self.rsc.ui_mut().masks.push_ref(self.mask);
}
let slot = h.slot;
self.own(h);
slot
}
/// Take ownership of a handle this widget just wrote.
fn own(&mut self, h: PrimitiveHandle) {
self.render_state
.primitives
.set_handle_index(h.slot, self.primitives.len() as u32);
self.primitives.push(h);
}
pub fn primitive<P: Primitive>(&mut self, primitive: P) {
self.primitive_at(primitive, self.region)
}
/// Resolves a public paint handle to the compact index stored by a GPU
/// primitive and retains the handle for exactly as long as that draw.
pub fn paint(&mut self, paint: &PaintId) -> u32 {
if !self.paints.contains(paint) {
self.paints.push(paint.clone());
}
paint.slot()
}
pub fn paint_value(&mut self, paint: &mut crate::PaintValue) -> u32 {
let paint = paint.resolve(&mut self.rsc.ui_mut().paints).clone();
self.paint(&paint)
}
pub fn primitive_within<P: Primitive>(&mut self, primitive: P, region: UiRegion) {
self.primitive_at(primitive, region.within(&self.region));
}
/// The slot is allocated once and **rewritten in place** on every
/// later draw rather than pushed again, because a descendant whose own
/// region did not change is not redrawn (`draw_inner`'s fast path) and
/// so keeps pointing at whichever slot it was drawn under. See
/// `ActiveData::own_mask` for what pushing a fresh one cost.
pub fn set_mask(&mut self, region: UiRegion) {
let paint = self.paint(&PaintId::NONE);
let shape = self.write_primitive(RectPrimitive::color(paint), region, Drawn::No);
self.set_mask_to(shape);
}
/// Clip everything this widget draws after this call to `shape`'s
/// own shape -- the first primitive `shape`'s subtree drew, which
/// must already have been drawn this frame
/// (`UiRenderState::first_primitive`). What `.masked_by()` uses to
/// clip a container's content to the rounded background it draws,
/// with no radius argument anywhere that could fall out of step with
/// the one being drawn.
pub fn set_mask_to_widget<W: ?Sized>(&mut self, shape: &StrongWidget<W>) {
let slot = self.render_state.first_primitive(shape.id()).unwrap_or_else(|| {
panic!(
"'{}' was given as a mask's shape but drew no primitive, so there is nothing to \
clip to",
self.rsc.widgets().label(shape.id()),
)
});
self.set_mask_to(slot);
}
fn set_mask_to(&mut self, shape: u32) {
assert!(
self.own_mask == MaskIdx::NONE || self.mask != self.own_mask,
"set_mask called twice while drawing one widget: the second would replace the first \
rather than nest inside it",
);
let binding = self.render_state.primitives.instance(shape).binding;
assert_eq!(
binding,
RectPrimitive::BINDING,
"a mask's shape must be a rect primitive; primitive {shape} is binding {binding}",
);
let parent = self.mask;
let mask = Mask {
primitive: shape,
parent,
};
let old_parent = if self.own_mask == MaskIdx::NONE {
let slot = self.rsc.ui_mut().masks.push(mask);
self.rsc.ui_mut().masks.push_ref(slot);
self.own_mask = slot;
MaskIdx::NONE
} else {
let old = self.rsc.ui().masks[self.own_mask.idx()].parent;
*self.rsc.ui_mut().masks.get_mut(self.own_mask) = mask;
old
};
if old_parent != parent {
if parent != MaskIdx::NONE {
self.rsc.ui_mut().masks.push_ref(parent);
}
if old_parent != MaskIdx::NONE {
self.rsc.ui_mut().masks.remove(old_parent);
}
}
self.mask = self.own_mask;
}
pub fn widget<'p, W: ?Sized>(&'p mut self, id: &StrongWidget<W>) -> DrawResult<'p, 'a> {
self.widget_at(id, self.region)
}
pub fn widget_within<'p, W: ?Sized>(
&'p mut self,
id: &StrongWidget<W>,
region: UiRegion,
) -> DrawResult<'p, 'a> {
self.widget_at(id, region.within(&self.region))
}
/// Translate this widget's children as one retained subtree, in output
/// pixels. The first call must happen before drawing a child, because the
/// slot becomes the parent of every direct child's ordinary move slot.
/// Once retained, it may be updated later in a redraw (for example after
/// measuring a changed child). All deeper descendants inherit it and the
/// CPU hit-test walk resolves the same translation as the shader.
pub fn set_child_offset(&mut self, offset: Vec2) {
let slot = match self.child_move_slot {
Some(slot) => slot,
None => {
assert!(
self.children.is_empty(),
"a child offset must be created before drawing a child"
);
let parent = self.move_slot.idx() as u32;
let slot = self
.rsc
.ui_mut()
.move_offsets
.push(MoveOffset::new([offset.x, offset.y], parent));
// One ref for this widget's ownership and one on the
// up-link. Direct children take their own refs when their
// move slots are allocated.
self.rsc.ui_mut().move_offsets.push_ref(slot);
self.rsc.ui_mut().move_offsets.push_ref(self.move_slot);
self.child_move_slot = Some(slot);
return;
}
};
let next = [offset.x, offset.y];
if self.rsc.ui().move_offsets[slot.idx()].delta != next {
self.rsc.ui_mut().move_offsets.get_mut(slot).delta = next;
self.render_state.note_move();
}
}
pub fn known_len<W: ?Sized>(&mut self, id: &StrongWidget<W>, axis: Axis) -> Option<Len> {
let len = if let Some(len) = self.rsc.widgets().get_dyn(id.id())?.size_hint(axis) {
Some(len.fold_dp(self.density()))
} else if !self.reuse_child_sizes[match axis {
Axis::X => 0,
Axis::Y => 1,
}] || self.rsc.widgets().needs_redraw.contains(&id.id())
{
None
} else {
self.render_state
.active
.get(&id.id())
.map(|a| a.size.axis(axis))
};
if len.is_some() && !self.size_dependencies.contains(&id.id()) {
self.size_dependencies.push(id.id());
}
len
}
fn widget_at<'p, W: ?Sized>(
&'p mut self,
id: &StrongWidget<W>,
region: UiRegion,
) -> DrawResult<'p, 'a> {
self.children.push(id.id());
let parent_move_slot = self.child_move_slot.unwrap_or(self.move_slot);
self.render_state.draw_inner(
self.layer,
id.id(),
region,
Some(self.id),
parent_move_slot.idx() as u32,
self.mask,
Retained::default(),
self.rsc,
);
DrawResult {
painter: self,
child: id.id(),
}
}
pub fn place<'p, W: ?Sized>(
&'p mut self,
id: &StrongWidget<W>,
region: UiRegion,
) -> DrawResult<'p, 'a> {
let region = region.within(&self.region);
let retained = self
.render_state
.active
.get(&id.id())
.map(|active| (active.layer, active.mask));
if self.render_state.place(id.id(), region, self.rsc).is_some() {
} else if let Some((layer, mask)) = retained {
self.children.push(id.id());
self.rsc.widgets_mut().needs_redraw.insert(id.id());
let parent_move_slot = self.child_move_slot.unwrap_or(self.move_slot);
self.render_state.draw_inner(
layer,
id.id(),
region,
Some(self.id),
parent_move_slot.idx() as u32,
mask,
Retained::default(),
self.rsc,
);
} else {
self.children.push(id.id());
let parent_move_slot = self.child_move_slot.unwrap_or(self.move_slot);
self.render_state.draw_inner(
self.layer,
id.id(),
region,
Some(self.id),
parent_move_slot.idx() as u32,
self.mask,
Retained::default(),
self.rsc,
);
}
DrawResult {
painter: self,
child: id.id(),
}
}
pub fn place_used<W: ?Sized>(
&mut self,
id: &StrongWidget<W>,
used: Size,
within: UiRegion,
) -> DrawResult<'_, 'a> {
let region = self.fit_region(used, within);
self.place(id, region)
}
pub fn fit_region(&mut self, used: Size, mut within: UiRegion) -> UiRegion {
let mut region = used
.to_uivec2(self.density())
.align(RegionAlign::TOP_LEFT)
.within(&within);
let output = self.output_size();
for axis in [Axis::X, Axis::Y] {
let mut actual = region.within(&self.region);
let mut available = within.within(&self.region);
if actual.axis(axis).len().to_abs(output.axis(axis))
> available.axis(axis).len().to_abs(output.axis(axis))
{
*region.axis_mut(axis) = *within.axis(axis);
}
}
region
}
pub fn texture_within(&mut self, handle: &TextureHandle, region: UiRegion) {
self.textures.push(handle.clone());
self.write_image(handle.image_index(), region.within(&self.region));
}
pub fn texture(&mut self, handle: &TextureHandle) {
self.textures.push(handle.clone());
self.write_image(handle.image_index(), self.region);
}
pub fn texture_at(&mut self, handle: &TextureHandle, region: UiRegion) {
self.textures.push(handle.clone());
self.write_image(handle.image_index(), region);
}
fn write_image(&mut self, texture_idx: u32, region: UiRegion) {
let h = match self.take_recycled(IMAGE_BINDING, Drawn::Yes) {
Some(h) => {
self.render_state.primitives.recycle_image(
&h,
self.id,
texture_idx,
region,
self.mask,
self.move_slot,
);
h
}
None => self.render_state.write_image(
self.layer,
self.id,
texture_idx,
region,
self.mask,
self.move_slot,
),
};
if self.mask != MaskIdx::NONE {
self.rsc.ui_mut().masks.push_ref(self.mask);
}
self.own(h);
}
pub fn render_text(&mut self, text: &TextHandle, width: Option<f32>) -> RenderedText {
let density = self.render_state.density;
let ui: &mut UiData = self.rsc.ui_mut();
let (rendered, prepared) = text.render(width, self.id, &mut ui.textures, density);
self.render_state.shape_count += u64::from(prepared);
rendered
}
fn atlas_generation(&self) -> u64 {
self.rsc.ui().text.borrow().atlas.generation()
}
pub fn glyphs(&mut self, text: &RenderedText, origin: UiRegion) {
// A caller re-emitting quads placed against an atlas that has since
// been cleared draws every glyph from coordinates now holding
// something else. Caught at the submission rather than on screen,
// where it reads as fragments of unrelated letters. `assert_eq!`
// for R1's reason: two integers per laid-out string, not per
// glyph, and the failure is unreadable text on a release build.
assert_eq!(
text.generation,
self.atlas_generation(),
"glyphs placed against atlas generation {} submitted against {}: the holder did not \
re-render after the atlas was cleared",
text.generation,
self.atlas_generation(),
);
let flags_for = |is_color| {
if is_color {
GlyphPrimitive::IS_COLOR
} else {
0
}
};
for paint in text.paints.iter() {
self.paint(paint);
}
for glyph in text.glyphs.iter() {
let mut region = origin;
region.x.end = region.x.start;
region.y.end = region.y.start;
let mut region = region.offset(UiVec2::abs(glyph.offset));
region.x.end = region.x.start + UiScalar::abs(glyph.entry.width as f32);
region.y.end = region.y.start + UiScalar::abs(glyph.entry.height as f32);
self.primitive_at(
GlyphPrimitive::new(
glyph.entry.uv_min,
glyph.entry.uv_max,
glyph.entry.layer,
glyph.paint,
flags_for(glyph.entry.is_color),
),
region,
);
}
}
pub fn region(&self) -> UiRegion {
self.region
}
pub fn output_size(&self) -> Vec2 {
self.render_state.output_size
}
/// Physical pixels per `dp` -- see `UiRenderState::density`'s field
/// doc. What `Len::dp`'s `apply_rest` call resolves against.
pub fn density(&self) -> f32 {
self.render_state.density
}
pub fn px_size(&mut self) -> Vec2 {
self.region.size().to_abs(self.render_state.output_size)
}
pub fn text_resources(&mut self) -> Rc<RefCell<TextResources>> {
self.rsc.ui().text.clone()
}
pub fn child_layer(&mut self) {
self.layer = self.render_state.layers.child(self.layer);
}
pub fn next_layer(&mut self) {
self.layer = self.render_state.layers.next(self.layer);
}
pub fn label(&self) -> &str {
&self.rsc.widgets().data(self.id).unwrap().label
}
pub fn id(&self) -> &WidgetId {
&self.id
}
}