use std::vec;
use log::warn;
use wgpu::BufferUsages;
use wgpu::Operations;
use wgpu::RenderPassColorAttachment;
use wgpu::RenderPassDescriptor;
use wgpu::ShaderModule;
use wgpu::ShaderModuleDescriptor;
use crate::modules::renderer::SdrSurfaceRenderer;
use crate::Dependencies;
use crate::elements::texture::rgba_bind_group_layout;
use crate::elements::BindableTexture;
use crate::elements::GrowableBuffer;
use crate::modules::ui::board::BoardPhase;
use crate::modules::Arenas;
use crate::modules::GraphicsContext;
use crate::modules::MainScreenSize;
use crate::modules::Prepare;
use crate::modules::Renderer;
use crate::utils::watcher::ShaderFileWatcher;
use crate::utils::Timing;
use crate::Handle;
use crate::Module;
use super::batching::get_batches;
use super::batching::BatchRegion;
use super::batching::BatchingResult;
use super::batching::GlyphRaw;
use super::batching::RectRaw;
use super::batching::RectRawTextured;
use super::board::Board;
use super::font_cache::FontCache;
pub struct UiRenderer {
shader_watcher: Option<ShaderFileWatcher>,
glyph_pipeline: wgpu::RenderPipeline,
rect_pipeline: wgpu::RenderPipeline,
textured_rect_pipeline: wgpu::RenderPipeline,
collected_batches: BatchingResult,
draw_batches: Vec<BatchRegion>,
rect_buffer: GrowableBuffer<RectRaw>,
textured_rect_buffer: GrowableBuffer<RectRawTextured>,
glyph_buffer: GrowableBuffer<GlyphRaw>,
deps: Deps,
}
#[derive(Debug, Dependencies)]
pub struct Deps {
renderer: Handle<Renderer>,
fonts: Handle<FontCache>,
arenas: Handle<Arenas>,
ctx: Handle<GraphicsContext>,
main_screen: Handle<MainScreenSize>,
}
impl Module for UiRenderer {
type Config = ();
type Dependencies = Deps;
fn new(_config: Self::Config, deps: Self::Dependencies) -> anyhow::Result<Self> {
let device = &deps.ctx.device;
let rect_buffer = GrowableBuffer::new(device, 256, BufferUsages::VERTEX);
let glyph_buffer = GrowableBuffer::new(device, 512, BufferUsages::VERTEX);
let textured_rect_buffer = GrowableBuffer::new(device, 256, BufferUsages::VERTEX);
let shader_watcher = None;
let shader_module = deps
.ctx
.device
.create_shader_module(ShaderModuleDescriptor {
label: Some("Ui Renderer Shaders"),
source: wgpu::ShaderSource::Wgsl(include_str!("ui.wgsl").into()),
});
let glyph_pipeline = create_glyph_pipeline(&shader_module, &deps);
let rect_pipeline = create_rect_pipeline(&shader_module, &deps);
let textured_rect_pipeline = create_textured_rect_pipeline(&shader_module, &deps);
Ok(UiRenderer {
shader_watcher,
glyph_pipeline,
rect_pipeline,
textured_rect_pipeline,
collected_batches: BatchingResult::new(),
draw_batches: vec![],
rect_buffer,
textured_rect_buffer,
glyph_buffer,
deps,
})
}
fn intialize(handle: Handle<Self>) -> anyhow::Result<()> {
let mut renderer = handle.deps.renderer;
renderer.register_prepare(handle);
renderer.register_surface_renderer(handle, Timing::DEFAULT);
Ok(())
}
}
impl UiRenderer {
pub fn watch_shader_file(&mut self, path: &str) {
self.shader_watcher = Some(ShaderFileWatcher::new(path));
}
pub fn draw_ui_board(&mut self, board: &Board) {
assert_eq!(board.phase(), BoardPhase::Rendering);
let batches = get_batches(board);
self.collected_batches.combine(batches);
}
}
impl Prepare for UiRenderer {
fn prepare(
&mut self,
device: &wgpu::Device,
queue: &wgpu::Queue,
_encoder: &mut wgpu::CommandEncoder,
) {
if let Some(watcher) = &self.shader_watcher {
if let Some(new_wgsl) = watcher.check_for_changes() {
let shader_module =
self.deps
.ctx
.device
.create_shader_module(ShaderModuleDescriptor {
label: Some("Ui Renderer Shaders"),
source: wgpu::ShaderSource::Wgsl(new_wgsl.into()),
});
self.glyph_pipeline = create_glyph_pipeline(&shader_module, &self.deps);
self.rect_pipeline = create_rect_pipeline(&shader_module, &self.deps);
self.textured_rect_pipeline =
create_textured_rect_pipeline(&shader_module, &self.deps);
}
}
self.rect_buffer
.prepare(&self.collected_batches.rects, device, queue);
self.textured_rect_buffer
.prepare(&self.collected_batches.textured_rects, device, queue);
self.glyph_buffer
.prepare(&self.collected_batches.glyphs, device, queue);
self.draw_batches.clear();
std::mem::swap(&mut self.draw_batches, &mut self.collected_batches.batches);
self.collected_batches.glyphs.clear();
self.collected_batches.rects.clear();
self.collected_batches.textured_rects.clear();
}
}
impl SdrSurfaceRenderer for UiRenderer {
fn render<'e>(&'e self, encoder: &'e mut wgpu::CommandEncoder, view: &wgpu::TextureView) {
let mut render_pass = encoder.begin_render_pass(&RenderPassDescriptor {
label: Some("Ui Render Pass"),
color_attachments: &[Some(RenderPassColorAttachment {
view,
resolve_target: None,
ops: Operations {
load: wgpu::LoadOp::Load,
store: wgpu::StoreOp::Store,
},
})],
depth_stencil_attachment: None,
timestamp_writes: None,
occlusion_query_set: None,
});
assert!(self.collected_batches.is_empty()); render_pass.set_bind_group(0, self.deps.main_screen.bind_group(), &[]);
const VERTEX_COUNT: u32 = 6;
let atlas_texture = self.deps.fonts.atlas_texture();
let textures = self.deps.arenas.arena::<BindableTexture>();
let atlas_texture = textures.get(atlas_texture).unwrap();
for batch in self.draw_batches.iter() {
match batch {
BatchRegion::Rect(r) => {
render_pass.set_pipeline(&self.rect_pipeline);
render_pass.set_vertex_buffer(0, self.rect_buffer.buffer().slice(..));
render_pass.draw(0..VERTEX_COUNT, r.start as u32..r.end as u32);
}
BatchRegion::Text(r, _font) => {
render_pass.set_bind_group(1, &atlas_texture.bind_group, &[]);
render_pass.set_pipeline(&self.glyph_pipeline);
render_pass.set_vertex_buffer(0, self.glyph_buffer.buffer().slice(..));
render_pass.draw(0..VERTEX_COUNT, r.start as u32..r.end as u32);
}
BatchRegion::TexturedRect(r, texture) => {
if let Some(texture) = textures.get(*texture) {
render_pass.set_bind_group(1, &texture.bind_group, &[]);
render_pass.set_pipeline(&self.textured_rect_pipeline);
render_pass
.set_vertex_buffer(0, self.textured_rect_buffer.buffer().slice(..));
render_pass.draw(0..VERTEX_COUNT, r.start as u32..r.end as u32);
} else {
warn!("Texture not found for key {texture} in a ui rendering batch (textured rect)")
}
}
}
}
}
}
use crate::modules::VertexT;
fn create_rect_pipeline(shader_module: &ShaderModule, deps: &Deps) -> wgpu::RenderPipeline {
let device = &deps.ctx.device;
create_pipeline::<RectRaw>(
shader_module,
"rect_vs",
"rect_fs",
"Rect",
device,
&[deps.main_screen.bind_group_layout()],
)
}
fn create_textured_rect_pipeline(
shader_module: &ShaderModule,
deps: &Deps,
) -> wgpu::RenderPipeline {
let device = &deps.ctx.device;
create_pipeline::<RectRawTextured>(
shader_module,
"textured_rect_vs",
"textured_rect_fs",
"Textured Rect",
device,
&[
deps.main_screen.bind_group_layout(),
rgba_bind_group_layout(device),
],
)
}
fn create_glyph_pipeline(shader_module: &ShaderModule, deps: &Deps) -> wgpu::RenderPipeline {
let device = &deps.ctx.device;
create_pipeline::<GlyphRaw>(
shader_module,
"glyph_vs",
"glyph_fs",
"Glyph",
device,
&[
deps.main_screen.bind_group_layout(),
rgba_bind_group_layout(device),
],
)
}
fn create_pipeline<I: VertexT>(
shader_module: &ShaderModule,
vertex_entry: &str,
fragment_entry: &str,
label: &str,
device: &wgpu::Device,
bind_group_layouts: &[&wgpu::BindGroupLayout],
) -> wgpu::RenderPipeline {
let _empty = &mut vec![];
let vertex_buffers_layout = &[I::vertex_buffer_layout(0, true, _empty)];
let layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: Some(&format!("{label} PipelineLayout")),
bind_group_layouts,
push_constant_ranges: &[],
});
device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
label: Some(&format!("{label} Pipeline")),
layout: Some(&layout),
vertex: wgpu::VertexState {
module: shader_module,
entry_point: vertex_entry,
buffers: vertex_buffers_layout,
},
fragment: Some(wgpu::FragmentState {
module: shader_module,
entry_point: fragment_entry,
targets: &[Some(wgpu::ColorTargetState {
format: wgpu::TextureFormat::Bgra8UnormSrgb,
blend: Some(wgpu::BlendState::ALPHA_BLENDING),
write_mask: wgpu::ColorWrites::ALL,
})],
}),
primitive: Default::default(), depth_stencil: None,
multisample: wgpu::MultisampleState {
alpha_to_coverage_enabled: false,
count: 1,
mask: !0,
},
multiview: None,
})
}