Answer "have I asked this child?" in one read
A container's draw asked it once per child by searching the list of children it had added so far, and four other per-child steps searched a list too, so one draw cost the square of its children: 70% of a 1,600-child redraw was those searches. A draw takes a DrawId and leaves it on every widget it asks about; one note per widget is enough because the handle a container holds a child by cannot be cloned. tests/children_cost.rs is the rig that shows it, and it is the only one here that varies width: 3.680 ms to 0.811 ms at 1,600 children, and flat per child. Beside it, the rest of the fourteenth sweep of #19: a mask's rectangle resolved once per fragment instead of once per instance, which takes the storage buffers out of the fragment stage and is 8.8x on a screenful of deeply nested clips; TextBuffer::shape copying its attrs before the check that would not need them, which allocated once per named-family text per frame; a should_panic test on a debug assertion that made cargo test --release fail; Fixed::div, reached only by its own test; Moves::remove re-uploading an array it cannot have changed; and two comments the branch itself falsified. docs/LAYOUT_LOG.md has all eight with their measurements, the five things looked at and left, and what was verified.
This commit is contained in:
1 parent
cbccfb600a
commit
97fca76108
17 files changed
+588
-149
No files matched your search
+77
-34
@@ -8,13 +8,15 @@
|
||||
//! submitted and waited on, so this is the GPU's cost and not the recording
|
||||
//! loop's -- which is what `draw_cost.rs` measures instead.
|
||||
//!
|
||||
//! The instances are two pixels wide so that vertex work dominates; a chain
|
||||
//! walk that does not show up against small quads will not show up against
|
||||
//! anything.
|
||||
//! Two fixtures, because the walk happens in both stages. `chain_cost_by_depth`
|
||||
//! draws instances two pixels wide so that vertex work dominates; a walk that
|
||||
//! does not show up against small quads will not show up against anything.
|
||||
//! `mask_cost_by_depth` draws one screenful through a mask instead, which is
|
||||
//! where a walk in the fragment stage would show and nowhere else.
|
||||
|
||||
use iris::prelude::*;
|
||||
use iris_core::{
|
||||
Len, MaskIdx, MoveIdx, PrimitiveInst, RectPrimitive, UiData, UiRegion, UiRenderNode,
|
||||
Len, Mask, MaskIdx, MoveIdx, PrimitiveInst, RectPrimitive, UiData, UiRegion, UiRenderNode,
|
||||
UiRenderState, UiSpan,
|
||||
};
|
||||
use wgpu::{Color as GpuColor, *};
|
||||
@@ -45,8 +47,17 @@ fn gpu() -> Option<(Device, Queue, f32)> {
|
||||
Some((device, queue, period))
|
||||
}
|
||||
|
||||
/// Which stage the fill puts the work in: many small quads, where a walk per
|
||||
/// vertex is what shows, or one screenful of masked rows, where a walk per
|
||||
/// fragment would.
|
||||
#[derive(Clone, Copy)]
|
||||
enum Fixture {
|
||||
Quads,
|
||||
Masked,
|
||||
}
|
||||
|
||||
/// A chain `depth` slots long, and instances that all resolve through its end.
|
||||
fn fill(ui: &mut UiData, render: &mut UiRenderState, depth: usize) {
|
||||
fn fill(ui: &mut UiData, render: &mut UiRenderState, depth: usize, fixture: Fixture) {
|
||||
let kind = ui.primitives.kind::<RectPrimitive>();
|
||||
let id = ui.widgets.add_strong(Rect::new(UiColor::WHITE)).id();
|
||||
|
||||
@@ -56,20 +67,51 @@ fn fill(ui: &mut UiData, render: &mut UiRenderState, depth: usize) {
|
||||
}
|
||||
|
||||
let px = |v: f32| Len::px(v);
|
||||
for i in 0..INSTANCES {
|
||||
let x = (i % (SIZE as usize / 2)) as f32 * 2.0;
|
||||
let y = (i / (SIZE as usize / 2)) as f32;
|
||||
let rows = SIZE as usize;
|
||||
let mask_idx = match fixture {
|
||||
Fixture::Quads => MaskIdx::NONE,
|
||||
// Its own chain as long as the instances', since a viewport sits as
|
||||
// deep in the tree as the content it clips.
|
||||
Fixture::Masked => {
|
||||
let idx = ui.masks.push(Mask {
|
||||
region: UiRegion::FULL,
|
||||
move_idx: slot,
|
||||
});
|
||||
// Nothing frees it here, but the owner's reference is what a real
|
||||
// one is kept alive by.
|
||||
ui.masks.push_ref(idx);
|
||||
idx
|
||||
}
|
||||
};
|
||||
let instances = match fixture {
|
||||
Fixture::Quads => INSTANCES,
|
||||
Fixture::Masked => rows,
|
||||
};
|
||||
for i in 0..instances {
|
||||
let region = match fixture {
|
||||
Fixture::Quads => {
|
||||
let x = (i % (rows / 2)) as f32 * 2.0;
|
||||
let y = (i / (rows / 2)) as f32;
|
||||
UiRegion::new(
|
||||
UiSpan::new(px(x), px(x + 2.0)),
|
||||
UiSpan::new(px(y), px(y + 1.0)),
|
||||
)
|
||||
}
|
||||
// A full row each, so one screenful of fragments goes through the
|
||||
// mask and the vertex stage is four corners per row.
|
||||
Fixture::Masked => UiRegion::new(
|
||||
UiSpan::new(px(0.0), px(SIZE as f32)),
|
||||
UiSpan::new(px(i as f32), px(i as f32 + 1.0)),
|
||||
),
|
||||
};
|
||||
render.layers.write(
|
||||
0,
|
||||
PrimitiveInst {
|
||||
kind,
|
||||
id,
|
||||
primitive: RectPrimitive::color(UiColor::WHITE),
|
||||
region: UiRegion::new(
|
||||
UiSpan::new(px(x), px(x + 2.0)),
|
||||
UiSpan::new(px(y), px(y + 1.0)),
|
||||
),
|
||||
mask_idx: MaskIdx::NONE,
|
||||
region,
|
||||
mask_idx,
|
||||
move_idx: slot,
|
||||
},
|
||||
);
|
||||
@@ -77,28 +119,15 @@ fn fill(ui: &mut UiData, render: &mut UiRenderState, depth: usize) {
|
||||
}
|
||||
|
||||
/// Nanoseconds the pass took on the GPU, best of `BATCHES`.
|
||||
fn pass_cost(device: &Device, queue: &Queue, period: f32, depth: usize) -> f64 {
|
||||
fn pass_cost(device: &Device, queue: &Queue, period: f32, depth: usize, fixture: Fixture) -> f64 {
|
||||
let format = TextureFormat::Bgra8Unorm;
|
||||
let mut node = UiRenderNode::new(device, &gpu::config(format, SIZE));
|
||||
let mut ui = UiData::default();
|
||||
let mut render = UiRenderState::new();
|
||||
fill(&mut ui, &mut render, depth);
|
||||
fill(&mut ui, &mut render, depth, fixture);
|
||||
node.update(device, queue, &mut ui, &mut render);
|
||||
|
||||
let target = device.create_texture(&TextureDescriptor {
|
||||
label: Some("chain cost"),
|
||||
size: Extent3d {
|
||||
width: SIZE,
|
||||
height: SIZE,
|
||||
depth_or_array_layers: 1,
|
||||
},
|
||||
mip_level_count: 1,
|
||||
sample_count: 1,
|
||||
dimension: TextureDimension::D2,
|
||||
format,
|
||||
usage: TextureUsages::RENDER_ATTACHMENT,
|
||||
view_formats: &[],
|
||||
});
|
||||
let target = gpu::target(device, format, SIZE, false);
|
||||
let view = target.create_view(&TextureViewDescriptor::default());
|
||||
|
||||
let queries = device.create_query_set(&QuerySetDescriptor {
|
||||
@@ -178,17 +207,15 @@ fn pass_cost(device: &Device, queue: &Queue, period: f32, depth: usize) -> f64 {
|
||||
best
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore = "measurement, not a check"]
|
||||
fn chain_cost_by_depth() {
|
||||
fn by_depth(fixture: Fixture, instances: usize) {
|
||||
let Some((device, queue, period)) = gpu() else {
|
||||
println!("no gpu with timestamps; nothing measured");
|
||||
return;
|
||||
};
|
||||
println!("{INSTANCES} instances, {SIZE}x{SIZE}, best of {BATCHES} batches");
|
||||
println!("{instances} instances, {SIZE}x{SIZE}, best of {BATCHES} batches");
|
||||
let mut base = None;
|
||||
for depth in [1, 2, 4, 8, 16, 32, 64] {
|
||||
let ns = pass_cost(&device, &queue, period, depth);
|
||||
let ns = pass_cost(&device, &queue, period, depth, fixture);
|
||||
let base = *base.get_or_insert(ns);
|
||||
println!(
|
||||
"depth {depth:>3}: {:>9.1} us {:+6.1}% against depth 1",
|
||||
@@ -197,3 +224,19 @@ fn chain_cost_by_depth() {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore = "measurement, not a check"]
|
||||
fn chain_cost_by_depth() {
|
||||
by_depth(Fixture::Quads, INSTANCES);
|
||||
}
|
||||
|
||||
/// One screenful of rows, every one clipped by a mask whose own chain is that
|
||||
/// deep. What this says that the quads cannot is whether a mask costs the walk
|
||||
/// once per instance or once per fragment: at a screenful of fragments per
|
||||
/// chain, the second is the difference between these two tables.
|
||||
#[test]
|
||||
#[ignore = "measurement, not a check"]
|
||||
fn mask_cost_by_depth() {
|
||||
by_depth(Fixture::Masked, SIZE as usize);
|
||||
}
|
||||
Reference in new issue
Block a user