#![allow(dead_code)]
use std::collections::HashMap;
use crate::{
components::{FacesGPU, ModelMatrix, Name, NormalsGPU, Renderable, VertsGPU, VisMesh, VisOutline},
config::RenderConfig,
forward_renderer::{bind_group_collection::BindGroupCollection, locals::LocalEntData},
scene::Scene,
selector::Selectable,
};
use easy_wgpu::{
bind_group::{BindGroupBuilder, BindGroupWrapper},
bind_group_layout::{BindGroupLayoutBuilder, BindGroupLayoutDesc},
buffer::Buffer,
};
use easy_wgpu::{gpu::Gpu, utils::create_empty_group};
use gloss_hecs::Entity;
use super::{pipeline_runner::PipelineRunner, upload_pass::PerFrameUniforms};
use easy_wgpu::pipeline::RenderPipelineDescBuilder;
use encase;
use gloss_utils::numerical::align;
#[include_wgsl_oil::include_wgsl_oil("../../../shaders/outline.wgsl")]
mod shader_code {}
pub struct OutlinePass {
render_pipeline: wgpu::RenderPipeline,
_empty_group: wgpu::BindGroup,
locals_uniform: Buffer,
locals_bind_groups: LocalsBindGroups,
}
impl OutlinePass {
pub fn new(gpu: &Gpu, params: &RenderConfig, color_target_format: wgpu::TextureFormat, depth_target_format: wgpu::TextureFormat) -> Self {
const_assert!(std::mem::size_of::<Locals>() % 16 == 0);
let render_pipeline = RenderPipelineDescBuilder::new()
.label("outline_pass")
.shader_code(shader_code::SOURCE)
.shader_label("outline_shader")
.add_bind_group_layout_desc(PerFrameUniforms::build_layout_desc())
.add_bind_group_layout_desc(LocalsBindGroups::build_layout_desc())
.add_vertex_buffer_layout(VertsGPU::vertex_buffer_layout::<0>())
.add_vertex_buffer_layout(NormalsGPU::vertex_buffer_layout::<1>())
.add_render_target(wgpu::ColorTargetState {
format: color_target_format,
blend: None,
write_mask: wgpu::ColorWrites::ALL,
})
.depth_state(Some(wgpu::DepthStencilState {
format: depth_target_format,
depth_write_enabled: false,
depth_compare: wgpu::CompareFunction::Always, stencil: wgpu::StencilState {
front: wgpu::StencilFaceState {
compare: wgpu::CompareFunction::NotEqual, fail_op: wgpu::StencilOperation::Keep,
depth_fail_op: wgpu::StencilOperation::Keep,
pass_op: wgpu::StencilOperation::Keep,
},
back: wgpu::StencilFaceState {
compare: wgpu::CompareFunction::NotEqual, fail_op: wgpu::StencilOperation::Keep,
depth_fail_op: wgpu::StencilOperation::Keep,
pass_op: wgpu::StencilOperation::Keep,
},
read_mask: 0xFF,
write_mask: 0x00, },
bias: wgpu::DepthBiasState::default(),
}))
.multisample(wgpu::MultisampleState {
count: params.msaa_nr_samples,
..Default::default()
})
.build_pipeline(gpu.device());
let empty_group = create_empty_group(gpu.device());
let size_bytes = 0x10000;
let usage = wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::UNIFORM;
let locals_uniform = Buffer::new_empty(gpu.device(), usage, Some("local_buffer"), size_bytes);
let locals_bind_groups = LocalsBindGroups::new(gpu);
Self {
render_pipeline,
_empty_group: empty_group,
locals_uniform,
locals_bind_groups,
}
}
}
impl PipelineRunner for OutlinePass {
type QueryItems<'a> = (
&'a VertsGPU,
&'a FacesGPU,
&'a NormalsGPU,
&'a VisMesh,
&'a Name,
&'a ModelMatrix,
&'a VisOutline,
);
type QueryState<'a> = gloss_hecs::QueryBorrow<'a, gloss_hecs::With<Self::QueryItems<'a>, (&'a Renderable, &'a Selectable)>>;
fn query_state(scene: &Scene) -> Self::QueryState<'_> {
scene.world().query::<Self::QueryItems<'_>>().with::<(&Renderable, &Selectable)>()
}
fn prepare<'a>(&mut self, gpu: &Gpu, _per_frame_uniforms: &PerFrameUniforms, scene: &'a Scene) -> Self::QueryState<'a> {
self.begin_pass();
self.update_locals(gpu, scene);
Self::query_state(scene)
}
#[allow(clippy::too_many_lines)]
fn run<'r>(
&'r mut self,
render_pass: &mut wgpu::RenderPass<'r>,
per_frame_uniforms: &'r PerFrameUniforms,
_render_params: &RenderConfig,
query_state: &'r mut Self::QueryState<'_>,
_scene: &Scene,
) {
if query_state.iter().count() == 0 {
return;
}
render_pass.set_pipeline(&self.render_pipeline);
render_pass.set_bind_group(0, &per_frame_uniforms.bind_group, &[]);
render_pass.set_stencil_reference(1);
for (_id, (verts, faces, normals, vis_mesh, name, _model_matrix, vis_outline)) in query_state.iter() {
if !vis_mesh.show_mesh || !vis_outline.show_outline {
continue;
}
let (local_bg, offset) = &self.locals_bind_groups.mesh2local_bind[&name.0.clone()];
render_pass.set_bind_group(1, local_bg.bg(), &[*offset]);
render_pass.set_vertex_buffer(0, verts.buf.buffer.slice(..));
render_pass.set_vertex_buffer(1, normals.buf.buffer.slice(..));
render_pass.set_index_buffer(faces.buf.buffer.slice(..), wgpu::IndexFormat::Uint32);
render_pass.draw_indexed(0..faces.nr_triangles * 3, 0, 0..1);
}
}
fn begin_pass(&mut self) {}
fn update_locals(&mut self, gpu: &Gpu, scene: &Scene) {
Self::update_locals_inner::<Locals, _>(
gpu,
scene,
&mut self.locals_uniform,
&mut self.locals_bind_groups,
&mut Self::query_state(scene),
);
}
}
#[repr(C)]
#[derive(Clone, Copy, encase::ShaderType)]
struct Locals {
model_matrix: nalgebra::Matrix4<f32>,
outline_color: nalgebra::Vector4<f32>,
outline_width: f32,
is_floor: u32,
pad_c: f32,
pad_d: f32,
}
impl LocalEntData for Locals {
fn new(entity: Entity, scene: &Scene) -> Self {
let model_matrix = scene.get_comp::<&ModelMatrix>(&entity).unwrap().0.to_homogeneous();
let Ok(vis_outline) = scene.get_comp::<&VisOutline>(&entity) else {
let default_outline = VisOutline::default();
return Locals {
model_matrix,
outline_color: default_outline.outline_color,
outline_width: 0.0,
is_floor: 0,
pad_c: 0.0,
pad_d: 0.0,
};
};
let is_floor = if let Some(floor) = scene.get_floor() {
floor.entity == entity
} else {
false
};
let is_floor = u32::from(is_floor);
let scale_factor = 1.0;
Locals {
model_matrix,
outline_color: vis_outline.outline_color,
outline_width: vis_outline.outline_width * scale_factor,
is_floor,
pad_c: 0.0,
pad_d: 0.0,
}
}
}
struct LocalsBindGroups {
layout: wgpu::BindGroupLayout,
pub mesh2local_bind: HashMap<String, (BindGroupWrapper, u32)>,
}
impl BindGroupCollection for LocalsBindGroups {
fn new(gpu: &Gpu) -> Self {
Self {
layout: Self::build_layout_desc().into_bind_group_layout(gpu.device()),
mesh2local_bind: HashMap::default(),
}
}
fn build_layout_desc() -> BindGroupLayoutDesc {
BindGroupLayoutBuilder::new()
.label("gbuffer_pass_locals_layout")
.add_entry_uniform(
wgpu::ShaderStages::VERTEX | wgpu::ShaderStages::FRAGMENT,
true,
wgpu::BufferSize::new(u64::from(align(u32::try_from(std::mem::size_of::<Locals>()).unwrap(), 256))),
)
.build()
}
fn update_bind_group(&mut self, _entity: Entity, gpu: &Gpu, mesh_name: &str, ubo: &Buffer, offset_in_ubo: u32, _scene: &Scene) {
let entries = BindGroupBuilder::new().add_entry_buf_chunk::<Locals>(&ubo.buffer).build_entries();
self.update_if_stale(mesh_name, entries, offset_in_ubo, gpu);
}
fn get_layout(&self) -> &wgpu::BindGroupLayout {
&self.layout
}
fn get_mut_entity2binds(&mut self) -> &mut HashMap<String, (BindGroupWrapper, u32)> {
&mut self.mesh2local_bind
}
}