Retain opt-in layout performance diagnostics
This commit is contained in:
1 parent
84f589e364
commit
480f0bc99f
8 files changed
+613
-22
No files matched your search
+78
-18
@@ -1,3 +1,5 @@
|
||||
#[cfg(feature = "layout-diagnostics")]
|
||||
use crate::layout_diagnostics::{self as diag, Counter, TimerKind};
|
||||
use crate::{
|
||||
ActiveData, Axis, DrawLayers, IdLike, MaskIdx, MoveIdx, Moves, OnResize, Painter, PixelRegion,
|
||||
Size, StrongWidget, UiRegion, UiRsc, WidgetId, Widgets,
|
||||
@@ -44,6 +46,10 @@ impl UiRenderState {
|
||||
}
|
||||
|
||||
pub fn update<'a>(&mut self, root: impl Into<Option<&'a StrongWidget>>, rsc: &mut dyn UiRsc) {
|
||||
#[cfg(feature = "layout-diagnostics")]
|
||||
diag::bump(Counter::Updates);
|
||||
#[cfg(feature = "layout-diagnostics")]
|
||||
let _update = diag::timer(TimerKind::Update);
|
||||
// safety mechanism for memory leaks; might wanna return a result instead so user can
|
||||
// decide whether to panic or not
|
||||
if !rsc.widgets().waiting.is_empty() {
|
||||
@@ -68,9 +74,15 @@ impl UiRenderState {
|
||||
// A region is a fraction of the output plus an offset, resolved
|
||||
// against the window in the shader, so a resize moves the whole
|
||||
// drawing on its own. Only a widget that read pixels can be wrong.
|
||||
for (&id, active) in &self.active {
|
||||
if active.reads_output {
|
||||
rsc.widgets_mut().needs_redraw.insert(id);
|
||||
{
|
||||
#[cfg(feature = "layout-diagnostics")]
|
||||
let _marking = diag::timer(TimerKind::ResizeMarking);
|
||||
for (&id, active) in &self.active {
|
||||
if active.reads_output {
|
||||
#[cfg(feature = "layout-diagnostics")]
|
||||
diag::bump(Counter::ResizeDependents);
|
||||
rsc.widgets_mut().needs_redraw.insert(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -81,6 +93,8 @@ impl UiRenderState {
|
||||
}
|
||||
|
||||
fn redraw_all(&mut self, root: Option<&StrongWidget>, rsc: &mut dyn UiRsc) {
|
||||
#[cfg(feature = "layout-diagnostics")]
|
||||
let _layout = diag::timer(TimerKind::FullLayout);
|
||||
self.clear(rsc);
|
||||
// free all resources & cache
|
||||
if let Some(id) = root {
|
||||
@@ -112,6 +126,8 @@ impl UiRenderState {
|
||||
old_children: Option<Vec<WidgetId>>,
|
||||
rsc: &mut dyn UiRsc,
|
||||
) -> Size {
|
||||
#[cfg(feature = "layout-diagnostics")]
|
||||
diag::bump(Counter::DrawRequests);
|
||||
let mut old_children = old_children.unwrap_or_default();
|
||||
if self.active.contains_key(&id) {
|
||||
if let Some(size) = self.try_reuse(id, region, parent_move, rsc) {
|
||||
@@ -151,6 +167,11 @@ impl UiRenderState {
|
||||
rsc,
|
||||
};
|
||||
|
||||
#[cfg(feature = "layout-diagnostics")]
|
||||
{
|
||||
diag::bump(Counter::WidgetDraws);
|
||||
diag::draw_widget(id, painter.rsc.widgets().label(id));
|
||||
}
|
||||
let mut widget = painter.rsc.widgets().get_dyn_dynamic(id);
|
||||
let size = widget.draw(&mut painter);
|
||||
drop(widget);
|
||||
@@ -246,31 +267,42 @@ impl UiRenderState {
|
||||
parent_move: MoveIdx,
|
||||
rsc: &mut dyn UiRsc,
|
||||
) -> Option<Size> {
|
||||
#[cfg(feature = "layout-diagnostics")]
|
||||
diag::bump(Counter::ReuseAttempts);
|
||||
if rsc.widgets().needs_redraw.contains(&id) {
|
||||
#[cfg(feature = "layout-diagnostics")]
|
||||
diag::bump(Counter::ReuseDirty);
|
||||
return None;
|
||||
}
|
||||
let active = self.active.get(&id)?;
|
||||
// Drawn somewhere else in the tree: its box is in coordinates it no
|
||||
// longer sits in, and its slot names the wrong parent.
|
||||
if active.parent_move != parent_move {
|
||||
#[cfg(feature = "layout-diagnostics")]
|
||||
diag::bump(Counter::ReuseWrongParent);
|
||||
return None;
|
||||
}
|
||||
let (size, old, slot, was) = (active.size, active.region, active.move_idx, active.px);
|
||||
let (size, old_region, slot, old_px) =
|
||||
(active.size, active.region, active.move_idx, active.px);
|
||||
// In pixels, because `region` is a fraction of a slot's box and that
|
||||
// box may be what changed -- an unchanged fraction of a box half the
|
||||
// size is half the widget.
|
||||
let px = self.px_of(parent_move, region);
|
||||
let mut changed = [false; 2];
|
||||
for (axis, c) in AXES.into_iter().zip(changed.iter_mut()) {
|
||||
*c = px.axis(axis) != was.axis(axis);
|
||||
*c = px.axis(axis) != old_px.axis(axis);
|
||||
}
|
||||
if !changed.iter().any(|&c| c) && old == region {
|
||||
if !changed.iter().any(|&c| c) && old_region == region {
|
||||
#[cfg(feature = "layout-diagnostics")]
|
||||
diag::bump(Counter::ReuseExact);
|
||||
return Some(size);
|
||||
}
|
||||
// Only a placed widget can be given a different box without drawing
|
||||
// again: everything it drew is a fraction of its slot's box, so one
|
||||
// entry says where all of it went.
|
||||
if slot == parent_move {
|
||||
#[cfg(feature = "layout-diagnostics")]
|
||||
diag::bump(Counter::ReuseUnslotted);
|
||||
return None;
|
||||
}
|
||||
if changed.iter().any(|&c| c) {
|
||||
@@ -282,7 +314,14 @@ impl UiRenderState {
|
||||
// Anything under it that has to be drawn again is drawn by drawing
|
||||
// this, because whatever reads that widget's size sits in between
|
||||
// and has to lay out around what it comes to.
|
||||
if redraws || self.redraws_under(id, changed, rsc) {
|
||||
if redraws {
|
||||
#[cfg(feature = "layout-diagnostics")]
|
||||
diag::bump(Counter::ReuseOwnResize);
|
||||
return None;
|
||||
}
|
||||
if self.redraws_under(id, changed, rsc) {
|
||||
#[cfg(feature = "layout-diagnostics")]
|
||||
diag::bump(Counter::ReuseDescendantResize);
|
||||
return None;
|
||||
}
|
||||
}
|
||||
@@ -290,6 +329,8 @@ impl UiRenderState {
|
||||
let active = self.active.get_mut(&id).unwrap();
|
||||
active.region = region;
|
||||
active.px = px;
|
||||
#[cfg(feature = "layout-diagnostics")]
|
||||
diag::bump(Counter::ReuseMoved);
|
||||
Some(size)
|
||||
}
|
||||
|
||||
@@ -303,11 +344,15 @@ impl UiRenderState {
|
||||
/// change length has no descendant whose box did, and the walk stops
|
||||
/// there -- an 80-wide child of a widened row is not asked at all.
|
||||
fn redraws_under(&self, id: WidgetId, changed: [bool; 2], rsc: &dyn UiRsc) -> bool {
|
||||
#[cfg(feature = "layout-diagnostics")]
|
||||
diag::bump(Counter::ResizeChecks);
|
||||
let Some(active) = self.active.get(&id) else {
|
||||
return false;
|
||||
};
|
||||
let size_deps = &active.size_deps;
|
||||
active.children.iter().any(|&child| {
|
||||
#[cfg(feature = "layout-diagnostics")]
|
||||
diag::bump(Counter::ResizeCheckChildren);
|
||||
let Some(data) = self.active.get(&child) else {
|
||||
return false;
|
||||
};
|
||||
@@ -397,6 +442,8 @@ impl UiRenderState {
|
||||
}
|
||||
|
||||
pub fn redraw_updates(&mut self, rsc: &mut dyn UiRsc) {
|
||||
#[cfg(feature = "layout-diagnostics")]
|
||||
let _layout = diag::timer(TimerKind::IncrementalLayout);
|
||||
// A reader's answer is only valid after every dirty size it reads has
|
||||
// settled. Equal-depth widgets are independent, so their order does
|
||||
// not matter.
|
||||
@@ -407,6 +454,8 @@ impl UiRenderState {
|
||||
.copied()
|
||||
.max_by_key(|&id| self.depth(id))
|
||||
{
|
||||
#[cfg(feature = "layout-diagnostics")]
|
||||
diag::bump(Counter::QueuePops);
|
||||
self.redraw(id, rsc);
|
||||
}
|
||||
rsc.free();
|
||||
@@ -416,6 +465,8 @@ impl UiRenderState {
|
||||
let mut depth = 0;
|
||||
let mut at = Some(id);
|
||||
while let Some(id) = at {
|
||||
#[cfg(feature = "layout-diagnostics")]
|
||||
diag::bump(Counter::DepthSteps);
|
||||
at = self.active.get(&id).and_then(|active| active.parent);
|
||||
depth += 1;
|
||||
}
|
||||
@@ -480,6 +531,8 @@ impl UiRenderState {
|
||||
if (self.resized || box_changed)
|
||||
&& let Some(top) = self.mark_readers(id, rsc)
|
||||
{
|
||||
#[cfg(feature = "layout-diagnostics")]
|
||||
diag::bump(Counter::EagerReaderRedraws);
|
||||
self.redraw(top, rsc);
|
||||
rsc.widgets_mut().needs_redraw.remove(&id);
|
||||
return;
|
||||
@@ -493,8 +546,10 @@ impl UiRenderState {
|
||||
let Some(active) = self.remove(id, false, rsc) else {
|
||||
return;
|
||||
};
|
||||
#[cfg(feature = "layout-diagnostics")]
|
||||
diag::bump(Counter::LocalRedraws);
|
||||
|
||||
let was = active.size;
|
||||
let old_size = active.size;
|
||||
let size = self.draw_inner(
|
||||
active.layer,
|
||||
id,
|
||||
@@ -507,16 +562,21 @@ impl UiRenderState {
|
||||
rsc,
|
||||
);
|
||||
|
||||
if size != was
|
||||
&& let Some(parent) = self.active.get(&id).and_then(|active| active.parent)
|
||||
&& self
|
||||
.active
|
||||
.get(&parent)
|
||||
.is_some_and(|active| active.size_deps.contains(&id))
|
||||
{
|
||||
// Propagate one dependency edge at a time. If drawing the reader
|
||||
// does not change its own size, nothing above it can observe this.
|
||||
rsc.widgets_mut().needs_redraw.insert(parent);
|
||||
if size != old_size {
|
||||
#[cfg(feature = "layout-diagnostics")]
|
||||
diag::bump(Counter::SizeChanges);
|
||||
if let Some(parent) = self.active.get(&id).and_then(|active| active.parent)
|
||||
&& self
|
||||
.active
|
||||
.get(&parent)
|
||||
.is_some_and(|active| active.size_deps.contains(&id))
|
||||
{
|
||||
// Propagate one dependency edge at a time. If drawing the reader
|
||||
// does not change its own size, nothing above it can observe this.
|
||||
rsc.widgets_mut().needs_redraw.insert(parent);
|
||||
#[cfg(feature = "layout-diagnostics")]
|
||||
diag::bump(Counter::ReaderEdges);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user