tawasuyu

tawasuyu/taffy

A high performance rust-powered UI layout library

HTML 5544Rust 88Markdown 11TOML 91926 Definiciones

Optimise CSS Grid (#338)

* Only compute the intrinsic sizes of children that are actually required

* Use inner_node_size rather than available_grid_space for margin and known_dimension computation

* Convert distribute_item_space_to_base_size to use inner_node_size rather than available_grid_space

* Convert compute_alignment_gutter_adjustment to use inner_node_size

* Remove track_sizing_algorithm dependency on available_space

* Convert definite_value methods on track sizing functions to take Option<f32> instead of AvailableSpace

* Scatter inline(always) on grid sizing functions

* Add early return to grid algorithm for RunMode::ComputeSize case

* Use separate cache slots for width and height

* Remove absolutely positioned child from deep grid benchmarks (fixes superdeep benchmarks)

* Add caching tests for css grid

* Cache minimum_contribution  correctly

6ce28e1535f2ab4631d0502f2fee13d4827f2a11 · por Nico Burns · (hace 3 años) · padre 1624a68d7 · ver el árbol en este commit

Delta semántico · 36

benches/big_tree_grid.rs

src/compute/flexbox.rs

src/compute/grid/explicit_grid.rs

src/compute/grid/mod.rs

src/compute/grid/track_sizing.rs

src/compute/grid/types/grid_item.rs

src/compute/mod.rs

src/data.rs

src/layout.rs

src/style/grid.rs

tests/caching.rs

tests/measure.rs

12 archivos · +321 −209

diff --git a/benches/big_tree_grid.rs b/benches/big_tree_grid.rs
index 23c4f3e0e..1077dd934 100644
--- a/benches/big_tree_grid.rs
+++ b/benches/big_tree_grid.rs
@@ -67,7 +67,7 @@ pub fn build_deep_grid_tree(
     create_container_node: &mut impl FnMut(&mut Taffy, Vec<Node>) -> Node,
 ) -> Vec<Node> {
     // The extra one is for a position:absolute child
-    let child_count = track_count.pow(2) + 1;
+    let child_count = track_count * track_count;
 
     if levels == 1 {
         // Build leaf nodes
@@ -80,13 +80,6 @@ pub fn build_deep_grid_tree(
         .map(|_| {
             let sub_children =
                 build_deep_grid_tree(tree, levels - 1, track_count, create_leaf_node, create_container_node);
-            tree.set_style(sub_children[child_count - 1], {
-                let mut style = tree.style(sub_children[child_count - 1]).unwrap().clone();
-                style.position = Position::Absolute;
-                style.size = percent(0.5);
-                style
-            })
-            .unwrap();
             create_container_node(tree, sub_children)
         })
         .collect()
@@ -127,8 +120,8 @@ fn taffy_benchmarks(c: &mut Criterion) {
 
     let mut group = c.benchmark_group("grid/deep");
     group.sample_size(10);
-    for (tracks, levels) in [(3, 3), (3, 4) /*, (3, 5)*/, (2, 3), (2, 4), (2, 5), (2, 6)].iter() {
-        let children_per_level: usize = (tracks * tracks) + 1;
+    for (tracks, levels) in [(2, 5), (3, 4), (2, 7) /*, (3, 5)*/].iter() {
+        let children_per_level: usize = tracks * tracks;
         group.bench_with_input(
             BenchmarkId::new(format!("{c}x{c}", c = tracks), children_per_level.pow(*levels as u32)),
             &(*levels, *tracks),
@@ -145,7 +138,7 @@ fn taffy_benchmarks(c: &mut Criterion) {
 
     let mut group = c.benchmark_group("grid/superdeep");
     group.sample_size(10);
-    for levels in [10, 20].iter() {
+    for levels in [100, 1000].iter() {
         group.bench_with_input(BenchmarkId::new("1x1", levels), levels, |b, &levels| {
             b.iter_batched(
                 || build_taffy_deep_grid_hierarchy(levels, 1),
diff --git a/src/compute/flexbox.rs b/src/compute/flexbox.rs
index a0279872d..f4cee05f2 100644
--- a/src/compute/flexbox.rs
+++ b/src/compute/flexbox.rs
@@ -364,7 +364,7 @@ fn compute_preliminary(
     // We have the container size.
     // If our caller does not care about performing layout we are done now.
     if run_mode == RunMode::ComputeSize {
-        return SizeAndBaselines { size: constants.container_size, first_baselines: Point::NONE };
+        return constants.container_size.into();
     }
 
     // 16. Align all flex lines per align-content.
diff --git a/src/compute/grid/explicit_grid.rs b/src/compute/grid/explicit_grid.rs
index 6cdadbf54..ef85bb389 100644
--- a/src/compute/grid/explicit_grid.rs
+++ b/src/compute/grid/explicit_grid.rs
@@ -4,9 +4,7 @@ use super::types::{GridTrack, TrackCounts};
 use crate::axis::AbsoluteAxis;
 use crate::math::MaybeMath;
 use crate::resolve::ResolveOrZero;
-use crate::style::{
-    AvailableSpace, GridTrackRepetition, LengthPercentage, NonRepeatedTrackSizingFunction, Style, TrackSizingFunction,
-};
+use crate::style::{GridTrackRepetition, LengthPercentage, NonRepeatedTrackSizingFunction, Style, TrackSizingFunction};
 use crate::style_helpers::TaffyAuto;
 use crate::sys::{GridTrackVec, Vec};
 use core::cmp::{max, min};
@@ -83,25 +81,20 @@ pub(crate) fn compute_explicit_grid_size_in_axis(style: &Style, axis: AbsoluteAx
     let num_repetitions: u16 = match inner_container_size {
         None => 1,
         Some(inner_container_size) => {
-            let available_space = AvailableSpace::Definite(inner_container_size);
+            let parent_size = Some(inner_container_size);
 
             /// ...treating each track as its max track sizing function if that is definite or as its minimum track sizing function
             /// otherwise, flooring the max track sizing function by the min track sizing function if both are definite
-            fn track_definite_value(
-                sizing_function: &NonRepeatedTrackSizingFunction,
-                available_space: AvailableSpace,
-            ) -> f32 {
-                let max_size = sizing_function.max.definite_value(available_space);
-                let min_size = sizing_function.max.definite_value(available_space);
+            fn track_definite_value(sizing_function: &NonRepeatedTrackSizingFunction, parent_size: Option<f32>) -> f32 {
+                let max_size = sizing_function.max.definite_value(parent_size);
+                let min_size = sizing_function.max.definite_value(parent_size);
                 max_size.map(|max| max.maybe_min(min_size)).or(min_size).unwrap()
             }
 
             let non_repeating_track_used_space: f32 = template
                 .iter()
                 .map(|track_def| match track_def {
-                    TrackSizingFunction::Single(sizing_function) => {
-                        track_definite_value(sizing_function, available_space)
-                    }
+                    TrackSizingFunction::Single(sizing_function) => track_definite_value(sizing_function, parent_size),
                     TrackSizingFunction::AutoRepeat(_, _) => 0.0,
                 })
                 .sum();
@@ -110,7 +103,7 @@ pub(crate) fn compute_explicit_grid_size_in_axis(style: &Style, axis: AbsoluteAx
             // Compute the amount of space that a single repetition of the repeated track list takes
             let per_repetition_track_used_space: f32 = repetition_definition
                 .iter()
-                .map(|sizing_function| track_definite_value(sizing_function, available_space))
+                .map(|sizing_function| track_definite_value(sizing_function, parent_size))
                 .sum::<f32>();
 
             // We special case the first repetition here because the number of gaps in the first repetition
diff --git a/src/compute/grid/mod.rs b/src/compute/grid/mod.rs
index c8f6c2939..1fc504081 100644
--- a/src/compute/grid/mod.rs
+++ b/src/compute/grid/mod.rs
@@ -72,7 +72,7 @@ pub fn compute(
     known_dimensions: Size<Option<f32>>,
     parent_size: Size<Option<f32>>,
     available_space: Size<AvailableSpace>,
-    _run_mode: RunMode,
+    run_mode: RunMode,
 ) -> SizeAndBaselines {
     let get_child_styles_iter = |node| tree.children(node).map(|child_node: &Node| tree.style(*child_node));
     let style = tree.style(node).clone();
@@ -181,16 +181,15 @@ pub fn compute(
     track_sizing_algorithm(
         tree,
         AbstractAxis::Inline,
-        available_space,
+        min_size.get(AbstractAxis::Inline),
+        max_size.get(AbstractAxis::Inline),
+        style.grid_align_content(AbstractAxis::Block),
         available_grid_space,
         inner_node_size,
-        &style,
         &mut columns,
         &mut rows,
         &mut items,
-        |track: &GridTrack, available_space: AvailableSpace| {
-            track.max_track_sizing_function.definite_value(available_space)
-        },
+        |track: &GridTrack, parent_size: Option<f32>| track.max_track_sizing_function.definite_value(parent_size),
     );
     let initial_column_sum = columns.iter().map(|track| track.base_size).sum::<f32>();
     inner_node_size.width = inner_node_size.width.or_else(|| initial_column_sum.into());
@@ -199,10 +198,11 @@ pub fn compute(
     track_sizing_algorithm(
         tree,
         AbstractAxis::Block,
-        available_space,
+        min_size.get(AbstractAxis::Block),
+        max_size.get(AbstractAxis::Block),
+        style.grid_align_content(AbstractAxis::Inline),
         available_grid_space,
         inner_node_size,
-        &style,
         &mut rows,
         &mut columns,
         &mut items,
@@ -211,14 +211,35 @@ pub fn compute(
     let initial_row_sum = rows.iter().map(|track| track.base_size).sum::<f32>();
     inner_node_size.height = inner_node_size.height.or_else(|| initial_row_sum.into());
 
+    // 6. Compute container size
+    let resolved_style_size = known_dimensions.or(style.size.maybe_resolve(parent_size));
+    let container_border_box = Size {
+        width: resolved_style_size
+            .get(AbstractAxis::Inline)
+            .unwrap_or_else(|| initial_column_sum + padding.horizontal_axis_sum() + border.horizontal_axis_sum()),
+        height: resolved_style_size
+            .get(AbstractAxis::Block)
+            .unwrap_or_else(|| initial_row_sum + padding.vertical_axis_sum() + border.vertical_axis_sum()),
+    };
+    let container_content_box = Size {
+        width: container_border_box.width - padding.horizontal_axis_sum() - border.horizontal_axis_sum(),
+        height: container_border_box.height - padding.vertical_axis_sum() - border.vertical_axis_sum(),
+    };
+
+    // If only the container's size has been requested
+    if run_mode == RunMode::ComputeSize {
+        return container_border_box.into();
+    }
+
     // Re-run track sizing algorithm for Inline axis
     track_sizing_algorithm(
         tree,
         AbstractAxis::Inline,
-        available_space,
+        min_size.get(AbstractAxis::Inline),
+        max_size.get(AbstractAxis::Inline),
+        style.grid_align_content(AbstractAxis::Block),
         available_grid_space,
         inner_node_size,
-        &style,
         &mut columns,
         &mut rows,
         &mut items,
@@ -229,31 +250,17 @@ pub fn compute(
     track_sizing_algorithm(
         tree,
         AbstractAxis::Block,
-        available_space,
+        min_size.get(AbstractAxis::Block),
+        max_size.get(AbstractAxis::Block),
+        style.grid_align_content(AbstractAxis::Inline),
         available_grid_space,
         inner_node_size,
-        &style,
         &mut rows,
         &mut columns,
         &mut items,
         |track: &GridTrack, _| Some(track.base_size),
     );
 
-    // 6. Compute container size
-    let resolved_style_size = known_dimensions.or(style.size.maybe_resolve(parent_size));
-    let container_border_box = Size {
-        width: resolved_style_size
-            .get(AbstractAxis::Inline)
-            .unwrap_or_else(|| initial_column_sum + padding.horizontal_axis_sum() + border.horizontal_axis_sum()),
-        height: resolved_style_size
-            .get(AbstractAxis::Block)
-            .unwrap_or_else(|| initial_row_sum + padding.vertical_axis_sum() + border.vertical_axis_sum()),
-    };
-    let container_content_box = Size {
-        width: container_border_box.width - padding.horizontal_axis_sum() - border.horizontal_axis_sum(),
-        height: container_border_box.height - padding.vertical_axis_sum() - border.vertical_axis_sum(),
-    };
-
     // 7. Resolve percentage track base sizes
     // In the case of an indefinitely sized container these resolve to zero during the "Initialise Tracks" step
     // and therefore need to be re-resolved here based on the content-sized content box of the container
diff --git a/src/compute/grid/track_sizing.rs b/src/compute/grid/track_sizing.rs
index 65d64dd92..fc6943ffc 100644
--- a/src/compute/grid/track_sizing.rs
+++ b/src/compute/grid/track_sizing.rs
@@ -5,8 +5,8 @@ use crate::axis::AbstractAxis;
 use crate::geometry::{Rect, Size};
 use crate::math::MaybeMath;
 use crate::prelude::LayoutTree;
-use crate::resolve::{MaybeResolve, ResolveOrZero};
-use crate::style::{AlignContent, AvailableSpace, MaxTrackSizingFunction, MinTrackSizingFunction, Style};
+use crate::resolve::ResolveOrZero;
+use crate::style::{AlignContent, AvailableSpace, MaxTrackSizingFunction, MinTrackSizingFunction};
 use crate::sys::{f32_max, f32_min};
 use core::cmp::Ordering;
 
@@ -26,12 +26,14 @@ struct ItemBatcher {
 
 impl ItemBatcher {
     /// Create a new ItemBatcher for the specified axis
+    #[inline(always)]
     fn new(axis: AbstractAxis) -> Self {
         ItemBatcher { index_offset: 0, axis, current_span: 1, current_is_flex: false }
     }
 
     /// This is basically a manual version of Iterator::next which passes `items`
     /// in as a parameter on each iteration to work around borrow checker rules
+    #[inline]
     fn next<'items>(&mut self, items: &'items mut [GridItem]) -> Option<(&'items mut [GridItem], bool)> {
         if self.current_is_flex || self.index_offset >= items.len() {
             return None;
@@ -88,12 +90,17 @@ pub(super) fn cmp_by_cross_flex_then_span_then_start(
 /// When applying the track sizing algorithm and estimating the size in the other axis for content sizing items
 /// we should take into account align-content/justify-content if both the grid container and all items in the
 /// other axis have definite sizes. This function computes such a per-gutter additional size adjustment.
+#[inline(always)]
 pub(super) fn compute_alignment_gutter_adjustment(
     alignment: AlignContent,
-    available_space: AvailableSpace,
-    get_track_size_estimate: impl Fn(&GridTrack, AvailableSpace) -> Option<f32>,
+    axis_inner_node_size: Option<f32>,
+    get_track_size_estimate: impl Fn(&GridTrack, Option<f32>) -> Option<f32>,
     tracks: &[GridTrack],
 ) -> f32 {
+    if tracks.len() <= 1 {
+        return 0.0;
+    }
+
     // As items never cross the outermost gutters in a grid, we can simplify our calculations by treating
     // AlignContent::Start and AlignContent::End the same
     let outer_gutter_weight = match alignment {
@@ -120,24 +127,29 @@ pub(super) fn compute_alignment_gutter_adjustment(
         AlignContent::SpaceEvenly => 1,
     };
 
-    if inner_gutter_weight > 0 && available_space.is_definite() && tracks.len() > 1 {
-        let inner_available_space = tracks
+    if inner_gutter_weight == 0 {
+        return 0.0;
+    }
+
+    if let Some(axis_inner_node_size) = axis_inner_node_size {
+        let free_space = tracks
             .iter()
-            .map(|track| get_track_size_estimate(track, available_space))
+            .map(|track| get_track_size_estimate(track, Some(axis_inner_node_size)))
             .sum::<Option<f32>>()
-            .map(|track_size_sum| f32_max(0.0, available_space.unwrap() - track_size_sum))
+            .map(|track_size_sum| f32_max(0.0, axis_inner_node_size - track_size_sum))
             .unwrap_or(0.0);
 
         let weighted_track_count =
             (((tracks.len() - 3) / 2) * inner_gutter_weight as usize) + (2 * outer_gutter_weight as usize);
 
-        (inner_available_space / weighted_track_count as f32) * inner_gutter_weight as f32
-    } else {
-        0.0
+        return (free_space / weighted_track_count as f32) * inner_gutter_weight as f32;
     }
+
+    0.0
 }
 
 /// Convert origin-zero coordinates track placement in grid track vector indexes
+#[inline(always)]
 pub(super) fn resolve_item_track_indexes(items: &mut [GridItem], column_counts: TrackCounts, row_counts: TrackCounts) {
     for item in items {
         item.column_indexes = item.column.map(|line| line.into_track_vec_index(column_counts) as u16);
@@ -146,6 +158,7 @@ pub(super) fn resolve_item_track_indexes(items: &mut [GridItem], column_counts:
 }
 
 /// Determine (in each axis) whether the item crosses any flexible tracks
+#[inline(always)]
 pub(super) fn determine_if_item_crosses_flexible_tracks(
     items: &mut Vec<GridItem>,
     columns: &[GridTrack],
@@ -162,17 +175,19 @@ pub(super) fn determine_if_item_crosses_flexible_tracks(
 /// Track sizing algorithm
 /// Note: Gutters are treated as empty fixed-size tracks for the purpose of the track sizing algorithm.
 #[allow(clippy::too_many_arguments)]
+#[inline(always)]
 pub(super) fn track_sizing_algorithm<Tree: LayoutTree>(
     tree: &mut Tree,
     axis: AbstractAxis,
-    available_space: Size<AvailableSpace>,
+    axis_min_size: Option<f32>,
+    axis_max_size: Option<f32>,
+    other_axis_alignment: AlignContent,
     available_grid_space: Size<AvailableSpace>,
     inner_node_size: Size<Option<f32>>,
-    container_style: &Style,
     axis_tracks: &mut [GridTrack],
     other_axis_tracks: &mut [GridTrack],
     items: &mut [GridItem],
-    get_track_size_estimate: impl Fn(&GridTrack, AvailableSpace) -> Option<f32>,
+    get_track_size_estimate: impl Fn(&GridTrack, Option<f32>) -> Option<f32>,
 ) {
     // 11.4 Initialise Track sizes
     // Initialize each track’s base size and growth limit.
@@ -198,8 +213,8 @@ pub(super) fn track_sizing_algorithm<Tree: LayoutTree>(
     // Compute an additional amount to add to each spanned gutter when computing item's estimated size in the
     // in the opposite axis based on the alignment, container size, and estimated track sizes in that axis
     let gutter_alignment_adjustment = compute_alignment_gutter_adjustment(
-        container_style.grid_align_content(axis.other()),
-        available_space.get(axis.other()),
+        other_axis_alignment,
+        inner_node_size.get(axis.other()),
         &get_track_size_estimate,
         other_axis_tracks,
     );
@@ -218,19 +233,17 @@ pub(super) fn track_sizing_algorithm<Tree: LayoutTree>(
         axis_tracks,
         other_axis_tracks,
         items,
-        available_grid_space,
+        available_grid_space.get(axis),
         inner_node_size,
         get_track_size_estimate,
     );
 
     // 11.6. Maximise Tracks
     // Distributes free space (if any) to tracks with FINITE growth limits, up to their limits.
-    maximise_tracks(axis, axis_tracks, available_grid_space);
+    maximise_tracks(axis_tracks, inner_node_size.get(axis), available_grid_space.get(axis));
 
     // 11.7. Expand Flexible Tracks
     // This step sizes flexible tracks using the largest value it can assign to an fr without exceeding the available space.
-    let axis_min_size = container_style.min_size.get(axis).into_option();
-    let axis_max_size = container_style.max_size.get(axis).into_option();
     expand_flexible_tracks(
         tree,
         axis,
@@ -244,7 +257,7 @@ pub(super) fn track_sizing_algorithm<Tree: LayoutTree>(
 
     // 11.8. Stretch auto Tracks
     // This step expands tracks that have an auto max track sizing function by dividing any remaining positive, definite free space equally amongst them.
-    stretch_auto_tracks(axis, axis_tracks, container_style, available_space, available_grid_space);
+    stretch_auto_tracks(axis, axis_tracks, axis_min_size, available_grid_space);
 }
 
 /// Whether it is a minimum or maximum size's space being distributed
@@ -260,6 +273,7 @@ enum IntrinsicContributionType {
 
 /// Add any planned base size increases to the base size after a round of distributing space to base sizes
 /// Reset the planed base size increase to zero ready for the next round.
+#[inline(always)]
 fn flush_planned_base_size_increases(tracks: &mut [GridTrack]) {
     for track in tracks {
         track.base_size += track.base_size_planned_increase;
@@ -269,6 +283,7 @@ fn flush_planned_base_size_increases(tracks: &mut [GridTrack]) {
 
 /// Add any planned growth limit increases to the growth limit after a round of distributing space to growth limits
 /// Reset the planed growth limit increase to zero ready for the next round.
+#[inline(always)]
 fn flush_planned_growth_limit_increases(tracks: &mut [GridTrack], set_infinitely_growable: bool) {
     for track in tracks {
         if track.growth_limit_planned_increase > 0.0 {
@@ -287,6 +302,7 @@ fn flush_planned_growth_limit_increases(tracks: &mut [GridTrack], set_infinitely
 
 /// 11.4 Initialise Track sizes
 /// Initialize each track’s base size and growth limit.
+#[inline(always)]
 fn initialize_track_sizes(axis_tracks: &mut [GridTrack], axis_inner_node_size: Option<f32>) {
     let last_track_idx = axis_tracks.len() - 1;
 
@@ -304,7 +320,7 @@ fn initialize_track_sizes(axis_tracks: &mut [GridTrack], axis_inner_node_size: O
         //     Note: Indefinite lengths cannot occur, as they’re treated as auto.
         // - An intrinsic sizing function
         //     Use an initial base size of zero.
-        track.base_size = track.min_track_sizing_function.definite_value(axis_inner_node_size.into()).unwrap_or(0.0);
+        track.base_size = track.min_track_sizing_function.definite_value(axis_inner_node_size).unwrap_or(0.0);
 
         // For each track, if the track’s max track sizing function is:
         // - A fixed sizing function
@@ -314,7 +330,7 @@ fn initialize_track_sizes(axis_tracks: &mut [GridTrack], axis_inner_node_size: O
         // - A flexible sizing function
         //     Use an initial growth limit of infinity.
         track.growth_limit =
-            track.max_track_sizing_function.definite_value(axis_inner_node_size.into()).unwrap_or(f32::INFINITY);
+            track.max_track_sizing_function.definite_value(axis_inner_node_size).unwrap_or(f32::INFINITY);
 
         // In all cases, if the growth limit is less than the base size, increase the growth limit to match the base size.
         if track.growth_limit < track.base_size {
@@ -331,9 +347,9 @@ fn resolve_intrinsic_track_sizes(
     axis_tracks: &mut [GridTrack],
     other_axis_tracks: &mut [GridTrack],
     items: &mut [GridItem],
-    available_grid_space: Size<AvailableSpace>,
+    axis_available_grid_space: AvailableSpace,
     inner_node_size: Size<Option<f32>>,
-    get_track_size_estimate: impl Fn(&GridTrack, AvailableSpace) -> Option<f32>,
+    get_track_size_estimate: impl Fn(&GridTrack, Option<f32>) -> Option<f32>,
 ) {
     // Step 1. Shim baseline-aligned items so their intrinsic size contributions reflect their baseline alignment.
     // TODO: we do not yet support baseline alignment for CSS Grid
@@ -355,58 +371,121 @@ fn resolve_intrinsic_track_sizes(
     // Note: For items with a specified minimum size of auto (the initial value), the minimum contribution is usually equivalent
     // to the min-content contribution—but can differ in some cases, see §6.6 Automatic Minimum Size of Grid Items.
     // Also, minimum contribution <= min-content contribution <= max-content contribution.
-    // TODO: be smarter about only computing these when they are required
-    let mut compute_item_sizes = |item: &mut GridItem, axis_tracks: &[GridTrack]| {
-        let known_dimensions = item.known_dimensions_cached(
-            axis,
-            other_axis_tracks,
-            available_grid_space.get(axis.other()),
-            &get_track_size_estimate,
-        );
 
-        let margin = Rect {
-            left: item.margin.left.resolve_or_zero(Some(0.0)),
-            right: item.margin.right.resolve_or_zero(Some(0.0)),
-            top: item.margin.top.resolve_or_zero(available_grid_space.width.into_option()),
-            bottom: item.margin.bottom.resolve_or_zero(available_grid_space.width.into_option()),
+    /// This struct captures a bunch of variables which are used to compute the intrinsic sizes of children so that those variables
+    /// don't have to be passed around all over the place below. It then has methods that implement the intrinsic sizing computations
+    struct IntrisicSizeMeasurer<'tree, 'oat, Tree, EstimateFunction>
+    where
+        Tree: LayoutTree,
+        EstimateFunction: Fn(&GridTrack, Option<f32>) -> Option<f32>,
+    {
+        /// The layout tree
+        tree: &'tree mut Tree,
+        /// The tracks in the opposite axis to the one we are currently sizing
+        other_axis_tracks: &'oat [GridTrack],
+        /// A function that computes an estimate of an other-axis track's size which is passed to
+        /// the child size measurement functions
+        get_track_size_estimate: EstimateFunction,
+        /// The axis we are currently sizing
+        axis: AbstractAxis,
+        /// The available grid space
+        inner_node_size: Size<Option<f32>>,
+    }
+
+    impl<'tree, 'oat, Tree, EstimateFunction> IntrisicSizeMeasurer<'tree, 'oat, Tree, EstimateFunction>
+    where
+        Tree: LayoutTree,
+        EstimateFunction: Fn(&GridTrack, Option<f32>) -> Option<f32>,
+    {
+        /// Compute the known_dimensions to be passed to the child sizing functions
+        /// These are estimates based on either the max track sizing function or the provisional base size in the opposite
+        /// axis to the one currently being sized.
+        /// https://www.w3.org/TR/css-grid-1/#algo-overview
+        #[inline(always)]
+        fn known_dimensions(&self, item: &mut GridItem) -> Size<Option<f32>> {
+            item.known_dimensions_cached(
+                self.axis,
+                self.other_axis_tracks,
+                self.inner_node_size.get(self.axis.other()),
+                &self.get_track_size_estimate,
+            )
         }
-        .sum_axes();
 
-        let min_content_size = item.min_content_contribution_cached(tree, known_dimensions, inner_node_size) + margin;
-        let max_content_size = item.max_content_contribution_cached(tree, known_dimensions, inner_node_size) + margin;
-        let axis_minimum_size = item.minimum_contribution_cached(
-            tree,
-            axis,
-            axis_tracks,
-            available_grid_space,
-            known_dimensions,
-            inner_node_size,
-        ) + margin.get(axis);
+        /// Compute the item's resolved margins for size contributions. Horizontal percentage margins always resolve
+        /// to zero if the container size is indefinite as otherwise this would introduce a cyclic dependency.
+        #[inline(always)]
+        fn margins_axis_sums(&self, item: &mut GridItem) -> Size<f32> {
+            let parent_width = self.inner_node_size.width;
+            Rect {
+                left: item.margin.left.resolve_or_zero(Some(0.0)),
+                right: item.margin.right.resolve_or_zero(Some(0.0)),
+                top: item.margin.top.resolve_or_zero(parent_width),
+                bottom: item.margin.bottom.resolve_or_zero(parent_width),
+            }
+            .sum_axes()
+        }
 
-        (axis_minimum_size, min_content_size.get(axis), max_content_size.get(axis))
-    };
+        /// Retrieve the item's min content contribution from the cache or compute it using the provided parameters
+        #[inline(always)]
+        fn min_content_contribution(&mut self, item: &mut GridItem) -> f32 {
+            let known_dimensions = self.known_dimensions(item);
+            let margin_axis_sums = self.margins_axis_sums(item);
+            let contribution = item.min_content_contribution_cached(self.tree, known_dimensions, self.inner_node_size);
+            contribution.get(self.axis) + margin_axis_sums.get(self.axis)
+        }
 
-    let axis_available_grid_space = available_grid_space.get(axis);
+        /// Retrieve the item's max content contribution from the cache or compute it using the provided parameters
+        #[inline(always)]
+        fn max_content_contribution(&mut self, item: &mut GridItem) -> f32 {
+            let known_dimensions = self.known_dimensions(item);
+            let margin_axis_sums = self.margins_axis_sums(item);
+            let contribution = item.max_content_contribution_cached(self.tree, known_dimensions, self.inner_node_size);
+            contribution.get(self.axis) + margin_axis_sums.get(self.axis)
+        }
+
+        /// The minimum contribution of an item is the smallest outer size it can have.
+        /// Specifically:
+        ///   - If the item’s computed preferred size behaves as auto or depends on the size of its containing block in the relevant axis:
+        ///     Its minimum contribution is the outer size that would result from assuming the item’s used minimum size as its preferred size;
+        ///   - Else the item’s minimum contribution is its min-content contribution.
+        /// Because the minimum contribution often depends on the size of the item’s content, it is considered a type of intrinsic size contribution.
+        #[inline(always)]
+        fn minimum_contribution(&mut self, item: &mut GridItem, axis_tracks: &[GridTrack]) -> f32 {
+            let known_dimensions = self.known_dimensions(item);
+            let margin_axis_sums = self.margins_axis_sums(item);
+            let contribution = item.minimum_contribution_cached(
+                self.tree,
+                self.axis,
+                axis_tracks,
+                known_dimensions,
+                self.inner_node_size,
+            );
+            contribution + margin_axis_sums.get(self.axis)
+        }
+    }
+
+    let axis_inner_node_size = inner_node_size.get(axis);
+    let mut item_sizer =
+        IntrisicSizeMeasurer { tree, other_axis_tracks, axis, inner_node_size, get_track_size_estimate };
 
     let mut batched_item_iterator = ItemBatcher::new(axis);
     while let Some((batch, is_flex)) = batched_item_iterator.next(items) {
         // 1. For intrinsic minimums:
         // First increase the base size of tracks with an intrinsic min track sizing function
-        let has_intrinsic_min_track_sizing_function = move |track: &GridTrack| {
-            track.min_track_sizing_function.definite_value(available_grid_space.get(axis)).is_none()
-        };
+        let has_intrinsic_min_track_sizing_function =
+            move |track: &GridTrack| track.min_track_sizing_function.definite_value(axis_inner_node_size).is_none();
         for item in batch.iter_mut() {
-            let (axis_minimum_size, axis_min_content_size, _) = compute_item_sizes(item, axis_tracks);
-
             // ...by distributing extra space as needed to accommodate these items’ minimum contributions.
             // If the grid container is being sized under a min- or max-content constraint, use the items’ limited min-content contributions
             // in place of their minimum contributions here.
             let space = match axis_available_grid_space {
                 AvailableSpace::MinContent | AvailableSpace::MaxContent => {
-                    let limit = item.spanned_fixed_track_limit(axis, axis_tracks, inner_node_size.get(axis).into());
+                    let axis_minimum_size = item_sizer.minimum_contribution(item, axis_tracks);
+                    let axis_min_content_size = item_sizer.min_content_contribution(item);
+                    let limit = item.spanned_fixed_track_limit(axis, axis_tracks, axis_inner_node_size);
                     axis_min_content_size.maybe_min(limit).max(axis_minimum_size)
                 }
-                AvailableSpace::Definite(_) => axis_minimum_size,
+                AvailableSpace::Definite(_) => item_sizer.minimum_contribution(item, axis_tracks),
             };
             let tracks = &mut axis_tracks[item.track_range_excluding_lines(axis)];
             if space > 0.0 {
@@ -416,7 +495,7 @@ fn resolve_intrinsic_track_sizes(
                     tracks,
                     has_intrinsic_min_track_sizing_function,
                     IntrinsicContributionType::Minimum,
-                    available_grid_space.get(axis),
+                    axis_inner_node_size,
                 );
             }
         }
@@ -430,9 +509,8 @@ fn resolve_intrinsic_track_sizes(
             matches!(track.min_track_sizing_function, MinContent | MaxContent)
         };
         for item in batch.iter_mut() {
-            let (_, axis_min_content_size, _) = compute_item_sizes(item, axis_tracks);
+            let space = item_sizer.min_content_contribution(item);
             let tracks = &mut axis_tracks[item.track_range_excluding_lines(axis)];
-            let space = axis_min_content_size;
             if space > 0.0 {
                 distribute_item_space_to_base_size(
                     is_flex,
@@ -440,7 +518,7 @@ fn resolve_intrinsic_track_sizes(
                     tracks,
                     has_min_or_max_content_min_track_sizing_function,
                     IntrinsicContributionType::Minimum,
-                    available_grid_space.get(axis),
+                    axis_inner_node_size,
                 );
             }
         }
@@ -457,8 +535,8 @@ fn resolve_intrinsic_track_sizes(
                 matches!(track.min_track_sizing_function, Auto | MaxContent)
             };
             for item in batch.iter_mut() {
-                let (_, _, axis_max_content_size) = compute_item_sizes(item, axis_tracks);
-                let limit = item.spanned_fixed_track_limit(axis, axis_tracks, inner_node_size.get(axis).into());
+                let axis_max_content_size = item_sizer.max_content_contribution(item);
+                let limit = item.spanned_fixed_track_limit(axis, axis_tracks, axis_inner_node_size);
                 let space = axis_max_content_size.maybe_min(limit);
                 let tracks = &mut axis_tracks[item.track_range_excluding_lines(axis)];
                 if space > 0.0 {
@@ -468,7 +546,7 @@ fn resolve_intrinsic_track_sizes(
                         tracks,
                         has_auto_or_max_content_min_track_sizing_function,
                         IntrinsicContributionType::Maximum,
-                        available_grid_space.get(axis),
+                        axis_inner_node_size,
                     );
                 }
             }
@@ -480,7 +558,7 @@ fn resolve_intrinsic_track_sizes(
         let has_max_content_min_track_sizing_function =
             move |track: &GridTrack| matches!(track.min_track_sizing_function, MinTrackSizingFunction::MaxContent);
         for item in batch.iter_mut() {
-            let (_, _, axis_max_content_size) = compute_item_sizes(item, axis_tracks);
+            let axis_max_content_size = item_sizer.max_content_contribution(item);
             let space = axis_max_content_size;
             let tracks = &mut axis_tracks[item.track_range_excluding_lines(axis)];
             if space > 0.0 {
@@ -490,7 +568,7 @@ fn resolve_intrinsic_track_sizes(
                     tracks,
                     has_max_content_min_track_sizing_function,
                     IntrinsicContributionType::Maximum,
-                    available_grid_space.get(axis),
+                    axis_inner_node_size,
                 );
             }
         }
@@ -508,11 +586,10 @@ fn resolve_intrinsic_track_sizes(
         if !is_flex {
             // 5. For intrinsic maximums: Next increase the growth limit of tracks with an intrinsic max track sizing function by
             // distributing extra space as needed to account for these items' min-content contributions.
-            let has_intrinsic_max_track_sizing_function = move |track: &GridTrack| {
-                track.max_track_sizing_function.definite_value(available_grid_space.get(axis)).is_none()
-            };
+            let has_intrinsic_max_track_sizing_function =
+                move |track: &GridTrack| track.max_track_sizing_function.definite_value(axis_inner_node_size).is_none();
             for item in batch.iter_mut() {
-                let (_, axis_min_content_size, _) = compute_item_sizes(item, axis_tracks);
+                let axis_min_content_size = item_sizer.min_content_contribution(item);
                 let space = axis_min_content_size;
                 let tracks = &mut axis_tracks[item.track_range_excluding_lines(axis)];
                 if space > 0.0 {
@@ -533,7 +610,7 @@ fn resolve_intrinsic_track_sizes(
             let has_max_content_max_track_sizing_function =
                 |track: &GridTrack| track.max_track_sizing_function.is_max_content_alike();
             for item in batch.iter_mut() {
-                let (_, _, axis_max_content_size) = compute_item_sizes(item, axis_tracks);
+                let axis_max_content_size = item_sizer.max_content_contribution(item);
                 let space = axis_max_content_size;
                 let tracks = &mut axis_tracks[item.track_range_excluding_lines(axis)];
                 if space > 0.0 {
@@ -561,13 +638,14 @@ fn resolve_intrinsic_track_sizes(
 
 /// 11.5.1. Distributing Extra Space Across Spanned Tracks
 /// https://www.w3.org/TR/css-grid-1/#extra-space
+#[inline(always)]
 fn distribute_item_space_to_base_size(
     is_flex: bool,
     space: f32,
     tracks: &mut [GridTrack],
     track_is_affected: impl Fn(&GridTrack) -> bool,
     intrinsic_contribution_type: IntrinsicContributionType,
-    axis_available_grid_space: AvailableSpace,
+    axis_inner_node_size: Option<f32>,
 ) {
     if is_flex {
         let filter = |track: &GridTrack| track.is_flexible() && track_is_affected(track);
@@ -576,7 +654,7 @@ fn distribute_item_space_to_base_size(
             tracks,
             filter,
             intrinsic_contribution_type,
-            axis_available_grid_space,
+            axis_inner_node_size,
         )
     } else {
         distribute_item_space_to_base_size_inner(
@@ -584,7 +662,7 @@ fn distribute_item_space_to_base_size(
             tracks,
             track_is_affected,
             intrinsic_contribution_type,
-            axis_available_grid_space,
+            axis_inner_node_size,
         )
     }
 
@@ -595,7 +673,7 @@ fn distribute_item_space_to_base_size(
         tracks: &mut [GridTrack],
         track_is_affected: impl Fn(&GridTrack) -> bool,
         intrinsic_contribution_type: IntrinsicContributionType,
-        axis_available_grid_space: AvailableSpace,
+        axis_inner_node_size: Option<f32>,
     ) {
         // Skip this distribution if there is either
         //   - no space to distribute
@@ -611,9 +689,8 @@ fn distribute_item_space_to_base_size(
         // Define fit_content_limited_growth_limit function. This is passed to the distribute_space_up_to_limits
         // helper function, and is used to compute the limit to distribute up to for each track.
         // Wrapping the method on GridTrack is necessary in order to resolve percentage fit-content arguments.
-        let axis_available_grid_space_opt = axis_available_grid_space.into_option();
         let fit_content_limited_growth_limit =
-            move |track: &GridTrack| track.fit_content_limited_growth_limit(axis_available_grid_space_opt);
+            move |track: &GridTrack| track.fit_content_limited_growth_limit(axis_inner_node_size);
 
         // 1. Find the space to distribute
         let track_sizes: f32 = tracks.iter().map(|track| track.base_size).sum();
@@ -749,19 +826,23 @@ fn distribute_item_space_to_growth_limit(
 
 /// 11.6 Maximise Tracks
 /// Distributes free space (if any) to tracks with FINITE growth limits, up to their limits.
-fn maximise_tracks(axis: AbstractAxis, axis_tracks: &mut [GridTrack], available_grid_space: Size<AvailableSpace>) {
+#[inline(always)]
+fn maximise_tracks(
+    axis_tracks: &mut [GridTrack],
+    axis_inner_node_size: Option<f32>,
+    axis_available_grid_space: AvailableSpace,
+) {
     let used_space: f32 = axis_tracks.iter().map(|track| track.base_size).sum();
-    let free_space = available_grid_space.get(axis).compute_free_space(used_space);
+    let free_space = axis_available_grid_space.compute_free_space(used_space);
     if free_space == f32::INFINITY {
         axis_tracks.iter_mut().for_each(|track| track.base_size = track.growth_limit);
     } else if free_space > 0.0 {
-        let axis_available_grid_space = available_grid_space.get(axis).into_option();
         distribute_space_up_to_limits(
             free_space,
             axis_tracks,
             |_| true,
             |track| track.base_size,
-            move |track: &GridTrack| track.fit_content_limited_growth_limit(axis_available_grid_space),
+            move |track: &GridTrack| track.fit_content_limited_growth_limit(axis_inner_node_size),
         );
         for track in axis_tracks.iter_mut() {
             track.base_size += track.item_incurred_increase;
@@ -773,6 +854,7 @@ fn maximise_tracks(axis: AbstractAxis, axis_tracks: &mut [GridTrack], available_
 /// 11.7. Expand Flexible Tracks
 /// This step sizes flexible tracks using the largest value it can assign to an fr without exceeding the available space.
 #[allow(clippy::too_many_arguments)]
+#[inline(always)]
 fn expand_flexible_tracks(
     tree: &mut impl LayoutTree,
     axis: AbstractAxis,
@@ -873,6 +955,7 @@ fn expand_flexible_tracks(
 /// 11.7.1. Find the Size of an fr
 /// This algorithm finds the largest size that an fr unit can be without exceeding the target size.
 /// It must be called with a set of grid tracks and some quantity of space to fill.
+#[inline(always)]
 fn find_size_of_fr(tracks: &[GridTrack], space_to_fill: f32) -> f32 {
     // Handle the trivial case where there is no space to fill
     // Do not remove as otherwise the loop below will loop infinitely
@@ -929,11 +1012,11 @@ fn find_size_of_fr(tracks: &[GridTrack], space_to_fill: f32) -> f32 {
 
 /// 11.8. Stretch auto Tracks
 /// This step expands tracks that have an auto max track sizing function by dividing any remaining positive, definite free space equally amongst them.
+#[inline(always)]
 fn stretch_auto_tracks(
     axis: AbstractAxis,
     axis_tracks: &mut [GridTrack],
-    container_style: &Style,
-    available_space: Size<AvailableSpace>,
+    axis_min_size: Option<f32>,
     available_grid_space: Size<AvailableSpace>,
 ) {
     let num_auto_tracks =
@@ -946,7 +1029,7 @@ fn stretch_auto_tracks(
         let free_space = if available_grid_space.get(axis).is_definite() {
             available_grid_space.get(axis).compute_free_space(used_space)
         } else {
-            match container_style.min_size.maybe_resolve(available_space.into_options()).get(axis) {
+            match axis_min_size {
                 Some(size) => size - used_space,
                 None => 0.0,
             }
@@ -963,6 +1046,7 @@ fn stretch_auto_tracks(
 
 /// Helper function for distributing space to tracks evenly
 /// Used by both distribute_item_space_to_base_size and maximise_tracks steps
+#[inline(always)]
 fn distribute_space_up_to_limits(
     space_to_distribute: f32,
     tracks: &mut [GridTrack],
diff --git a/src/compute/grid/types/grid_item.rs b/src/compute/grid/types/grid_item.rs
index 36f0d5776..f42bb5ada 100644
--- a/src/compute/grid/types/grid_item.rs
+++ b/src/compute/grid/types/grid_item.rs
@@ -8,7 +8,7 @@ use crate::layout::SizingMode;
 use crate::node::Node;
 use crate::prelude::LayoutTree;
 use crate::resolve::MaybeResolve;
-use crate::style::{AvailableSpace, LengthPercentageAuto, MaxTrackSizingFunction, MinTrackSizingFunction, Style};
+use crate::style::{LengthPercentageAuto, MaxTrackSizingFunction, MinTrackSizingFunction, Style};
 use crate::style_helpers::*;
 use core::ops::Range;
 
@@ -125,15 +125,15 @@ impl GridItem {
     }
 
     /// Compute the known_dimensions to be passed to the child sizing functions
-    /// These are estimates based on either the max track sizing function on the provisional base size in the opposite
+    /// These are estimates based on either the max track sizing function or the provisional base size in the opposite
     /// axis to the one currently being sized.
     /// https://www.w3.org/TR/css-grid-1/#algo-overview
     pub fn known_dimensions_cached(
         &mut self,
         axis: AbstractAxis,
         other_axis_tracks: &[GridTrack],
-        other_axis_available_space: AvailableSpace,
-        get_track_size_estimate: impl Fn(&GridTrack, AvailableSpace) -> Option<f32>,
+        other_axis_available_space: Option<f32>,
+        get_track_size_estimate: impl Fn(&GridTrack, Option<f32>) -> Option<f32>,
     ) -> Size<Option<f32>> {
         self.known_dimensions_cache.unwrap_or_else(|| {
             let item_other_axis_size: Option<f32> = {
@@ -163,16 +163,16 @@ impl GridItem {
         &mut self,
         axis: AbstractAxis,
         axis_tracks: &[GridTrack],
-        axis_available_space: AvailableSpace,
+        axis_parent_size: Option<f32>,
     ) -> Option<f32> {
         let spanned_tracks = &axis_tracks[self.track_range_excluding_lines(axis)];
         let tracks_all_fixed = spanned_tracks
             .iter()
-            .all(|track| track.max_track_sizing_function.definite_limit(axis_available_space).is_some());
+            .all(|track| track.max_track_sizing_function.definite_limit(axis_parent_size).is_some());
         if tracks_all_fixed {
             let limit: f32 = spanned_tracks
                 .iter()
-                .map(|track| track.max_track_sizing_function.definite_limit(axis_available_space).unwrap())
+                .map(|track| track.max_track_sizing_function.definite_limit(axis_parent_size).unwrap())
                 .sum();
             Some(limit)
         } else {
@@ -233,23 +233,18 @@ impl GridItem {
         tree: &mut impl LayoutTree,
         axis: AbstractAxis,
         axis_tracks: &[GridTrack],
-        available_space: Size<AvailableSpace>,
         known_dimensions: Size<Option<f32>>,
         inner_node_size: Size<Option<f32>>,
     ) -> f32 {
         self.minimum_contribution_cache.unwrap_or_else(|| {
             let style = tree.style(self.node);
-            style
+            let contribution = style
                 .size
-                .maybe_resolve(available_space.into_options())
+                .maybe_resolve(inner_node_size)
                 .maybe_apply_aspect_ratio(style.aspect_ratio)
                 .get(axis)
                 .or_else(|| {
-                    style
-                        .min_size
-                        .maybe_resolve(available_space.into_options())
-                        .maybe_apply_aspect_ratio(style.aspect_ratio)
-                        .get(axis)
+                    style.min_size.maybe_resolve(inner_node_size).maybe_apply_aspect_ratio(style.aspect_ratio).get(axis)
                 })
                 .unwrap_or_else(|| {
                     // Automatic minimum size. See https://www.w3.org/TR/css-grid-1/#min-size-auto
@@ -282,7 +277,9 @@ impl GridItem {
                     } else {
                         0.0
                     }
-                })
+                });
+            self.minimum_contribution_cache = Some(contribution);
+            contribution
         })
     }
 
diff --git a/src/compute/mod.rs b/src/compute/mod.rs
index 6008d8a3a..5bf1dc6dd 100644
--- a/src/compute/mod.rs
+++ b/src/compute/mod.rs
@@ -286,16 +286,21 @@ fn compute_cache_slot(known_dimensions: Size<Option<f32>>, available_space: Size
         return 0;
     }
 
-    // Slot 1: 1 of 2 known_dimensions were set and the other dimension was either a MaxContent or Definite available space constraint
-    // Slot 2: 1 of 2 known_dimensions were set and the other dimension was a MinContent constraint
-    if has_known_width || has_known_height {
-        let other_dim_available_space = if has_known_width { available_space.height } else { available_space.width };
-        return 1 + (other_dim_available_space == AvailableSpace::MinContent) as usize;
+    // Slot 1: width but not height known_dimension was set and the other dimension was either a MaxContent or Definite available space constraint
+    // Slot 2: width but not height known_dimension was set and the other dimension was a MinContent constraint
+    if has_known_width && !has_known_height {
+        return 1 + (available_space.height == AvailableSpace::MinContent) as usize;
     }
 
-    // Slot 3: Neither known_dimensions were set and we are sizing under a MaxContent or Definite available space constraint
-    // Slot 4: Neither known_dimensions were set and we are sizing under a MinContent constraint
-    3 + (available_space.width == AvailableSpace::MinContent) as usize
+    // Slot 3: height but not width known_dimension was set and the other dimension was either a MaxContent or Definite available space constraint
+    // Slot 4: height but not width known_dimension was set and the other dimension was a MinContent constraint
+    if !has_known_width && has_known_height {
+        return 3 + (available_space.width == AvailableSpace::MinContent) as usize;
+    }
+
+    // Slot 5: Neither known_dimensions were set and we are sizing under a MaxContent or Definite available space constraint
+    // Slot 6: Neither known_dimensions were set and we are sizing under a MinContent constraint
+    5 + (available_space.width == AvailableSpace::MinContent) as usize
 }
 
 /// Try to get the computation result from the cache.
diff --git a/src/data.rs b/src/data.rs
index f09d3cb1b..03a06c673 100644
--- a/src/data.rs
+++ b/src/data.rs
@@ -6,7 +6,7 @@ use crate::layout::{Cache, Layout};
 use crate::style::Style;
 
 /// The number of cache entries for each node in the tree
-pub(crate) const CACHE_SIZE: usize = 5;
+pub(crate) const CACHE_SIZE: usize = 7;
 
 /// Layout information for a given [`Node`](crate::node::Node)
 ///
diff --git a/src/layout.rs b/src/layout.rs
index 13fabe78e..522cce1bd 100644
--- a/src/layout.rs
+++ b/src/layout.rs
@@ -36,6 +36,12 @@ pub struct SizeAndBaselines {
     pub first_baselines: Point<Option<f32>>,
 }
 
+impl From<Size<f32>> for SizeAndBaselines {
+    fn from(size: Size<f32>) -> Self {
+        Self { size, first_baselines: Point::NONE }
+    }
+}
+
 /// The final result of a layout algorithm for a single [`Node`](crate::node::Node).
 #[derive(Debug, Copy, Clone)]
 pub struct Layout {
diff --git a/src/style/grid.rs b/src/style/grid.rs
index cf89e057e..fa6364698 100644
--- a/src/style/grid.rs
+++ b/src/style/grid.rs
@@ -3,7 +3,6 @@ use super::{AlignContent, LengthPercentage, Style};
 use crate::axis::{AbsoluteAxis, AbstractAxis};
 use crate::compute::grid::{GridCoordinate, GridLine, OriginZeroLine};
 use crate::geometry::{Line, MinMax};
-use crate::style::AvailableSpace;
 use crate::style_helpers::*;
 use crate::sys::GridTrackVec;
 use core::cmp::{max, min};
@@ -312,14 +311,11 @@ impl MaxTrackSizingFunction {
     /// the passed available_space and returns if this results in a concrete value (which it
     /// will if the available_space is `Some`). Otherwise returns None.
     #[inline(always)]
-    pub fn definite_value(self, available_space: AvailableSpace) -> Option<f32> {
+    pub fn definite_value(self, parent_size: Option<f32>) -> Option<f32> {
         use MaxTrackSizingFunction::{Auto, *};
         match self {
             Fixed(LengthPercentage::Points(size)) => Some(size),
-            Fixed(LengthPercentage::Percent(fraction)) => match available_space {
-                AvailableSpace::Definite(available_size) => Some(fraction * available_size),
-                _ => None,
-            },
+            Fixed(LengthPercentage::Percent(fraction)) => parent_size.map(|size| fraction * size),
             MinContent | MaxContent | FitContent(_) | Auto | Flex(_) => None,
         }
     }
@@ -330,15 +326,13 @@ impl MaxTrackSizingFunction {
     ///     - A fit-content sizing function with fixed argument
     ///     - A fit-content sizing function with percentage argument (with definite available space)
     /// All other kinds of track sizing function return None.
-    pub fn definite_limit(self, available_space: AvailableSpace) -> Option<f32> {
+    #[inline(always)]
+    pub fn definite_limit(self, parent_size: Option<f32>) -> Option<f32> {
         use MaxTrackSizingFunction::FitContent;
         match self {
             FitContent(LengthPercentage::Points(size)) => Some(size),
-            FitContent(LengthPercentage::Percent(fraction)) => match available_space {
-                AvailableSpace::Definite(available_size) => Some(fraction * available_size),
-                _ => None,
-            },
-            _ => self.definite_value(available_space),
+            FitContent(LengthPercentage::Percent(fraction)) => parent_size.map(|size| fraction * size),
+            _ => self.definite_value(parent_size),
         }
     }
 
@@ -398,14 +392,11 @@ impl MinTrackSizingFunction {
     /// the passed available_space and returns if this results in a concrete value (which it
     /// will if the available_space is `Some`). Otherwise returns `None`.
     #[inline(always)]
-    pub fn definite_value(self, available_space: AvailableSpace) -> Option<f32> {
+    pub fn definite_value(self, parent_size: Option<f32>) -> Option<f32> {
         use MinTrackSizingFunction::{Auto, *};
         match self {
             Fixed(LengthPercentage::Points(size)) => Some(size),
-            Fixed(LengthPercentage::Percent(fraction)) => match available_space {
-                AvailableSpace::Definite(available_size) => Some(fraction * available_size),
-                _ => None,
-            },
+            Fixed(LengthPercentage::Percent(fraction)) => parent_size.map(|size| fraction * size),
             MinContent | MaxContent | Auto => None,
         }
     }
diff --git a/tests/caching.rs b/tests/caching.rs
new file mode 100644
index 000000000..37eff95e2
--- /dev/null
+++ b/tests/caching.rs
@@ -0,0 +1,64 @@
+#[cfg(test)]
+mod caching {
+    use taffy::node::MeasureFunc;
+    use taffy::prelude::*;
+
+    #[test]
+    fn measure_count_flexbox() {
+        use std::sync::atomic::{AtomicU32, Ordering};
+
+        let mut taffy = Taffy::new();
+        static NUM_MEASURES: AtomicU32 = AtomicU32::new(0);
+
+        let grandchild = taffy
+            .new_leaf_with_measure(
+                Style { ..Default::default() },
+                MeasureFunc::Raw(|known_dimensions, _available_space| {
+                    NUM_MEASURES.fetch_add(1, Ordering::SeqCst);
+                    Size {
+                        width: known_dimensions.width.unwrap_or(50.0),
+                        height: known_dimensions.height.unwrap_or(50.0),
+                    }
+                }),
+            )
+            .unwrap();
+
+        let child = taffy.new_with_children(Style::DEFAULT, &[grandchild]).unwrap();
+
+        let node = taffy.new_with_children(Style::DEFAULT, &[child]).unwrap();
+        taffy.compute_layout(node, Size::MAX_CONTENT).unwrap();
+
+        assert_eq!(NUM_MEASURES.load(Ordering::SeqCst), 2);
+    }
+
+    #[test]
+    #[cfg(feature = "grid")]
+    fn measure_count_grid() {
+        use std::sync::atomic::{AtomicU32, Ordering};
+
+        let style = || Style { display: Display::Grid, ..Default::default() };
+
+        let mut taffy = Taffy::new();
+        static NUM_MEASURES: AtomicU32 = AtomicU32::new(0);
+
+        let grandchild = taffy
+            .new_leaf_with_measure(
+                style(),
+                MeasureFunc::Raw(|known_dimensions, _available_space| {
+                    NUM_MEASURES.fetch_add(1, Ordering::SeqCst);
+                    Size {
+                        width: known_dimensions.width.unwrap_or(50.0),
+                        height: known_dimensions.height.unwrap_or(50.0),
+                    }
+                }),
+            )
+            .unwrap();
+
+        let child = taffy.new_with_children(style(), &[grandchild]).unwrap();
+
+        let node = taffy.new_with_children(style(), &[child]).unwrap();
+
+        taffy.compute_layout(node, Size::MAX_CONTENT).unwrap();
+        assert_eq!(NUM_MEASURES.load(Ordering::SeqCst), 2);
+    }
+}
diff --git a/tests/measure.rs b/tests/measure.rs
index 25b9c0f96..3d9cfce44 100644
--- a/tests/measure.rs
+++ b/tests/measure.rs
@@ -446,32 +446,4 @@ mod measure {
         assert_eq!(taffy.layout(child).unwrap().size.width, 100.0);
         assert_eq!(taffy.layout(child).unwrap().size.height, 100.0);
     }
-
-    #[test]
-    fn only_measure_once() {
-        use std::sync::atomic::{AtomicU32, Ordering};
-
-        let mut taffy = Taffy::new();
-        static NUM_MEASURES: AtomicU32 = AtomicU32::new(0);
-
-        let grandchild = taffy
-            .new_leaf_with_measure(
-                Style { ..Default::default() },
-                MeasureFunc::Raw(|known_dimensions, _available_space| {
-                    NUM_MEASURES.fetch_add(1, Ordering::SeqCst);
-                    Size {
-                        width: known_dimensions.width.unwrap_or(50.0),
-                        height: known_dimensions.height.unwrap_or(50.0),
-                    }
-                }),
-            )
-            .unwrap();
-
-        let child = taffy.new_with_children(Style { ..Default::default() }, &[grandchild]).unwrap();
-
-        let node = taffy.new_with_children(Style { ..Default::default() }, &[child]).unwrap();
-        taffy.compute_layout(node, Size::MAX_CONTENT).unwrap();
-
-        assert_eq!(NUM_MEASURES.load(Ordering::SeqCst), 2);
-    }
 }