use std::any::Any;
use std::sync::Arc;
use bevy_ecs::prelude::*;
use glifo::Glyph;
use parley::Layout;
use parley::PositionedLayoutItem;
use vello::kurbo::Affine;
use vello::kurbo::Rect;
use vello::kurbo::Shape as _;
use vello::util::RenderContext;
use vello::util::RenderSurface;
use vello_hybrid::RenderSize;
use vello_hybrid::RenderTargetConfig;
use vello_hybrid::Resources;
use vello_hybrid::Scene;
use vello_hybrid::TextureBindings;
use wgpu::CurrentSurfaceTexture;
use winit::dpi::PhysicalSize;
use winit::window::Window;
use crate::color::Color;
use crate::layout::ContainerNode;
use crate::layout::MeasuredLayout;
use crate::layout::RenderedLayout;
use crate::layout::RootNode;
use crate::layout::ScrollNode;
use crate::layout::WindowSize;
use crate::point::Point;
use crate::style::Rectangle;
use crate::widget::text::TextNode;
pub mod schedule {
use bevy_ecs::schedule::ScheduleLabel;
#[derive(ScheduleLabel, Hash, Debug, Clone, Copy, PartialEq, Eq)]
pub struct AppResume;
#[derive(ScheduleLabel, Hash, Debug, Clone, Copy, PartialEq, Eq)]
pub struct AppSuspend;
#[derive(ScheduleLabel, Hash, Debug, Clone, Copy, PartialEq, Eq)]
pub struct Measure;
#[derive(ScheduleLabel, Hash, Debug, Clone, Copy, PartialEq, Eq)]
pub struct Render;
#[derive(ScheduleLabel, Hash, Debug, Clone, Copy, PartialEq, Eq)]
pub struct Flush;
}
pub(crate) fn create_vello_renderer(
render_cx: &RenderContext,
surface: &RenderSurface<'_>,
) -> vello_hybrid::Renderer {
vello_hybrid::Renderer::new(
&render_cx.devices[surface.dev_id].device,
&RenderTargetConfig {
format: surface.config.format,
width: surface.config.width,
height: surface.config.height,
},
)
}
#[derive(Resource, Debug, Clone, Copy)]
pub struct ScaleFactor(pub f32);
#[derive(Default)]
pub struct VelloRenderers(pub Vec<Option<vello_hybrid::Renderer>>);
#[derive(Default)]
pub struct VelloResources(pub Resources);
#[derive(Clone, Debug)]
pub enum Command {
Rectangle {
rectangle: Rectangle,
color: Color,
},
Text {
color: Color,
layout: Layout<[u8; 4]>,
},
}
#[derive(Clone, Debug)]
pub struct DrawCommand {
pub render_target: RenderTarget,
pub commands: Vec<Command>,
}
#[derive(Resource)]
pub struct DrawList(pub Vec<DrawCommand>);
pub trait Measurable: Send + Sync + 'static {
fn as_any_mut(&mut self) -> &mut dyn Any;
fn measure(&mut self, available_space: taffy::Size<taffy::AvailableSpace>) -> Rectangle {
let _ = available_space;
Rectangle::zero()
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct RenderTarget {
pub rectangle: Rectangle,
pub scroll: Point,
}
pub trait Renderable: Send + Sync + 'static {
fn as_any_mut(&mut self) -> &mut dyn Any;
fn render(&mut self, render_target: RenderTarget) -> Vec<DrawCommand> {
let _ = render_target;
Vec::new()
}
}
#[derive(Component)]
pub struct Measurer(pub Box<dyn Measurable + Send + Sync>);
#[derive(Component)]
pub struct EmptyMeasurer;
impl Measurable for EmptyMeasurer {
fn as_any_mut(&mut self) -> &mut dyn Any {
self
}
}
#[derive(Component)]
pub struct Renderer(pub Box<dyn Renderable + Send + Sync>);
#[derive(Resource)]
pub(crate) enum RenderState {
Initial,
Suspended {
window: Arc<Window>,
},
Running {
surface: Box<RenderSurface<'static>>,
valid_surface: bool,
window: Arc<Window>,
},
}
#[derive(Event, Debug, Clone, Copy)]
pub struct AppResize(pub PhysicalSize<u32>);
#[derive(Event, Debug, Clone, Copy)]
pub struct AppScaleFactor(pub ScaleFactor);
fn resume(
mut commands: Commands,
mut app_state: ResMut<RenderState>,
mut render_context: NonSendMut<RenderContext>,
mut renderers: NonSendMut<VelloRenderers>,
) {
let RenderState::Suspended { ref window } = *app_state else {
unreachable!("got window event while suspended");
};
let size = window.inner_size();
let surface = render_context.create_surface(
window.clone(),
size.width,
size.height,
wgpu::PresentMode::AutoVsync,
);
let surface = pollster::block_on(surface).expect("failed creating surface");
renderers
.0
.resize_with(render_context.devices.len(), || None);
renderers.0[surface.dev_id]
.get_or_insert_with(|| create_vello_renderer(&render_context, &surface));
commands.trigger(AppScaleFactor(ScaleFactor(window.scale_factor() as f32)));
*app_state = RenderState::Running {
surface: Box::new(surface),
valid_surface: true,
window: window.clone(),
};
}
fn suspend(mut app_state: ResMut<RenderState>) {
let RenderState::Running { ref window, .. } = *app_state else {
unreachable!("got resumed event while not running");
};
*app_state = RenderState::Suspended {
window: window.clone(),
};
}
fn resize(
resize: On<AppResize>,
mut app_state: ResMut<RenderState>,
render_context: NonSend<RenderContext>,
mut scene: NonSendMut<Scene>,
) {
let RenderState::Running {
ref mut surface,
ref mut valid_surface,
ref window,
} = *app_state
else {
unreachable!("got window event while suspended");
};
if resize.0.width != 0 && resize.0.height != 0 {
render_context.resize_surface(surface, resize.0.width, resize.0.height);
*scene = Scene::new(
u16::try_from(resize.0.width).unwrap(),
u16::try_from(resize.0.height).unwrap(),
);
*valid_surface = true;
window.request_redraw();
} else {
*valid_surface = false;
}
}
fn scale_factor(
scale_factor: On<AppScaleFactor>,
app_state: Res<RenderState>,
mut scale_factor_res: ResMut<ScaleFactor>,
) {
let RenderState::Running { ref window, .. } = *app_state else {
unreachable!("got window event while suspended");
};
if scale_factor.0.0 == scale_factor_res.0 {
return;
}
*scale_factor_res = scale_factor.0;
window.request_redraw();
}
pub(crate) fn renderer_setup(world: &mut World) {
world.insert_resource(RenderState::Initial);
world.insert_resource(DrawList(Vec::new()));
world.insert_resource(ScaleFactor(1.0));
world.insert_non_send_resource(RenderContext::new());
world.insert_non_send_resource(VelloRenderers::default());
world.insert_non_send_resource(VelloResources::default());
world.insert_non_send_resource(Scene::new(100, 100));
world
.get_resource_or_init::<Schedules>()
.add_systems(schedule::AppResume, resume)
.add_systems(schedule::AppSuspend, suspend)
.add_systems(schedule::Render, render)
.add_systems(schedule::Flush, flush);
world.add_observer(resize);
world.add_observer(scale_factor);
}
type ContainerQuery<'world, 'state> = Query<
'world,
'state,
(
&'static Name,
Entity,
&'static MeasuredLayout,
Option<&'static ScrollNode>,
Option<&'static Children>,
Option<&'static mut Renderer>,
),
(Without<RootNode>, Or<(With<ContainerNode>, With<TextNode>)>),
>;
fn render(
mut commands: Commands,
mut draw_list: ResMut<DrawList>,
window_size: Res<WindowSize>,
scale_factor: Res<ScaleFactor>,
mut root: Query<
(
Entity,
&Name,
&MeasuredLayout,
Option<&'static ScrollNode>,
Option<&Children>,
&mut Renderer,
),
With<RootNode>,
>,
mut containers: ContainerQuery,
) {
let window_size = window_size.0 / scale_factor.0;
for (root_entity, root_name, root_layout, root_scroll, root_children, mut root_renderer) in
&mut root
{
tracing::trace!(?root_entity, ?root_name, "Rendering root");
let root_scroll = root_scroll.map_or_else(Point::default, |p| p.0);
commands
.entity(root_entity)
.insert(RenderedLayout(root_layout.0));
let root_render_target = RenderTarget {
rectangle: root_layout.0,
scroll: root_scroll,
};
let root_commands = root_renderer.0.render(root_render_target);
draw_list.0.extend(root_commands);
let Some(root_children) = root_children else {
continue;
};
fn render_children(
commands: &mut Commands,
window_size: &Rectangle,
draw_list: &mut DrawList,
parent_rendered_layout: Rectangle,
parent_scroll: Point,
child_entities: &[Entity],
containers: &mut ContainerQuery,
) {
for &child in child_entities {
let (grandchildren, child_rendered_layout, child_scroll) = {
let Ok((
child_name,
child_entity,
computed_layout,
child_scroll,
children,
renderer,
)) = containers.get_mut(child)
else {
continue;
};
let child_computed_layout = computed_layout.0.move_by(&-parent_scroll);
let child_rendered_layout =
child_computed_layout.clamp(&parent_rendered_layout);
let child_render_scroll = Point {
x: child_rendered_layout.x() - child_computed_layout.x(),
y: child_rendered_layout.y() - child_computed_layout.y(),
};
let child_scroll =
parent_scroll + child_scroll.map_or_else(Point::default, |s| s.0);
if let Some(mut renderer) = renderer {
if !child_rendered_layout.is_empty()
&& window_size.height() > child_computed_layout.y()
&& window_size.width() > child_computed_layout.x()
{
tracing::trace!(
?child_entity,
?child_name,
"Rendering child: {:?}",
child_rendered_layout,
);
let child_render_target = RenderTarget {
rectangle: child_rendered_layout,
scroll: child_render_scroll,
};
let draw_commands = renderer.0.render(child_render_target);
draw_list.0.extend(draw_commands);
}
} else {
tracing::warn!(
?child_entity,
?child_name,
"No renderer for child: {:?}",
computed_layout.0,
);
}
(
children
.map(|c| c.iter().collect::<Vec<_>>())
.unwrap_or_default(),
child_rendered_layout,
child_scroll,
)
};
commands
.entity(child)
.insert(RenderedLayout(child_rendered_layout));
if !grandchildren.is_empty() {
render_children(
commands,
window_size,
draw_list,
child_rendered_layout,
child_scroll,
&grandchildren,
containers,
);
}
}
}
let root_children: Vec<Entity> = root_children.iter().collect();
render_children(
&mut commands,
&window_size,
&mut draw_list,
root_layout.0,
root_scroll,
&root_children,
&mut containers,
);
}
}
fn flush(
app_state: Res<RenderState>,
scale_factor: Res<ScaleFactor>,
mut scene: NonSendMut<Scene>,
render_context: NonSend<RenderContext>,
mut renderers: NonSendMut<VelloRenderers>,
mut resources: NonSendMut<VelloResources>,
mut draw_list: ResMut<DrawList>,
) {
tracing::debug!("Flushing draw list with {} commands", draw_list.0.len());
let RenderState::Running {
ref window,
ref surface,
..
} = *app_state
else {
unreachable!("got window event while suspended");
};
scene.reset();
for DrawCommand {
render_target,
commands,
} in draw_list.0.drain(..)
{
tracing::trace!(
"Flushing draw command with render target: {}:{} ({})",
render_target.rectangle.width(),
render_target.rectangle.height(),
commands.len(),
);
for command in commands {
scene.set_transform(Affine::IDENTITY);
let clip_rectangle = render_target.rectangle * scale_factor.0;
scene.push_clip_path(
&Rect {
x0: clip_rectangle.x() as f64,
y0: clip_rectangle.y() as f64,
x1: clip_rectangle.x() as f64 + clip_rectangle.width() as f64,
y1: clip_rectangle.y() as f64 + clip_rectangle.height() as f64,
}
.to_path(0.1),
);
let transform =
Affine::translate((clip_rectangle.x() as f64, clip_rectangle.y() as f64));
scene.set_transform(transform);
match command {
Command::Rectangle { rectangle, color } => {
let rectangle = rectangle * scale_factor.0;
let color: vello::peniko::Color = color.into();
let x0 = rectangle.x() as f64;
let y0 = rectangle.y() as f64;
let x1 = rectangle.x() as f64 + rectangle.width() as f64;
let y1 = rectangle.y() as f64 + rectangle.height() as f64;
scene.set_paint(color);
scene.fill_path(&Rect { x0, y0, x1, y1 }.to_path(0.1));
}
Command::Text { color, layout } => {
let color: vello::peniko::Color = color.into();
scene.set_paint(color);
for line in layout.lines() {
for item in line.items() {
match item {
PositionedLayoutItem::GlyphRun(glyph_run) => {
let mut run_x = glyph_run.offset();
let run_y = glyph_run.baseline()
- render_target.scroll.y * scale_factor.0;
let glyphs = glyph_run.glyphs().map(move |glyph| {
let glyph_x = run_x + glyph.x;
let glyph_y = run_y - glyph.y;
run_x += glyph.advance;
Glyph {
id: glyph.id,
x: glyph_x,
y: glyph_y,
}
});
let run = glyph_run.run();
let font = run.font();
let font_size = run.font_size();
let normalized_coords =
bytemuck::cast_slice(run.normalized_coords());
scene
.glyph_run(&mut resources.0, font)
.font_size(font_size)
.normalized_coords(normalized_coords)
.hint(true)
.fill_glyphs(glyphs);
}
PositionedLayoutItem::InlineBox(_positioned_inline_box) => todo!(),
}
}
}
}
}
scene.pop_clip_path();
}
}
let surface_texture = match surface.surface.get_current_texture() {
CurrentSurfaceTexture::Success(surface_texture) => surface_texture,
CurrentSurfaceTexture::Outdated | CurrentSurfaceTexture::Suboptimal(_) => {
render_context.configure_surface(surface);
window.request_redraw();
return;
}
CurrentSurfaceTexture::Occluded | CurrentSurfaceTexture::Timeout => {
window.request_redraw();
return;
}
CurrentSurfaceTexture::Lost => panic!("Surface was lost"),
CurrentSurfaceTexture::Validation => {
panic!("Validation error getting surface")
}
};
let texture_view = surface_texture
.texture
.create_view(&wgpu::TextureViewDescriptor::default());
let device_handle = &render_context.devices[surface.dev_id];
let mut encoder =
device_handle
.device
.create_command_encoder(&wgpu::CommandEncoderDescriptor {
label: Some("Surface Blit"),
});
let render_size = RenderSize {
width: surface.config.width,
height: surface.config.height,
};
renderers.0[surface.dev_id]
.as_mut()
.unwrap()
.render(
&scene,
&mut resources.0,
&device_handle.device,
&device_handle.queue,
&mut encoder,
&render_size,
&texture_view,
&TextureBindings::new(),
)
.unwrap();
device_handle.queue.submit([encoder.finish()]);
surface_texture.present();
device_handle.device.poll(wgpu::PollType::Poll).unwrap();
}