Skip to content

Commit

Permalink
fix: improve performance for non-coupled size widgets
Browse files Browse the repository at this point in the history
  • Loading branch information
ten3roberts committed Mar 15, 2024
1 parent 8865ddb commit 40626c8
Show file tree
Hide file tree
Showing 19 changed files with 430 additions and 165 deletions.
5 changes: 3 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,8 @@ slab = "0.4"
tynm ="0.1"
tokio = { version = "1.0", default-features = false, features = ["macros", "rt"] }
arrayvec = "0.7"
sync_wrapper = "0.1"
sync_wrapper = "1.0"
smallvec = "1.0"

bytemuck = { version = "1.13", features = ["derive"] }
winit = "0.29"
Expand Down
69 changes: 69 additions & 0 deletions assets/shaders/border_shader.wgsl
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
struct VertexInput {
@location(0) pos: vec3<f32>,
@location(1) color: vec4<f32>,
@location(2) tex_coord: vec2<f32>,
@builtin(instance_index) instance: u32,
}

struct VertexOutput {
@builtin(position) pos: vec4<f32>,
@location(0) color: vec4<f32>,
@location(1) tex_coord: vec2<f32>,
@location(2) vertex_pos: vec3<f32>,
@location(3) scale: vec2<f32>,
}

struct Object {
world_matrix: mat4x4<f32>,
color: vec4<f32>,
}

struct Globals {
viewproj: mat4x4<f32>,
}

@group(0) @binding(0)
var<uniform> globals: Globals;

@group(1) @binding(0)
var<uniform> objects: array<Object, 32>;

@group(2) @binding(0)
var default_sampler: sampler;

@group(2) @binding(1)
var fill_image: texture_2d<f32>;

@vertex
fn vs_main(in: VertexInput) -> VertexOutput {
var out: VertexOutput;
let object = objects[in.instance];
let scale = (object.world_matrix * vec4(1.0, 1.0, 0.0, 0.0)).xy;
out.pos = globals.viewproj * object.world_matrix * vec4<f32>(in.pos, 1.0);
out.color = object.color;
out.tex_coord = in.tex_coord;
out.vertex_pos = in.pos;
out.scale = scale;

return out;
}

@fragment
fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {

let nearest_corner = vec2<f32>(select(0.0, in.scale.x, in.vertex_pos.x > 0.5), select(0.0, in.scale.y, in.vertex_pos.y > 0.5));
let to_nearest = abs(in.vertex_pos.xy * in.scale - nearest_corner);

// return vec4(length(nearest_corner - in.pos.xy) / 100.0);
let dist = max(1.0 - length(to_nearest) * 0.1, 0.0);
// return vec4(0.0, 0.0, dist, 1.0);
let border_size = 2.0;

var border = 0.0;

if to_nearest.x < border_size || to_nearest.y < border_size || to_nearest.x > in.scale.x - border_size || to_nearest.y > in.scale.y - border_size {
border = 1.0;
}

return in.color * textureSample(fill_image, default_sampler, in.tex_coord) * border;
}
54 changes: 39 additions & 15 deletions examples/row.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
use std::iter::repeat;

use itertools::Itertools;
use tracing_subscriber::{layer::SubscriberExt, registry, util::SubscriberInitExt, EnvFilter};
use tracing_tree::HierarchicalLayer;

Expand All @@ -12,10 +15,10 @@ use violet::core::{
};
use violet_core::{
style::{
colors::{JADE_400, JADE_DEFAULT, LION_DEFAULT},
spacing_medium, SizeExt,
colors::{JADE_400, JADE_DEFAULT, LION_DEFAULT, REDWOOD_100, ULTRA_VIOLET_DEFAULT},
spacing_medium, spacing_small, SizeExt,
},
widget::{column, row, Stack},
widget::{card, column, label, row, Stack},
};
use violet_wgpu::renderer::RendererConfig;

Expand All @@ -42,19 +45,28 @@ impl Widget for MainApp {
fn mount(self, scope: &mut Scope<'_>) {
Stack::new(
column((
row((
column((
Rectangle::new(JADE_DEFAULT).with_size(Unit::px2(900.0, 40.0)),
Rectangle::new(JADE_400)
.with_size(Unit::px2(900.0, 40.0))
.with_min_size(Unit::px2(400.0, 40.0)),
)),
Rectangle::new(LION_DEFAULT).with_size(Unit::px2(900.0, 40.0)),
)),
Rectangle::new(REDWOOD_DEFAULT)
.with_min_size(Unit::px2(100.0, 100.0))
.with_size(Unit::px2(0.0, 100.0) + Unit::rel2(1.0, 0.0)),
// row((
// label("This text can wrap to save horizontal space"),
// card((
// Rectangle::new(JADE_DEFAULT).with_size(Unit::px2(100.0, 40.0)),
// label("Jade"),
// )),
// label("This text can wrap to save horizontal space"),
// )),
// row((
// column((
// Rectangle::new(JADE_DEFAULT).with_size(Unit::px2(900.0, 40.0)),
// Rectangle::new(JADE_400)
// .with_size(Unit::px2(900.0, 40.0))
// .with_min_size(Unit::px2(400.0, 40.0)),
// )),
// Rectangle::new(LION_DEFAULT).with_size(Unit::px2(900.0, 40.0)),
// )),
// Rectangle::new(REDWOOD_DEFAULT)
// .with_min_size(Unit::px2(100.0, 100.0))
// .with_size(Unit::px2(0.0, 100.0) + Unit::rel2(1.0, 0.0)),
// .with_margin(spacing_medium()),
row((0..16).map(|_| Stack::new(Item)).collect_vec()),
))
// .with_padding(spacing_medium())
.contain_margins(true),
Expand All @@ -63,3 +75,15 @@ impl Widget for MainApp {
.mount(scope)
}
}

#[derive(Debug, Clone)]
struct Item;

impl Widget for Item {
fn mount(self, scope: &mut Scope<'_>) {
Rectangle::new(ULTRA_VIOLET_DEFAULT)
.with_size(Unit::px2(100.0, 100.0))
.with_margin(spacing_small())
.mount(scope)
}
}
1 change: 1 addition & 0 deletions violet-core/src/constraints.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ impl SizeResolver for FixedAreaConstraint {
SizingHints {
can_grow: BVec2::TRUE,
relative_size: BVec2::TRUE,
coupled_size: true,
},
)
}
Expand Down
23 changes: 15 additions & 8 deletions violet-core/src/layout/cache.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use flax::{component, components::child_of, Entity, FetchExt, RelationExt, World};
use glam::{BVec2, Vec2};

use super::{flow::Row, Block, Direction, LayoutLimits, Sizing};
use super::{flow::Row, Block, Direction, LayoutLimits, Sizing, SizingHints};

#[derive(Debug)]
pub struct CachedValue<T> {
Expand All @@ -10,7 +10,7 @@ pub struct CachedValue<T> {
pub value: T,
}

pub const LAYOUT_TOLERANCE: f32 = 0.1;
pub const LAYOUT_TOLERANCE: f32 = 0.01;

impl<T> CachedValue<T> {
pub(crate) fn new(limits: LayoutLimits, content_area: Vec2, value: T) -> Self {
Expand All @@ -34,7 +34,7 @@ pub struct LayoutCache {
pub(crate) query_row: Option<CachedValue<Row>>,
pub(crate) layout: Option<CachedValue<Block>>,
on_invalidated: Option<Box<dyn Fn(LayoutUpdate) + Send + Sync>>,
pub(crate) relative_size: BVec2,
pub(crate) hints: SizingHints,
}

impl LayoutCache {
Expand All @@ -44,7 +44,7 @@ impl LayoutCache {
query_row: None,
layout: None,
on_invalidated,
relative_size: BVec2::TRUE,
hints: Default::default(),
}
}

Expand All @@ -59,6 +59,7 @@ impl LayoutCache {
}

pub(crate) fn insert_query(&mut self, direction: Direction, value: CachedValue<Sizing>) {
self.hints = value.value.hints;
self.query[direction as usize] = Some(value);
if let Some(f) = self.on_invalidated.as_ref() {
f(LayoutUpdate::SizeQueryUpdate)
Expand All @@ -83,15 +84,19 @@ impl LayoutCache {
self.layout.as_ref()
}

pub(crate) fn get_query(&self, direction: Direction) -> Option<&CachedValue<Sizing>> {
pub fn get_query(&self, direction: Direction) -> Option<&CachedValue<Sizing>> {
self.query[direction as usize].as_ref()
}

pub fn hints(&self) -> SizingHints {
self.hints
}
}

/// Invalidates a widgets layout cache along with its ancestors
pub(crate) fn invalidate_widget(world: &World, id: Entity) {
let entity = world.entity(id).unwrap();
tracing::info!(%entity, "invalidating widget");
// tracing::info!(%entity, "invalidating widget");

let query = (layout_cache().as_mut(), child_of.first_relation().opt());
let mut query = entity.query(&query);
Expand All @@ -117,10 +122,11 @@ pub(crate) fn validate_cached_query(
// tracing::debug!( ?preferred_size, %cache.limits.max_size, %limits.max_size, "validate_cached_query");

let hints = &value.hints;
#[allow(clippy::nonminimal_bool)]
if hints.can_grow.x && cache.limits.max_size.x < limits.max_size.x
|| (hints.can_grow.x && cache.limits.max_size.y < limits.max_size.y)
{
tracing::info!(%hints.can_grow, ?cache.limits.max_size, %limits.max_size, "invalidated by can_grow");
// tracing::info!(%hints.can_grow, ?cache.limits.max_size, %limits.max_size, "invalidated by can_grow");
}

min_size.x >= limits.min_size.x - LAYOUT_TOLERANCE
Expand Down Expand Up @@ -149,10 +155,11 @@ pub(crate) fn validate_cached_layout(

// tracing::debug!( ?size, %cache.limits.max_size, %limits.max_size, "validate_cached_layout");

#[allow(clippy::nonminimal_bool)]
if value.can_grow.x && cache.limits.max_size.x < limits.max_size.x
|| (value.can_grow.x && cache.limits.max_size.y < limits.max_size.y)
{
tracing::info!(%value.can_grow, ?cache.limits.max_size, %limits.max_size, "invalidated layout by can_grow");
// tracing::info!(%value.can_grow, ?cache.limits.max_size, %limits.max_size, "invalidated layout by can_grow");
}

size.x >= limits.min_size.x - LAYOUT_TOLERANCE
Expand Down
56 changes: 50 additions & 6 deletions violet-core/src/layout/flow.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ use crate::{
};

use super::{
cache::LayoutCache, resolve_pos, update_subtree, Block, Direction, LayoutLimits, QueryArgs,
apply_layout, cache::LayoutCache, resolve_pos, Block, Direction, LayoutLimits, QueryArgs,
Sizing,
};

Expand Down Expand Up @@ -163,6 +163,7 @@ pub(crate) struct Row {
pub(crate) min: Rect,
pub(crate) preferred: Rect,
pub(crate) blocks: Arc<Vec<(Entity, Sizing)>>,
pub(crate) margin: Edges,
pub(crate) hints: SizingHints,
}

Expand Down Expand Up @@ -336,7 +337,7 @@ impl FlowLayout {
};

// let local_rect = widget_outer_bounds(world, &child, size);
let block = update_subtree(world, &entity, content_area.size(), child_limits);
let block = apply_layout(world, &entity, content_area.size(), child_limits);

can_grow |= block.can_grow;
tracing::debug!(?block, "updated subtree");
Expand Down Expand Up @@ -489,7 +490,7 @@ impl FlowLayout {
"min is larger than preferred",
);

return;
// return;
}

assert!(block_min_size.is_finite());
Expand Down Expand Up @@ -665,11 +666,18 @@ impl FlowLayout {
let preferred = preferred_cursor.finish();
let min = min_cursor.finish();

let margin = self.direction.to_edges(
preferred_cursor.main_margin,
preferred_cursor.cross_margin,
self.reverse,
);

let row = Row {
min,
preferred,
blocks: Arc::new(blocks),
hints,
margin,
};

cache.insert_query_row(CachedValue::new(
Expand Down Expand Up @@ -730,8 +738,44 @@ impl FlowLayout {
},
);

let sizing = self.distribute_query(world, &row, args, preferred_size);
tracing::debug!(?self.direction, ?sizing, "query");
sizing
if row.hints.coupled_size {
let sizing = self.distribute_query(world, &row, args, preferred_size);
tracing::debug!(?self.direction, ?sizing, "query");
sizing
} else {
let (axis, cross) = self.direction.as_main_and_cross(self.reverse);
let minimum_inner_size = row.min.size().dot(axis);

let preferred_size = row.preferred.size().dot(axis);
let to_distribute = (preferred_size - minimum_inner_size).max(0.0);

let can_grow = to_distribute > (args.limits.max_size.dot(axis) - minimum_inner_size);

let can_grow = if self.direction.is_horizontal() {
BVec2::new(can_grow, false)
} else {
BVec2::new(false, can_grow)
};

let to_distribute = to_distribute
.min(args.limits.max_size.dot(axis) - minimum_inner_size)
.max(0.0);

let preferred =
(minimum_inner_size + to_distribute) * axis + row.preferred.size() * cross;

let min = row.min.max_size(args.limits.min_size);
let preferred = preferred.max(preferred).max(args.limits.min_size);

Sizing {
min,
preferred: Rect::from_size(preferred),
margin: row.margin,
hints: SizingHints {
can_grow: can_grow | row.hints.can_grow,
..row.hints
},
}
}
}
}
Loading

0 comments on commit 40626c8

Please sign in to comment.