mod tinyvg;
use crate::geometry::{Rectangle};
use crate::renderer::color::Color;
use crate::renderer::image_adapter::ImageAdapter;
use crate::renderer::renderer::{SortedCommands, RenderCommand, RenderList, Renderer, TextScroll};
use crate::resource_manager::resource::Resource;
use crate::resource_manager::{ResourceManager};
use std::sync::Arc;
use peniko::BrushRef;
use vello::kurbo::{Affine, Rect};
use vello::peniko::{BlendMode, Blob, Fill};
use vello::util::{RenderContext, RenderSurface};
use vello::{kurbo, peniko, AaConfig, RendererOptions};
use vello::{Glyph, Scene};
use winit::window::Window;
use crate::renderer::vello::tinyvg::draw_tiny_vg;
pub struct ActiveRenderState<'s> {
surface: RenderSurface<'s>,
window: Arc<dyn Window>,
}
enum RenderState<'a> {
Active(ActiveRenderState<'a>),
Suspended,
}
pub struct VelloRenderer<'a> {
context: RenderContext,
renderers: Vec<Option<vello::Renderer>>,
state: RenderState<'a>,
scene: Scene,
surface_clear_color: Color,
}
fn create_vello_renderer(render_cx: &RenderContext, surface: &RenderSurface) -> vello::Renderer {
vello::Renderer::new(
&render_cx.devices[surface.dev_id].device,
RendererOptions {
use_cpu: false,
antialiasing_support: if cfg!(any(target_os = "android", target_os = "ios")) {
vello::AaSupport {
area: true,
msaa8: false,
msaa16: false,
}
} else {
vello::AaSupport {
area: false,
msaa8: false,
msaa16: true,
}
},
num_init_threads: None,
pipeline_cache: None,
},
)
.expect("Couldn't create renderer")
}
impl<'a> VelloRenderer<'a> {
pub(crate) async fn new(window: Arc<dyn Window>) -> VelloRenderer<'a> {
let mut vello_renderer = VelloRenderer {
context: RenderContext::new(),
renderers: vec![],
state: RenderState::Suspended,
scene: Scene::new(),
surface_clear_color: Color::WHITE,
};
let surface_size = window.surface_size();
let surface = vello_renderer
.context
.create_surface(
window.clone(),
surface_size.width,
surface_size.height,
wgpu::PresentMode::AutoVsync,
)
.await
.unwrap();
vello_renderer.renderers.resize_with(vello_renderer.context.devices.len(), || None);
vello_renderer.renderers[0].get_or_insert_with(|| create_vello_renderer(&vello_renderer.context, &surface));
vello_renderer.state = RenderState::Active(ActiveRenderState { window, surface });
vello_renderer
}
}
fn vello_draw_rect(scene: &mut Scene, rectangle: Rectangle, fill_color: Color) {
let rect = Rect::new(
rectangle.x as f64,
rectangle.y as f64,
(rectangle.x + rectangle.width) as f64,
(rectangle.y + rectangle.height) as f64,
);
scene.fill(Fill::NonZero, Affine::IDENTITY, fill_color, None, &rect);
}
impl Renderer for VelloRenderer<'_> {
fn surface_width(&self) -> f32 {
match &self.state {
RenderState::Active(active_render_state) => active_render_state.window.surface_size().width as f32,
RenderState::Suspended => 0.0,
}
}
fn surface_height(&self) -> f32 {
match &self.state {
RenderState::Active(active_render_state) => active_render_state.window.surface_size().height as f32,
RenderState::Suspended => 0.0,
}
}
fn resize_surface(&mut self, width: f32, height: f32) {
let render_state = match &mut self.state {
RenderState::Active(state) => state,
_ => return,
};
self.context.resize_surface(&mut render_state.surface, width as u32, height as u32);
}
fn surface_set_clear_color(&mut self, color: Color) {
self.surface_clear_color = color;
}
fn prepare_render_list(&mut self, render_list: RenderList, resource_manager: Arc<ResourceManager>, window: Rectangle) {
SortedCommands::draw(&render_list, &render_list.overlay, &mut |command: &RenderCommand| {
let scene = &mut self.scene;
match command {
RenderCommand::DrawRect(rectangle, fill_color) => {
vello_draw_rect(scene, *rectangle, *fill_color);
}
RenderCommand::DrawRectOutline(_rectangle, _outline_color) => {
}
RenderCommand::DrawImage(rectangle, resource_identifier) => {
let resource = resource_manager.resources.get(resource_identifier);
if let Some(resource) = resource {
if let Resource::Image(resource) = resource.as_ref() {
let image = &resource.image;
let data = Arc::new(ImageAdapter::new(resource.clone()));
let blob = Blob::new(data);
let vello_image =
peniko::Image::new(blob, peniko::ImageFormat::Rgba8, image.width(), image.height());
let mut transform = Affine::IDENTITY;
transform =
transform.with_translation(kurbo::Vec2::new(rectangle.x as f64, rectangle.y as f64));
transform = transform.pre_scale_non_uniform(
rectangle.width as f64 / image.width() as f64,
rectangle.height as f64 / image.height() as f64,
);
scene.draw_image(&vello_image, transform);
}
}
}
RenderCommand::DrawText(text_render, rect, text_scroll, show_cursor) => {
let text_transform =
Affine::default().with_translation(kurbo::Vec2::new(rect.x as f64, rect.y as f64));
let scroll = text_scroll.unwrap_or(TextScroll::default()).scroll_y;
let text_transform = text_transform.then_translate(kurbo::Vec2::new(0.0, -scroll as f64));
let mut skip_remaining_lines = false;
let mut skip_line = false;
for line in &text_render.lines {
if skip_remaining_lines {
break;
}
if skip_line {
skip_line = false;
continue;
}
for item in &line.items {
if let Some(first_glyph) = item.glyphs.first() {
let gy = first_glyph.y + rect.y - scroll;
if gy < window.y {
skip_line = true;
break;
} else if gy > (window.y + window.height) {
skip_remaining_lines = true;
break;
}
}
for selection in &line.selections {
let selection_rect = Rectangle {
x: selection.x + rect.x,
y: -scroll + selection.y + rect.y,
width: selection.width,
height: selection.height,
};
vello_draw_rect(scene, selection_rect, Color::from_rgb8(0, 120, 215));
}
}
}
skip_remaining_lines = false;
skip_line = false;
for line in &text_render.lines {
if skip_remaining_lines {
break;
}
if skip_line {
skip_line = false;
continue;
}
for item in &line.items {
if let Some(first_glyph) = item.glyphs.first() {
let gy = first_glyph.y + rect.y - scroll;
if gy < window.y {
skip_line = true;
break;
} else if gy > (window.y + window.height) {
skip_remaining_lines = true;
break;
}
}
scene
.draw_glyphs(&item.font)
.font_size(item.font_size)
.brush(BrushRef::Solid(text_render.override_brush.map(|b| b.color).unwrap_or_else(|| item.brush.color)))
.transform(text_transform)
.glyph_transform(item.glyph_transform)
.draw(
Fill::NonZero,
item.glyphs.iter().map(|glyph| Glyph {
id: glyph.id as u32,
x: glyph.x,
y: glyph.y,
}),
);
}
}
if *show_cursor {
if let Some(cursor) = &text_render.cursor {
let cursor_rect = Rectangle {
x: cursor.x + rect.x,
y: -scroll + cursor.y + rect.y,
width: cursor.width,
height: cursor.height,
};
vello_draw_rect(scene, cursor_rect, Color::from_rgb8(0, 0, 0));
}
}
}
RenderCommand::DrawTinyVg(rectangle, resource_identifier, override_color) => {
draw_tiny_vg(scene, *rectangle, resource_manager.clone(), resource_identifier.clone(), override_color);
}
RenderCommand::PushLayer(rect) => {
let clip = Rect::new(
rect.x as f64,
rect.y as f64,
(rect.x + rect.width) as f64,
(rect.y + rect.height) as f64,
);
scene.push_layer(BlendMode::default(), 1.0, Affine::IDENTITY, &clip);
}
RenderCommand::PopLayer => {
scene.pop_layer();
}
RenderCommand::FillBezPath(path, brush) => {
scene.fill(Fill::NonZero, Affine::IDENTITY, brush, None, &path);
}
_ => {}
}
});
}
fn submit(&mut self, _resource_manager: Arc<ResourceManager>) {
let render_state = match &mut self.state {
RenderState::Active(state) => state,
_ => panic!("!!!"),
};
let surface = &render_state.surface;
let width = surface.config.width;
let height = surface.config.height;
let device_handle = &self.context.devices[surface.dev_id];
let surface_texture = surface.surface.get_current_texture().expect("failed to get surface texture");
self.renderers[surface.dev_id]
.as_mut()
.unwrap()
.render_to_texture(
&device_handle.device,
&device_handle.queue,
&self.scene,
&surface.target_view,
&vello::RenderParams {
base_color: self.surface_clear_color,
width,
height,
antialiasing_method: if cfg!(any(target_os = "android", target_os = "ios")) {
AaConfig::Area
} else {
AaConfig::Msaa16
},
},
)
.expect("failed to render to surface");
let mut encoder = device_handle.device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
label: Some("Surface Blit"),
});
surface.blitter.copy(
&device_handle.device,
&mut encoder,
&surface.target_view,
&surface_texture.texture.create_view(&wgpu::TextureViewDescriptor::default()),
);
device_handle.queue.submit([encoder.finish()]);
surface_texture.present();
self.scene.reset();
}
}