Skip to content

Commit

Permalink
feat: overhaul renderer structure
Browse files Browse the repository at this point in the history
Makes the renderer structure consistent.

- Introduces `draw_shape` which renderer a widget will employ, rather
  than being infered by its component, leading to possible
  dual-rendering and overlap.
- Makes the renderer own the resources, and move the Wgpu resources out
  of the ECS
- Improves object uniform updating
- Composable renderer system
  • Loading branch information
ten3roberts committed Jan 13, 2024
1 parent 50a1c8d commit 855d4a0
Show file tree
Hide file tree
Showing 18 changed files with 397 additions and 217 deletions.
2 changes: 1 addition & 1 deletion .env
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
RUST_LOG="info"
RUST_LOG="violet::wgpu::systems=debug,info"
RUST_BACKTRACE=1
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ parking_lot = "0.12"
slotmap = "1.0"
anyhow = "1.0"
once_cell = "1.18"
slab = "0.4"

bytemuck = { version = "1.13", features = ["derive"] }
winit = "0.28"
Expand Down
103 changes: 61 additions & 42 deletions examples/basic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use futures_signals::signal::Mutable;
use glam::{vec2, Vec2};
use image::{DynamicImage, ImageError};
use itertools::Itertools;
use palette::{Hsla, IntoColor, Srgba};
use palette::{Hsla, Hsva, IntoColor, Srgba};
use std::{path::PathBuf, time::Duration};
use tracing_subscriber::{
prelude::__tracing_subscriber_SubscriberExt, registry, util::SubscriberInitExt, EnvFilter,
Expand All @@ -13,14 +13,15 @@ use tracing_tree::HierarchicalLayer;
use violet::{
assets::AssetKey,
components::{
self, color, filled_rect, font_family, font_size, layout, size, text, Edges, FontFamily,
self, color, draw_shape, font_family, font_size, layout, shape_rectangle, size, text,
Edges, FontFamily,
},
layout::{CrossAlign, Direction, FlowLayout, Layout, StackLayout},
shapes::FilledRect,
style::StyleExt,
time::interval,
unit::Unit,
wgpu::font::FontFromFile,
widget::WidgetExt,
widgets::{Button, Rectangle},
App, Scope, StreamEffect, Widget, WidgetCollection,
};
Expand Down Expand Up @@ -153,13 +154,10 @@ impl<P: Into<PathBuf>> Widget for Image<P> {
})
.unwrap();

scope.set(name(), "Image".into()).set(
filled_rect(),
FilledRect {
color: Srgba::new(1.0, 1.0, 1.0, 1.0),
fill_image: Some(image),
},
);
scope
.set(name(), "Image".into())
.set(draw_shape(shape_rectangle()), ())
.set(components::image(), image);
}
}

Expand Down Expand Up @@ -311,16 +309,12 @@ impl<W: WidgetCollection> List<W> {

impl<W: WidgetCollection> Widget for List<W> {
fn mount(self, scope: &mut Scope<'_>) {
if let Some(background_color) = self.background_color {
scope
.set(draw_shape(shape_rectangle()), ())
.set(color(), background_color);
}
scope
.set_opt(
filled_rect(),
self.background_color.map(|bg| FilledRect {
// color: Hsla::new(180.0, 0.048, 0.243, 1.0).into_color(),
// color: Hsla::new(190.0, 0.048, 0.143, 1.0).into_color(),
color: bg,
fill_image: None,
}),
)
.set(layout(), Layout::Flow(self.layout))
.set_opt(color(), self.background_color);

Expand All @@ -336,12 +330,25 @@ impl Widget for MainApp {

Stack::new(
List::new((
List::new(
(0..4)
.map(|i| {
let size = vec2(50.0, 50.0);

Rectangle::new(Hsva::new(i as f32 * 30.0, 1.0, 1.0, 1.0).into_color())
.with_min_size(Unit::px(size))
.with_size(Unit::px(size * vec2(2.0, 1.0)))
})
.collect_vec(),
),
LayoutTest {
contain_margins: true,
},
}
.with_name("LayoutText 3"),
LayoutTest {
contain_margins: false,
},
}
.with_name("LayoutText 2"),
List::new(
(1..=4)
.map(|i| {
Expand All @@ -353,29 +360,23 @@ impl Widget for MainApp {
.with_margin(MARGIN)
})
.collect_vec(),
),
Stack {
items: (
Text::new(
"The quick brown fox 🦊 jumps over the lazy dog 🐕 fi fO t f-t ===",
)
)
.with_name("Images"),
Stack::new((Text::new(
"The quick brown fox 🦊 jumps over the lazy dog 🐕 fi fO t f-t ===",
)
.with_font("Inter/static/Inter-Bold.ttf")
.with_font_size(32.0)
.with_margin(MARGIN),))
.with_background(EERIE_BLACK),
Stack::new((
Text::new(" -> <==========> ======= != <$> ~~>")
.with_font("Inter/static/Inter-Bold.ttf")
.with_font_size(32.0)
.with_margin(MARGIN),
Rectangle::new(EERIE_BLACK)
.with_size(Unit::rel(vec2(1.0, 0.0)) + Unit::px(vec2(0.0, 50.0))),
),
},
Stack {
items: (
Text::new(" -> <==========> ======= != <$> ~~>")
.with_font("Inter/static/Inter-Bold.ttf")
.with_font_size(32.0)
.with_margin(MARGIN),
Rectangle::new(EERIE_BLACK)
.with_size(Unit::rel(vec2(1.0, 0.0)) + Unit::px(vec2(0.0, 50.0))),
),
},
Rectangle::new(EERIE_BLACK)
.with_size(Unit::rel(vec2(1.0, 0.0)) + Unit::px(vec2(0.0, 50.0))),
)),
))
.contain_margins(true)
.with_direction(Direction::Vertical)
Expand Down Expand Up @@ -426,11 +427,20 @@ impl Widget for MainApp {

struct Stack<W> {
items: W,
background: Option<Srgba>,
}

impl<W> Stack<W> {
fn new(items: W) -> Self {
Self { items }
Self {
items,
background: None,
}
}

fn with_background(mut self, background: Srgba) -> Self {
self.background = Some(background);
self
}
}

Expand All @@ -441,6 +451,12 @@ where
fn mount(self, scope: &mut Scope<'_>) {
self.items.attach(scope);

if let Some(background) = self.background {
scope
.set(draw_shape(shape_rectangle()), ())
.set(color(), background);
}

scope.set(
layout(),
Layout::Stack(StackLayout {
Expand All @@ -455,6 +471,9 @@ struct StackTest {}

impl Widget for StackTest {
fn mount(self, scope: &mut Scope<'_>) {
// Text::new("This is an overlaid text")
// .with_color(EMERALD)
// .mount(scope)
scope.set(layout(), Layout::Stack(Default::default()));
scope.attach(Text::new("This is an overlaid text").with_color(EMERALD));

Expand Down
14 changes: 10 additions & 4 deletions src/components.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,15 @@ use std::{
fmt::{Debug, Display},
};

use flax::{component, Debuggable, Entity};
use flax::{component, Debuggable, Entity, Exclusive};
use glam::{vec2, Vec2};
use image::DynamicImage;
use palette::Srgba;

use crate::{
assets::Handle,
layout::{Layout, SizeResolver},
shapes::FilledRect,
shapes::Shape,

Check warning on line 14 in src/components.rs

View workflow job for this annotation

GitHub Actions / check

unused import: `shapes::Shape`

Check warning on line 14 in src/components.rs

View workflow job for this annotation

GitHub Actions / check

unused import: `shapes::Shape`

Check warning on line 14 in src/components.rs

View workflow job for this annotation

GitHub Actions / Clippy

unused import: `shapes::Shape`

Check warning on line 14 in src/components.rs

View workflow job for this annotation

GitHub Actions / Clippy

unused import: `shapes::Shape`

Check warning on line 14 in src/components.rs

View workflow job for this annotation

GitHub Actions / test

unused import: `shapes::Shape`

Check warning on line 14 in src/components.rs

View workflow job for this annotation

GitHub Actions / test

unused import: `shapes::Shape`

Check warning on line 14 in src/components.rs

View workflow job for this annotation

GitHub Actions / test_miri

unused import: `shapes::Shape`

Check warning on line 14 in src/components.rs

View workflow job for this annotation

GitHub Actions / test_miri

unused import: `shapes::Shape`
unit::Unit,
};

Expand Down Expand Up @@ -61,13 +63,17 @@ component! {
/// To retain consistent text wrapping between size query and the snug fitted rect the bounds
/// of the size query are stored and used instead of the snug-fitted rect which will cause a
/// different wrapping, and therefore final size.
pub text_limits: Vec2 => [ Debuggable ],
pub layout_bounds: Vec2 => [ Debuggable ],

/// The color of the widget
pub color: Srgba => [ Debuggable ],

/// The widget will be rendered as a filled rectange coverings its bounds
pub filled_rect: FilledRect => [ Debuggable ],
pub image: Handle<DynamicImage> => [ Debuggable ],

pub draw_shape(variant): () => [ Debuggable, Exclusive ],
pub shape_rectangle,
pub shape_text,

pub font_family: FontFamily => [ Debuggable ],

Expand Down
33 changes: 31 additions & 2 deletions src/layout/flow.rs
Original file line number Diff line number Diff line change
Expand Up @@ -206,11 +206,21 @@ impl FlowLayout {

// If everything was squished as much as possible
let minimum_inner_size = row.min.size().dot(axis);

if minimum_inner_size > limits.max_size.dot(axis) {
tracing::error!(
"minimum inner size exceeded max size: {:?} > {:?}",
minimum_inner_size,
limits.max_size
);
}

// If everything could take as much space as it wants
let preferred_inner_size = row.preferred.size().dot(axis);

// How much space there is left to distribute out
let distribute_size = preferred_inner_size - minimum_inner_size;
// tracing::info!(?distribute_size);

// Clipped maximum that we remap to
let target_inner_size = distribute_size
Expand Down Expand Up @@ -251,11 +261,28 @@ impl FlowLayout {
let block_preferred_size = sizing.preferred.size().dot(axis);

let remaining = block_preferred_size - block_min_size;
let ratio = remaining / distribute_size;
let ratio = if distribute_size == 0.0 {
0.0
} else {
remaining / distribute_size
};

// assert!(remaining >= 0.0, "{remaining}");
// assert!(
// (distribute_size == 0.0 && ratio.is_nan())
// || (distribute_size > 0.0 && !ratio.is_nan()),
// "{distribute_size} {ratio}"
// );

sum += ratio;

let axis_sizing = (block_min_size + (target_inner_size * ratio)) * axis;
// tracing::info!(ratio, %axis_sizing, block_min_size, target_inner_size);

assert!(
axis_sizing.dot(axis) >= block_min_size,
"{axis_sizing} {block_min_size}"
);
// tracing::info!(%axis_sizing, block_min_size, remaining, "sizing: {}", ratio);

let child_margin = if self.contain_margins {
Expand Down Expand Up @@ -292,10 +319,12 @@ impl FlowLayout {
|| block.rect.size().y > child_limits.max_size.y
{
tracing::warn!(
block_min_size,
block_preferred_size,
"child {} exceeded max size: {:?} > {:?}",
entity,
block.rect.size(),
child_limits.max_size
child_limits.max_size,
);
}

Expand Down
6 changes: 4 additions & 2 deletions src/layout/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -170,11 +170,13 @@ pub(crate) fn update_subtree(
.copied()
.unwrap_or_default();

// Flow
// Layout
if let Some((children, layout)) = entity.query(&(children(), layout())).get() {
// For a given layout use the largest size that fits within the constraints and then
// potentially shrink it down.

// let _span = tracing::info_span!("Layout", %entity).entered();

let mut block = layout.apply(
world,
children,
Expand All @@ -198,7 +200,7 @@ pub(crate) fn update_subtree(
let pos = resolve_pos(entity, content_area, size);
let rect = Rect::from_size_pos(size, pos).clip(content_area);

entity.update_dedup(components::text_limits(), limits.max_size);
entity.update_dedup(components::layout_bounds(), size);

Block { rect, margin }
}
Expand Down
2 changes: 1 addition & 1 deletion src/layout/stack.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ impl StackLayout {
content_area: Rect,
limits: LayoutLimits,
) -> Block {
let _span = tracing::info_span!("Stack::apply").entered();
// let _span = tracing::info_span!("Stack::apply").entered();

// tracing::info!(
// ?content_area,
Expand Down
21 changes: 6 additions & 15 deletions src/shapes.rs
Original file line number Diff line number Diff line change
@@ -1,20 +1,11 @@
use image::DynamicImage;

Check warning on line 1 in src/shapes.rs

View workflow job for this annotation

GitHub Actions / check

unused import: `image::DynamicImage`

Check warning on line 1 in src/shapes.rs

View workflow job for this annotation

GitHub Actions / check

unused import: `image::DynamicImage`

Check warning on line 1 in src/shapes.rs

View workflow job for this annotation

GitHub Actions / Clippy

unused import: `image::DynamicImage`

Check warning on line 1 in src/shapes.rs

View workflow job for this annotation

GitHub Actions / Clippy

unused import: `image::DynamicImage`

Check warning on line 1 in src/shapes.rs

View workflow job for this annotation

GitHub Actions / test

unused import: `image::DynamicImage`

Check warning on line 1 in src/shapes.rs

View workflow job for this annotation

GitHub Actions / test

unused import: `image::DynamicImage`

Check warning on line 1 in src/shapes.rs

View workflow job for this annotation

GitHub Actions / test_miri

unused import: `image::DynamicImage`

Check warning on line 1 in src/shapes.rs

View workflow job for this annotation

GitHub Actions / test_miri

unused import: `image::DynamicImage`
use palette::Srgba;
use palette::{IntoColor, Srgba};

Check warning on line 2 in src/shapes.rs

View workflow job for this annotation

GitHub Actions / check

unused imports: `IntoColor`, `Srgba`

Check warning on line 2 in src/shapes.rs

View workflow job for this annotation

GitHub Actions / check

unused imports: `IntoColor`, `Srgba`

Check warning on line 2 in src/shapes.rs

View workflow job for this annotation

GitHub Actions / Clippy

unused imports: `IntoColor`, `Srgba`

Check warning on line 2 in src/shapes.rs

View workflow job for this annotation

GitHub Actions / Clippy

unused imports: `IntoColor`, `Srgba`

Check warning on line 2 in src/shapes.rs

View workflow job for this annotation

GitHub Actions / test

unused imports: `IntoColor`, `Srgba`

Check warning on line 2 in src/shapes.rs

View workflow job for this annotation

GitHub Actions / test

unused imports: `IntoColor`, `Srgba`

Check warning on line 2 in src/shapes.rs

View workflow job for this annotation

GitHub Actions / test_miri

unused imports: `IntoColor`, `Srgba`

Check warning on line 2 in src/shapes.rs

View workflow job for this annotation

GitHub Actions / test_miri

unused imports: `IntoColor`, `Srgba`

use crate::assets::Handle;

Check warning on line 4 in src/shapes.rs

View workflow job for this annotation

GitHub Actions / check

unused import: `crate::assets::Handle`

Check warning on line 4 in src/shapes.rs

View workflow job for this annotation

GitHub Actions / check

unused import: `crate::assets::Handle`

Check warning on line 4 in src/shapes.rs

View workflow job for this annotation

GitHub Actions / Clippy

unused import: `crate::assets::Handle`

Check warning on line 4 in src/shapes.rs

View workflow job for this annotation

GitHub Actions / Clippy

unused import: `crate::assets::Handle`

Check warning on line 4 in src/shapes.rs

View workflow job for this annotation

GitHub Actions / test

unused import: `crate::assets::Handle`

Check warning on line 4 in src/shapes.rs

View workflow job for this annotation

GitHub Actions / test

unused import: `crate::assets::Handle`

Check warning on line 4 in src/shapes.rs

View workflow job for this annotation

GitHub Actions / test_miri

unused import: `crate::assets::Handle`

Check warning on line 4 in src/shapes.rs

View workflow job for this annotation

GitHub Actions / test_miri

unused import: `crate::assets::Handle`

/// A rectangle sized to the widget
#[derive(Clone)]
pub struct FilledRect {
pub color: Srgba,
pub fill_image: Option<Handle<DynamicImage>>,
}

impl std::fmt::Debug for FilledRect {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("FilledRect")
.field("color", &self.color)
.field("fill_image", &self.fill_image.as_ref().map(Handle::id))
.finish()
}
/// Shape to use when drawing a widget
#[derive(Debug, Clone)]
pub enum Shape {
/// The widget will be drawn as a filled rectangle
Rectangle,
}
4 changes: 2 additions & 2 deletions src/systems.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use flax::{
use glam::{Mat4, Vec2};

use crate::{
components::{self, children, local_position, rect, screen_position, text, text_limits, Rect},
components::{self, children, local_position, rect, screen_position, text, layout_bounds, Rect},
layout::{update_subtree, LayoutLimits},
wgpu::components::model_matrix,
};
Expand All @@ -17,7 +17,7 @@ pub fn hydrate_text() -> BoxedSystem {
.with_query(Query::new(entity_ids()).with(text()))
.build(|cmd: &mut CommandBuffer, mut query: QueryBorrow<_, _>| {
query.for_each(|id| {
cmd.set_missing(id, text_limits(), Vec2::ZERO);
cmd.set_missing(id, layout_bounds(), Vec2::ZERO);
})
})
.boxed()
Expand Down
Loading

0 comments on commit 855d4a0

Please sign in to comment.