use astrelis_render::wgpu::util::DeviceExt;
use astrelis_render::{GraphicsContext, Renderer, Viewport, wgpu};
use astrelis_text::{FontRenderer, FontSystem, TextPipeline, shape_text};
use std::sync::Arc;
use crate::glyph_atlas::glyphs_to_instances;
use crate::gpu_types::{QuadInstance, QuadVertex, TextInstance};
use crate::instance_buffer::InstanceBuffer;
use super::overlay_draw_list::{OverlayCommand, OverlayDrawList};
pub struct OverlayRenderer {
#[allow(dead_code)]
context: Arc<GraphicsContext>,
renderer: Renderer,
font_renderer: FontRenderer,
text_pipeline: TextPipeline,
quad_pipeline: wgpu::RenderPipeline,
text_pipeline_gpu: wgpu::RenderPipeline,
unit_quad_vbo: wgpu::Buffer,
quad_instances: InstanceBuffer<QuadInstance>,
text_instances: InstanceBuffer<TextInstance>,
projection_buffer: wgpu::Buffer,
projection_bind_group: wgpu::BindGroup,
text_atlas_bind_group: wgpu::BindGroup,
text_projection_bind_group: wgpu::BindGroup,
scale_factor: f64,
}
impl OverlayRenderer {
pub fn new(context: Arc<GraphicsContext>) -> Self {
let renderer = Renderer::new(context.clone());
let font_system = FontSystem::with_system_fonts();
let font_renderer = FontRenderer::new(context.clone(), font_system);
let unit_quad_vertices = QuadVertex::unit_quad();
let unit_quad_vbo =
context
.device()
.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("Overlay Unit Quad VBO"),
contents: bytemuck::cast_slice(&unit_quad_vertices),
usage: wgpu::BufferUsages::VERTEX,
});
let quad_shader = renderer.create_shader(
Some("Overlay Quad Shader"),
include_str!("../../shaders/quad_instanced.wgsl"),
);
let text_shader = renderer.create_shader(
Some("Overlay Text Shader"),
include_str!("../../shaders/text_instanced.wgsl"),
);
let projection_buffer = context.device().create_buffer(&wgpu::BufferDescriptor {
label: Some("Overlay Projection Buffer"),
size: 64,
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
});
let projection_bind_group_layout = renderer.create_bind_group_layout(
Some("Overlay Projection BGL"),
&[wgpu::BindGroupLayoutEntry {
binding: 0,
visibility: wgpu::ShaderStages::VERTEX,
ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Uniform,
has_dynamic_offset: false,
min_binding_size: None,
},
count: None,
}],
);
let text_atlas_bind_group_layout = renderer.create_bind_group_layout(
Some("Overlay Text Atlas BGL"),
&[
wgpu::BindGroupLayoutEntry {
binding: 0,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Texture {
sample_type: wgpu::TextureSampleType::Float { filterable: true },
view_dimension: wgpu::TextureViewDimension::D2,
multisampled: false,
},
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 1,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
count: None,
},
],
);
let projection_bind_group = renderer.create_bind_group(
Some("Overlay Projection BG"),
&projection_bind_group_layout,
&[wgpu::BindGroupEntry {
binding: 0,
resource: projection_buffer.as_entire_binding(),
}],
);
let text_atlas_bind_group = renderer.create_bind_group(
Some("Overlay Text Atlas BG"),
&text_atlas_bind_group_layout,
&[
wgpu::BindGroupEntry {
binding: 0,
resource: wgpu::BindingResource::TextureView(
font_renderer.atlas_texture_view(),
),
},
wgpu::BindGroupEntry {
binding: 1,
resource: wgpu::BindingResource::Sampler(font_renderer.atlas_sampler()),
},
],
);
let text_projection_bind_group = renderer.create_bind_group(
Some("Overlay Text Projection BG"),
&projection_bind_group_layout,
&[wgpu::BindGroupEntry {
binding: 0,
resource: projection_buffer.as_entire_binding(),
}],
);
let quad_layout = renderer.create_pipeline_layout(
Some("Overlay Quad Pipeline Layout"),
&[&projection_bind_group_layout],
&[],
);
let quad_pipeline = renderer.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
label: Some("Overlay Quad Pipeline"),
layout: Some(&quad_layout),
vertex: wgpu::VertexState {
module: &quad_shader,
entry_point: Some("vs_main"),
buffers: &[QuadVertex::vertex_layout(), QuadInstance::vertex_layout()],
compilation_options: wgpu::PipelineCompilationOptions::default(),
},
fragment: Some(wgpu::FragmentState {
module: &quad_shader,
entry_point: Some("fs_main"),
targets: &[Some(wgpu::ColorTargetState {
format: wgpu::TextureFormat::Bgra8UnormSrgb,
blend: Some(wgpu::BlendState::ALPHA_BLENDING),
write_mask: wgpu::ColorWrites::ALL,
})],
compilation_options: wgpu::PipelineCompilationOptions::default(),
}),
primitive: wgpu::PrimitiveState {
topology: wgpu::PrimitiveTopology::TriangleList,
strip_index_format: None,
front_face: wgpu::FrontFace::Ccw,
cull_mode: None,
polygon_mode: wgpu::PolygonMode::Fill,
unclipped_depth: false,
conservative: false,
},
depth_stencil: None,
multisample: wgpu::MultisampleState {
count: 1,
mask: !0,
alpha_to_coverage_enabled: false,
},
multiview: None,
cache: None,
});
let text_layout = renderer.create_pipeline_layout(
Some("Overlay Text Pipeline Layout"),
&[&text_atlas_bind_group_layout, &projection_bind_group_layout],
&[],
);
let text_pipeline_gpu = renderer.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
label: Some("Overlay Text Pipeline"),
layout: Some(&text_layout),
vertex: wgpu::VertexState {
module: &text_shader,
entry_point: Some("vs_main"),
buffers: &[QuadVertex::vertex_layout(), TextInstance::vertex_layout()],
compilation_options: wgpu::PipelineCompilationOptions::default(),
},
fragment: Some(wgpu::FragmentState {
module: &text_shader,
entry_point: Some("fs_main"),
targets: &[Some(wgpu::ColorTargetState {
format: wgpu::TextureFormat::Bgra8UnormSrgb,
blend: Some(wgpu::BlendState::ALPHA_BLENDING),
write_mask: wgpu::ColorWrites::ALL,
})],
compilation_options: wgpu::PipelineCompilationOptions::default(),
}),
primitive: wgpu::PrimitiveState {
topology: wgpu::PrimitiveTopology::TriangleList,
strip_index_format: None,
front_face: wgpu::FrontFace::Ccw,
cull_mode: None,
polygon_mode: wgpu::PolygonMode::Fill,
unclipped_depth: false,
conservative: false,
},
depth_stencil: None,
multisample: wgpu::MultisampleState {
count: 1,
mask: !0,
alpha_to_coverage_enabled: false,
},
multiview: None,
cache: None,
});
let quad_instances =
InstanceBuffer::new(context.device(), Some("Overlay Quad Instances"), 256);
let text_instances =
InstanceBuffer::new(context.device(), Some("Overlay Text Instances"), 1024);
Self {
context,
renderer,
font_renderer,
text_pipeline: TextPipeline::new(),
quad_pipeline,
text_pipeline_gpu,
unit_quad_vbo,
quad_instances,
text_instances,
projection_buffer,
projection_bind_group,
text_atlas_bind_group,
text_projection_bind_group,
scale_factor: 1.0,
}
}
pub fn set_viewport(&mut self, viewport: Viewport) {
if (self.scale_factor - viewport.scale_factor.0).abs() > f64::EPSILON {
self.text_pipeline.clear_cache();
}
self.scale_factor = viewport.scale_factor.0;
self.font_renderer.set_viewport(viewport);
}
pub fn render(
&mut self,
draw_list: &OverlayDrawList,
render_pass: &mut wgpu::RenderPass,
viewport: Viewport,
) {
if draw_list.is_empty() {
return;
}
let logical = viewport.to_logical();
let projection = orthographic_projection(logical.width, logical.height);
self.renderer.queue().write_buffer(
&self.projection_buffer,
0,
bytemuck::cast_slice(&projection),
);
let mut quad_instances = Vec::new();
let mut text_instances = Vec::new();
const OVERLAY_Z_DEPTH: f32 = 1.0;
for cmd in draw_list.commands() {
match cmd {
OverlayCommand::Quad(q) => {
quad_instances.push(QuadInstance {
position: [q.position.x, q.position.y],
size: [q.size.x, q.size.y],
color: [
q.fill_color.r,
q.fill_color.g,
q.fill_color.b,
q.fill_color.a,
],
border_radius: q.border_radius,
border_thickness: 0.0,
z_depth: OVERLAY_Z_DEPTH,
_padding: 0.0,
});
if let Some(border_color) = q.border_color
&& q.border_width > 0.0
{
quad_instances.push(QuadInstance {
position: [q.position.x, q.position.y],
size: [q.size.x, q.size.y],
color: [
border_color.r,
border_color.g,
border_color.b,
border_color.a,
],
border_radius: q.border_radius,
border_thickness: q.border_width,
z_depth: OVERLAY_Z_DEPTH,
_padding: 0.0,
});
}
}
OverlayCommand::Text(t) => {
let shaped = {
let font_system = self.font_renderer.font_system();
let mut font_sys = font_system.write().unwrap();
shape_text(
&mut font_sys,
&t.text,
t.size,
None,
self.scale_factor as f32,
)
};
let instances = glyphs_to_instances(
&mut self.font_renderer,
&shaped.glyphs,
t.position,
t.color,
OVERLAY_Z_DEPTH,
);
text_instances.extend(instances);
}
OverlayCommand::Line(l) => {
let delta = l.end - l.start;
let length = delta.length();
if length < 0.001 {
continue;
}
let center = (l.start + l.end) * 0.5;
let _angle = delta.y.atan2(delta.x);
let min_x = l.start.x.min(l.end.x);
let min_y = l.start.y.min(l.end.y);
let max_x = l.start.x.max(l.end.x);
let max_y = l.start.y.max(l.end.y);
let width = (max_x - min_x).max(l.thickness);
let height = (max_y - min_y).max(l.thickness);
quad_instances.push(QuadInstance {
position: [center.x - width * 0.5, center.y - height * 0.5],
size: [width, height],
color: [l.color.r, l.color.g, l.color.b, l.color.a],
border_radius: 0.0,
border_thickness: 0.0,
z_depth: OVERLAY_Z_DEPTH,
_padding: 0.0,
});
}
}
}
self.quad_instances
.set_instances(self.renderer.device(), quad_instances);
self.text_instances
.set_instances(self.renderer.device(), text_instances);
self.quad_instances.upload_dirty(self.renderer.queue());
self.text_instances.upload_dirty(self.renderer.queue());
self.font_renderer.upload_atlas_if_dirty();
if !self.quad_instances.is_empty() {
render_pass.set_pipeline(&self.quad_pipeline);
render_pass.set_bind_group(0, &self.projection_bind_group, &[]);
render_pass.set_vertex_buffer(0, self.unit_quad_vbo.slice(..));
render_pass.set_vertex_buffer(1, self.quad_instances.buffer().slice(..));
render_pass.draw(0..6, 0..self.quad_instances.len() as u32);
}
if !self.text_instances.is_empty() {
render_pass.set_pipeline(&self.text_pipeline_gpu);
render_pass.set_bind_group(0, &self.text_atlas_bind_group, &[]);
render_pass.set_bind_group(1, &self.text_projection_bind_group, &[]);
render_pass.set_vertex_buffer(0, self.unit_quad_vbo.slice(..));
render_pass.set_vertex_buffer(1, self.text_instances.buffer().slice(..));
render_pass.draw(0..6, 0..self.text_instances.len() as u32);
}
}
pub fn font_renderer(&self) -> &FontRenderer {
&self.font_renderer
}
}
fn orthographic_projection(width: f32, height: f32) -> [[f32; 4]; 4] {
[
[2.0 / width, 0.0, 0.0, 0.0],
[0.0, -2.0 / height, 0.0, 0.0],
[0.0, 0.0, 1.0, 0.0],
[-1.0, 1.0, 0.0, 1.0],
]
}