diff --git a/.env b/.env index 6cc5a94..1279fb2 100644 --- a/.env +++ b/.env @@ -1,2 +1,2 @@ -RUST_LOG="info" +RUST_LOG="violet::wgpu::systems=debug,info" RUST_BACKTRACE=1 diff --git a/Cargo.toml b/Cargo.toml index 5911c8a..c59c1e8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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" diff --git a/examples/basic.rs b/examples/basic.rs index 5c1dea0..6ebd21f 100644 --- a/examples/basic.rs +++ b/examples/basic.rs @@ -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, @@ -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, }; @@ -153,13 +154,10 @@ impl> Widget for Image

{ }) .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); } } @@ -311,16 +309,12 @@ impl List { impl Widget for List { 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); @@ -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| { @@ -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) @@ -426,11 +427,20 @@ impl Widget for MainApp { struct Stack { items: W, + background: Option, } impl Stack { fn new(items: W) -> Self { - Self { items } + Self { + items, + background: None, + } + } + + fn with_background(mut self, background: Srgba) -> Self { + self.background = Some(background); + self } } @@ -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 { @@ -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)); diff --git a/src/components.rs b/src/components.rs index fe4474a..69cdff6 100644 --- a/src/components.rs +++ b/src/components.rs @@ -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, unit::Unit, }; @@ -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 => [ Debuggable ], + + pub draw_shape(variant): () => [ Debuggable, Exclusive ], + pub shape_rectangle, + pub shape_text, pub font_family: FontFamily => [ Debuggable ], diff --git a/src/layout/flow.rs b/src/layout/flow.rs index 060d8c5..39a3b53 100644 --- a/src/layout/flow.rs +++ b/src/layout/flow.rs @@ -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 @@ -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 { @@ -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, ); } diff --git a/src/layout/mod.rs b/src/layout/mod.rs index 67d1069..8c5946c 100644 --- a/src/layout/mod.rs +++ b/src/layout/mod.rs @@ -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, @@ -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 } } diff --git a/src/layout/stack.rs b/src/layout/stack.rs index b187dce..9dc967d 100644 --- a/src/layout/stack.rs +++ b/src/layout/stack.rs @@ -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, diff --git a/src/shapes.rs b/src/shapes.rs index 428e1bd..939c2e3 100644 --- a/src/shapes.rs +++ b/src/shapes.rs @@ -1,20 +1,11 @@ use image::DynamicImage; -use palette::Srgba; +use palette::{IntoColor, Srgba}; use crate::assets::Handle; -/// A rectangle sized to the widget -#[derive(Clone)] -pub struct FilledRect { - pub color: Srgba, - pub fill_image: Option>, -} - -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, } diff --git a/src/systems.rs b/src/systems.rs index 3c2cca1..e1a7368 100644 --- a/src/systems.rs +++ b/src/systems.rs @@ -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, }; @@ -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() diff --git a/src/wgpu/components.rs b/src/wgpu/components.rs index 70fa3e5..a30cd74 100644 --- a/src/wgpu/components.rs +++ b/src/wgpu/components.rs @@ -3,11 +3,13 @@ use std::sync::Arc; use cosmic_text::{Attrs, Buffer, FontSystem, Metrics, Shaping, Style}; use flax::component; use fontdue::Font; -use parking_lot::Mutex; use crate::{ assets::Handle, - wgpu::{graphics::texture::Texture, shape_renderer::DrawCommand}, + wgpu::{ + graphics::texture::Texture, + shape_renderer::{DrawCommand, ObjectData}, + }, }; use super::mesh_buffer::MeshHandle; @@ -20,6 +22,7 @@ component! { /// Renderer specific data for drawing a shape pub(crate) draw_cmd: DrawCommand, + pub(crate) object_data: ObjectData, pub(crate) mesh_handle: Arc, diff --git a/src/wgpu/mod.rs b/src/wgpu/mod.rs index 22d9f63..a830a30 100644 --- a/src/wgpu/mod.rs +++ b/src/wgpu/mod.rs @@ -11,5 +11,5 @@ mod texture; pub mod window_renderer; pub use graphics::Gpu; -pub use shape_renderer::ShapeRenderer; +pub use shape_renderer::WidgetRenderer; pub use window_renderer::WindowRenderer; diff --git a/src/wgpu/rect_renderer.rs b/src/wgpu/rect_renderer.rs index c26b8b7..e09153c 100644 --- a/src/wgpu/rect_renderer.rs +++ b/src/wgpu/rect_renderer.rs @@ -4,44 +4,65 @@ use flax::{ entity_ids, fetch::{Modified, TransformFetch}, filter::{All, With}, - CommandBuffer, Component, EntityIds, Fetch, FetchExt, Mutable, Query, + CommandBuffer, Component, EntityIds, Fetch, FetchExt, Mutable, Opt, OptOr, Query, }; use glam::{vec2, vec3, Mat4, Quat, Vec2}; use image::{DynamicImage, ImageBuffer}; -use wgpu::{BindGroup, BindGroupLayout, SamplerDescriptor, ShaderStages, TextureFormat}; +use palette::Srgba; +use wgpu::{BindGroupLayout, SamplerDescriptor, ShaderStages, TextureFormat}; use crate::{ assets::{map::HandleMap, Handle}, - components::{filled_rect, rect, screen_position, Rect}, - shapes::FilledRect, + components::{color, draw_shape, image, rect, screen_position, shape_rectangle, Rect}, Frame, }; use super::{ - components::{draw_cmd, mesh_handle, model_matrix}, + components::{draw_cmd, mesh_handle, object_data}, graphics::{ shader::ShaderDesc, texture::Texture, BindGroupBuilder, BindGroupLayoutBuilder, Shader, Vertex, VertexDesc, }, mesh_buffer::MeshHandle, renderer::RendererContext, - shape_renderer::DrawCommand, + shape_renderer::{srgba_to_vec4, DrawCommand, ObjectData, RendererStore}, Gpu, }; #[derive(Fetch)] -struct RectQuery { +struct RectObjectQuery { rect: Component, pos: Component, - model: Mutable, + color: OptOr, Srgba>, + object_data: Mutable, } -impl RectQuery { +impl RectObjectQuery { fn new() -> Self { Self { rect: rect(), pos: screen_position(), - model: model_matrix().as_mut(), + object_data: object_data().as_mut(), + color: color().opt_or(Srgba::new(1.0, 1.0, 1.0, 1.0)), + } + } +} + +#[derive(Fetch)] +#[fetch(transforms = [Modified])] +struct RectDrawQuery { + #[fetch(ignore)] + id: EntityIds, + image: Opt>>, + shape: Component<()>, +} + +impl RectDrawQuery { + fn new() -> Self { + Self { + id: entity_ids(), + image: image().opt(), + shape: draw_shape(shape_rectangle()), } } } @@ -52,18 +73,14 @@ pub struct RectRenderer { layout: BindGroupLayout, sampler: wgpu::Sampler, - rect_query: Query<( - EntityIds, - as TransformFetch>::Output, - )>, + rect_query: Query<>::Output>, + object_query: Query, - object_query: Query, - - bind_groups: HandleMap>, + bind_groups: HandleMap, mesh: Arc, - shader: Handle, + shader: usize, } impl RectRenderer { @@ -72,6 +89,7 @@ impl RectRenderer { frame: &Frame, color_format: TextureFormat, object_bind_group_layout: &BindGroupLayout, + store: &mut RendererStore, ) -> Self { let layout = BindGroupLayoutBuilder::new("RectRenderer::layout") .bind_sampler(ShaderStages::FRAGMENT) @@ -106,7 +124,7 @@ impl RectRenderer { let mesh = Arc::new(ctx.mesh_buffer.insert(&ctx.gpu, &vertices, &indices)); - let shader = frame.assets.insert(Shader::new( + let shader = store.push_shader(Shader::new( &ctx.gpu, &ShaderDesc { label: "ShapeRenderer::shader", @@ -121,21 +139,21 @@ impl RectRenderer { white_image, layout, sampler, - rect_query: Query::new((entity_ids(), filled_rect().modified())), - object_query: Query::new(RectQuery::new()).with(filled_rect()), + rect_query: Query::new(RectDrawQuery::new().modified()), + object_query: Query::new(RectObjectQuery::new()).with(draw_shape(shape_rectangle())), bind_groups: HandleMap::new(), mesh, shader, } } - pub fn build_commands(&mut self, gpu: &Gpu, frame: &mut Frame) { + pub fn build_commands(&mut self, gpu: &Gpu, frame: &mut Frame, store: &mut RendererStore) { let mut cmd = CommandBuffer::new(); self.rect_query .borrow(&frame.world) .iter() - .for_each(|(id, rect)| { - let image = rect.fill_image.as_ref().unwrap_or(&self.white_image); + .for_each(|item| { + let image = item.image.unwrap_or(&self.white_image); let bind_group = self.bind_groups.entry(&image.clone()).or_insert_with(|| { let texture = Texture::from_image(gpu, image); @@ -145,24 +163,29 @@ impl RectRenderer { .bind_texture(&texture.view(&Default::default())) .build(gpu, &self.layout); - frame.assets.insert(bind_group) + store.push_bind_group(bind_group) }); - cmd.set(id, mesh_handle(), self.mesh.clone()).set( - id, - draw_cmd(), - DrawCommand { - bind_group: bind_group.clone(), - shader: self.shader.clone(), - index_count: 6, - vertex_offset: 0, - }, - ); + cmd // + .set(item.id, mesh_handle(), self.mesh.clone()) + .set( + item.id, + draw_cmd(), + DrawCommand { + bind_group: bind_group.clone(), + shader: self.shader, + mesh: self.mesh.clone(), + index_count: 6, + vertex_offset: 0, + }, + ); }); cmd.apply(&mut frame.world).unwrap(); } + pub fn register_objects(&mut self) {} + pub fn update(&mut self, _: &Gpu, frame: &Frame) { self.object_query .borrow(&frame.world) @@ -170,11 +193,16 @@ impl RectRenderer { .for_each(|item| { let rect = item.rect.translate(*item.pos).align_to_grid(); - *item.model = Mat4::from_scale_rotation_translation( + let model_matrix = Mat4::from_scale_rotation_translation( rect.size().extend(1.0), Quat::IDENTITY, rect.pos().extend(0.1), ); + + *item.object_data = ObjectData { + model_matrix, + color: srgba_to_vec4(*item.color), + }; }) } } diff --git a/src/wgpu/shape_renderer.rs b/src/wgpu/shape_renderer.rs index ca4474e..3b320f1 100644 --- a/src/wgpu/shape_renderer.rs +++ b/src/wgpu/shape_renderer.rs @@ -1,17 +1,31 @@ use std::sync::Arc; +use bytemuck::Zeroable; use cosmic_text::FontSystem; -use flax::{components::child_of, FetchExt, Query}; +use flax::{ + component, + components::child_of, + entity_ids, + fetch::{entity_refs, nth_relation, EntityRefs, NthRelation}, + CommandBuffer, Component, EntityRef, Fetch, FetchExt, Opt, OptOr, Query, QueryBorrow, System, +}; use glam::{vec4, Mat4, Vec4}; +use image::DynamicImage; use itertools::Itertools; use palette::Srgba; -use slotmap::new_key_type; +use slab::Slab; +use slotmap::{new_key_type, SlotMap}; use wgpu::{BindGroup, BufferUsages, RenderPass, ShaderStages, TextureFormat}; -use crate::{assets::Handle, components::color, Frame}; +use crate::{ + assets::{map::HandleMap, Handle}, + components::{color, draw_shape}, + shapes::Shape, + Frame, +}; use super::{ - components::{draw_cmd, mesh_handle, model_matrix}, + components::{draw_cmd, mesh_handle, model_matrix, object_data}, graphics::{BindGroupBuilder, BindGroupLayoutBuilder, Mesh, Shader, TypedBuffer}, mesh_buffer::MeshHandle, rect_renderer::RectRenderer, @@ -27,9 +41,11 @@ new_key_type! { /// Specifies what to use when drawing a single entity #[derive(Debug, Clone, PartialEq, Eq)] pub(crate) struct DrawCommand { - pub(crate) shader: Handle, + pub(crate) shader: usize, + pub(crate) mesh: Arc, /// TODO: generate inside renderer - pub(crate) bind_group: Handle, + pub(crate) bind_group: usize, + pub(crate) index_count: u32, pub(crate) vertex_offset: i32, } @@ -37,32 +53,73 @@ pub(crate) struct DrawCommand { /// Compatible draw commands are given an instance in the object buffer and merged together struct InstancedDrawCommand { cmd: DrawCommand, - mesh: Arc, first_instance: u32, instance_count: u32, } -struct InstancedDrawCommandRef<'a> { +pub(crate) struct InstancedDrawCommandRef<'a> { cmd: &'a DrawCommand, - mesh: &'a Arc, - first_instance: u32, - instance_count: u32, + + pub(crate) first_instance: u32, + pub(crate) instance_count: u32, +} + +component! { + draw_cmd_id: usize, +} + +#[derive(Debug, Default)] +pub struct RendererStore { + pub bind_groups: Slab, + pub shaders: Slab, +} + +impl RendererStore { + pub fn push_bind_group(&mut self, bind_group: BindGroup) -> usize { + self.bind_groups.insert(bind_group) + } + + pub fn push_shader(&mut self, shader: Shader) -> usize { + self.shaders.insert(shader) + } +} + +#[derive(Fetch)] +pub(crate) struct DrawQuery { + pub(crate) entity: EntityRefs, + pub(crate) object_data: Component, + pub(crate) shape: NthRelation<()>, + pub(crate) draw_cmd: Component, +} + +impl DrawQuery { + pub fn new() -> Self { + Self { + entity: entity_refs(), + object_data: object_data(), + shape: nth_relation(draw_shape, 0), + draw_cmd: draw_cmd(), + } + } } /// Draws shapes from the frame -pub struct ShapeRenderer { +pub struct WidgetRenderer { + store: RendererStore, quad: Mesh, objects: Vec, object_buffer: TypedBuffer, bind_group: wgpu::BindGroup, + register_objects: flax::system::BoxedSystem, + commands: Vec, rect_renderer: RectRenderer, text_renderer: TextRenderer, } -impl ShapeRenderer { +impl WidgetRenderer { pub fn new( frame: &mut Frame, ctx: &mut RendererContext, @@ -85,18 +142,36 @@ impl ShapeRenderer { .bind_buffer(object_buffer.buffer()) .build(&ctx.gpu, &object_bind_group_layout); + let register_objects = flax::system::System::builder() + .with_cmd_mut() + .with_query(Query::new(entity_ids()).with_relation(draw_shape)) + .build(|cmd: &mut CommandBuffer, mut query: QueryBorrow<_, _>| { + (&mut query).into_iter().for_each(|id| { + cmd.set(id, object_data(), ObjectData::zeroed()); + }); + }) + .boxed(); + // let solid_layout = BindGroupLayoutBuilder::new("RectRenderer::layout") // .bind_sampler(ShaderStages::FRAGMENT) // .bind_texture(ShaderStages::FRAGMENT) // .build(&ctx.gpu); + let mut store = RendererStore::default(); + Self { quad: Mesh::quad(&ctx.gpu), objects: Vec::new(), object_buffer, bind_group, commands: Vec::new(), - rect_renderer: RectRenderer::new(ctx, frame, color_format, &object_bind_group_layout), + rect_renderer: RectRenderer::new( + ctx, + frame, + color_format, + &object_bind_group_layout, + &mut store, + ), text_renderer: TextRenderer::new( ctx, frame, @@ -104,6 +179,8 @@ impl ShapeRenderer { color_format, &object_bind_group_layout, ), + store, + register_objects, } } @@ -117,19 +194,16 @@ impl ShapeRenderer { self.quad.bind(render_pass); + self.register_objects.run(&mut frame.world)?; + self.rect_renderer.update(&ctx.gpu, frame); - self.rect_renderer.build_commands(&ctx.gpu, frame); + self.rect_renderer + .build_commands(&ctx.gpu, frame, &mut self.store); self.text_renderer.update_meshes(ctx, frame); self.text_renderer.update(&ctx.gpu, frame); - let mut query = Query::new(( - color().opt_or(Srgba::new(1.0, 1.0, 1.0, 1.0)), - mesh_handle(), - model_matrix(), - draw_cmd(), - )) - .topo(child_of); + let mut query = Query::new(DrawQuery::new()).topo(child_of); let mut query = query.borrow(&frame.world); @@ -137,28 +211,24 @@ impl ShapeRenderer { let commands = query .iter() - .map(|(&color, mesh, &model, cmd)| { - let instance = self.objects.len() as u32; + .map(|item| { + let instance_index = self.objects.len() as u32; - self.objects.push(ObjectData { - model_matrix: model, - color: srgba_to_vec4(color), - }); + self.objects.push(*item.object_data); + let draw_cmd = item.draw_cmd; // tracing::info!(?mesh, "drawing"); InstancedDrawCommandRef { - cmd, - mesh, - first_instance: instance, + cmd: draw_cmd, + first_instance: instance_index, instance_count: 1, } }) .coalesce(|prev, current| { - if prev.cmd == current.cmd && prev.mesh == current.mesh { + if prev.cmd == current.cmd { assert!(prev.first_instance + prev.instance_count == current.first_instance); Ok(InstancedDrawCommandRef { cmd: prev.cmd, - mesh: prev.mesh, first_instance: prev.first_instance, instance_count: prev.instance_count + 1, }) @@ -168,7 +238,6 @@ impl ShapeRenderer { }) .map(|cmd| InstancedDrawCommand { cmd: cmd.cmd.clone(), - mesh: cmd.mesh.clone(), first_instance: cmd.first_instance, instance_count: cmd.instance_count, }); @@ -181,17 +250,21 @@ impl ShapeRenderer { self.commands.iter().for_each(|instanced_cmd| { let cmd = &instanced_cmd.cmd; - render_pass.set_pipeline(cmd.shader.pipeline()); + let shader = &self.store.shaders[cmd.shader]; + let bind_group = &self.store.bind_groups[cmd.bind_group]; + + render_pass.set_pipeline(shader.pipeline()); render_pass.set_bind_group(0, &ctx.globals_bind_group, &[]); render_pass.set_bind_group(1, &self.bind_group, &[]); - render_pass.set_bind_group(2, &cmd.bind_group, &[]); + render_pass.set_bind_group(2, bind_group, &[]); - let first_index = instanced_cmd.mesh.ib().offset() as u32; + let mesh = &instanced_cmd.cmd.mesh; + let first_index = mesh.ib().offset() as u32; render_pass.draw_indexed( first_index..(first_index + cmd.index_count), - cmd.vertex_offset + instanced_cmd.mesh.vb().offset() as i32, + cmd.vertex_offset + mesh.vb().offset() as i32, instanced_cmd.first_instance ..(instanced_cmd.first_instance + instanced_cmd.instance_count), ) @@ -203,9 +276,10 @@ impl ShapeRenderer { #[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)] #[repr(C)] -struct ObjectData { - model_matrix: Mat4, - color: Vec4, +/// Per object uniform data +pub(crate) struct ObjectData { + pub(crate) model_matrix: Mat4, + pub(crate) color: Vec4, } pub fn srgba_to_vec4(color: Srgba) -> Vec4 { diff --git a/src/wgpu/systems.rs b/src/wgpu/systems.rs index 1861b3e..6a5aaa2 100644 --- a/src/wgpu/systems.rs +++ b/src/wgpu/systems.rs @@ -1,6 +1,6 @@ use std::sync::Arc; -use cosmic_text::{Attrs, Buffer, FontSystem, Metrics, Shaping}; +use cosmic_text::{Attrs, Buffer, FontSystem, LayoutGlyph, Metrics, Shaping}; use flax::{ entity_ids, fetch::{Modified, TransformFetch}, @@ -127,8 +127,14 @@ impl SizeResolver for TextSizeResolver { let query = (text_buffer_state().as_mut(), font_size(), text()); if let Some((state, &font_size, text)) = entity.query(&query).get() { let font_system = &mut *self.font_system.lock(); - let preferred = - Self::resolve_text_size(state, font_system, text, font_size, content_area, limits); + let preferred = Self::resolve_text_size( + state, + font_system, + text, + font_size, + content_area, + limits.map(|v| v.max_size), + ); let min = Self::resolve_text_size( state, @@ -136,12 +142,13 @@ impl SizeResolver for TextSizeResolver { text, font_size, content_area, - Some(LayoutLimits { - min_size: Vec2::ZERO, - max_size: Vec2::ZERO, - }), + Some(vec2(1.0, f32::MAX)), ); + // assert!(min.x <= 1.0); + // assert!(min.y <= 1.0); + + // tracing::debug!(%min, %preferred); return (min, preferred); } @@ -156,9 +163,9 @@ impl TextSizeResolver { text: &str, font_size: f32, _content_area: Rect, - limits: Option, + limits: Option, ) -> Vec2 { - let _span = tracing::debug_span!("resolve_text_size", font_size, ?text, ?limits).entered(); + // let _span = tracing::debug_span!("resolve_text_size", font_size, ?text, ?limits).entered(); { let mut buffer = state.buffer.borrow_with(font_system); @@ -167,7 +174,7 @@ impl TextSizeResolver { buffer.set_metrics(metrics); if let Some(limits) = limits { - buffer.set_size(limits.max_size.x, limits.max_size.y); + buffer.set_size(limits.x, limits.y); } else { buffer.set_size(f32::MAX, f32::MAX); } @@ -177,18 +184,36 @@ impl TextSizeResolver { let size = measure(&state.buffer); - tracing::debug!(%size); + // tracing::debug!(%size); size } } +fn glyph_bounds(glyph: &LayoutGlyph) -> (f32, f32) { + (glyph.x, glyph.x + glyph.w) +} + fn measure(buffer: &Buffer) -> Vec2 { - let (width, total_lines) = buffer - .layout_runs() - .fold((0.0, 0), |(width, total_lines), run| { - (run.line_w.max(width), total_lines + 1) - }); + let (width, total_lines) = + buffer + .layout_runs() + .fold((0.0f32, 0), |(width, total_lines), run| { + if let (Some(first), Some(last)) = (run.glyphs.first(), run.glyphs.last()) { + let (l1, r1) = glyph_bounds(first); + let (l2, r2) = glyph_bounds(last); + + let l = l1.min(l2); + let r = r1.max(r2); + + assert!(l <= r); + // tracing::debug!(l1, r1, l2, r2, "run"); + + (width.max(r - l), total_lines + 1) + } else { + (width, total_lines + 1) + } + }); vec2(width, total_lines as f32 * buffer.metrics().line_height) } diff --git a/src/wgpu/text_renderer.rs b/src/wgpu/text_renderer.rs index cbeb247..d805cd5 100644 --- a/src/wgpu/text_renderer.rs +++ b/src/wgpu/text_renderer.rs @@ -3,6 +3,7 @@ use std::sync::Arc; use cosmic_text::{Attrs, Buffer, CacheKey, FontSystem, Metrics, Placement, Shaping, SwashCache}; use flax::{ entity_ids, + fetch::{Modified, TransformFetch}, filter::{All, With}, CommandBuffer, Component, Debuggable, EntityIds, Fetch, FetchExt, Mutable, Opt, OptOr, Query, }; @@ -13,7 +14,7 @@ use wgpu::{BindGroup, BindGroupLayout, Sampler, SamplerDescriptor, ShaderStages, use crate::{ assets::{AssetCache, Handle}, - components::{font_size, rect, screen_position, text, text_limits, Rect}, + components::{font_size, layout_bounds, rect, screen_position, text, Rect}, wgpu::{ font::FontAtlas, graphics::{allocator::Allocation, BindGroupBuilder}, @@ -264,7 +265,7 @@ pub(crate) struct TextMeshQuery { rect: Component, text: Component, - text_limits: Component, + layout_bounds: Component, #[fetch(ignore)] font_size: OptOr, f32>, } @@ -276,7 +277,7 @@ impl TextMeshQuery { mesh: mesh_handle().as_mut().opt(), rect: rect(), text: text(), - text_limits: text_limits(), + layout_bounds: layout_bounds(), font_size: font_size().opt_or(16.0), state: text_buffer_state().as_mut(), } @@ -295,7 +296,7 @@ pub struct TextRenderer { swash_cache: SwashCache, object_query: Query, - mesh_query: Query, + mesh_query: Query<>::Output, All>, } impl TextRenderer { @@ -310,7 +311,7 @@ impl TextRenderer { Self { object_query: Query::new(ObjectQuery::new()).with(text()), mesh_generator, - mesh_query: Query::new(TextMeshQuery::new()), + mesh_query: Query::new(TextMeshQuery::new().modified()), font_system, swash_cache: SwashCache::new(), } @@ -335,39 +336,38 @@ impl TextRenderer { { let mut buffer = item.state.buffer.borrow_with(font_system); - let limits = item.text_limits; - - buffer.set_size(limits.x, limits.y); + buffer.set_size(item.layout_bounds.x, item.layout_bounds.y); buffer.shape_until_scroll(); } let mut new_mesh = None; - let mesh = match item.mesh { - Some(v) => v, - None => new_mesh.insert(Arc::new(ctx.mesh_buffer.allocate(&ctx.gpu, 0, 0))), - }; - - let index_count = self.mesh_generator.update_mesh( - ctx, - &frame.assets, - font_system, - &mut self.swash_cache, - &mut item.state.buffer, - mesh, - ); - - cmd.set( - item.id, - draw_cmd(), - DrawCommand { - bind_group: self.mesh_generator.rasterizer.rasterized.bind_group.clone(), - shader: self.mesh_generator.shader.clone(), - index_count, - vertex_offset: 0, - }, - ); + // let mesh = match item { + // Some(v) => v, + // None => new_mesh.insert(Arc::new(ctx.mesh_buffer.allocate(&ctx.gpu, 0, 0))), + // }; + + // let index_count = self.mesh_generator.update_mesh( + // ctx, + // &frame.assets, + // font_system, + // &mut self.swash_cache, + // &mut item.state.buffer, + // mesh, + // ); + + // cmd.set( + // item.id, + // draw_cmd(), + // DrawCommand { + // bind_group: self.mesh_generator.rasterizer.rasterized.bind_group.clone(), + // shader: self.mesh_generator.shader.clone(), + // mesh: , + // index_count, + // vertex_offset: 0, + // }, + // ); if let Some(v) = new_mesh { cmd.set(item.id, mesh_handle(), v); diff --git a/src/wgpu/window_renderer.rs b/src/wgpu/window_renderer.rs index 46ac566..64497a1 100644 --- a/src/wgpu/window_renderer.rs +++ b/src/wgpu/window_renderer.rs @@ -12,7 +12,7 @@ use crate::Frame; use super::{ graphics::{Gpu, Surface}, renderer::RendererContext, - ShapeRenderer, + WidgetRenderer, }; /// Renders to a window surface @@ -20,7 +20,7 @@ pub struct WindowRenderer { surface: Surface, ctx: RendererContext, - shape_renderer: ShapeRenderer, + shape_renderer: WidgetRenderer, } impl WindowRenderer { @@ -33,7 +33,7 @@ impl WindowRenderer { let mut ctx = RendererContext::new(gpu); let shape_renderer = - ShapeRenderer::new(frame, &mut ctx, font_system, surface.surface_format()); + WidgetRenderer::new(frame, &mut ctx, font_system, surface.surface_format()); Self { surface, diff --git a/src/widget/mod.rs b/src/widget/mod.rs index d1e8310..fc34655 100644 --- a/src/widget/mod.rs +++ b/src/widget/mod.rs @@ -1,4 +1,4 @@ -use crate::Scope; +use crate::{style::WithComponent, Scope}; mod future; pub use future::{SignalWidget, StreamWidget}; @@ -33,6 +33,21 @@ where } } +pub trait WidgetExt: Widget + Sized { + fn boxed<'a>(self) -> Box + where + Self: 'a + Sized, + { + Box::new(self) + } + + fn with_name(self, name: impl Into) -> WithComponent { + WithComponent::new(self, flax::components::name(), name.into()) + } +} + +impl WidgetExt for W where W: Widget {} + /// Represents a list of widgets pub trait WidgetCollection { fn attach(self, scope: &mut Scope); diff --git a/src/widgets/mod.rs b/src/widgets/mod.rs index 9dbb037..a6d3637 100644 --- a/src/widgets/mod.rs +++ b/src/widgets/mod.rs @@ -3,9 +3,8 @@ use palette::Srgba; use winit::event::ElementState; use crate::{ - components::{color, filled_rect}, + components::{color, draw_shape, shape_rectangle}, input::{on_focus, on_mouse_input}, - shapes::FilledRect, Frame, Scope, Widget, }; @@ -24,13 +23,7 @@ impl Widget for Rectangle { fn mount(self, scope: &mut Scope) { scope .set(name(), "Rectangle".into()) - .set( - filled_rect(), - FilledRect { - color: self.color, - fill_image: None, - }, - ) + .set(draw_shape(shape_rectangle()), ()) .set(color(), self.color); } } @@ -58,13 +51,7 @@ impl Button { impl Widget for Button { fn mount(mut self, scope: &mut Scope<'_>) { scope - .set( - filled_rect(), - FilledRect { - color: self.normal_color, - fill_image: None, - }, - ) + .set(draw_shape(shape_rectangle()), ()) .set(color(), self.normal_color) .set( on_focus(),