use bytemuck::{Pod, Zeroable};
use glam::Vec3;
use crate::ecs::{Application, Component, Entity, Plugin, Query, ResMut, Resource, World};
use crate::render3d::DEPTH_FORMAT;
use crate::scene::SceneEntity;
use crate::scene::Spawn;
use crate::sceneobjects::lights::{Light, LightShape, LocalLight, Suns};
use crate::sceneobjects::{Category, SceneObject};
use crate::ui::icons::path;
use crate::ui::{Color, Mix, linear_rgba};
pub use crate::helpers::grid::{Grid, grid};
#[derive(Component, Clone, Debug)]
pub struct Wires {
pub segments: Vec<[Vec3; 2]>,
pub color: Color,
pub shaded: Vec<([Vec3; 2], [Color; 2])>,
pub faces: Vec<([Vec3; 3], Color)>,
pub images: Vec<Quad>,
}
#[derive(Clone, Copy, Debug)]
pub struct Quad {
pub corners: [Vec3; 4],
pub uv: [f32; 4],
pub tint: Color,
}
impl Wires {
pub fn new(color: Color) -> Self {
Self {
segments: Vec::new(),
color,
shaded: Vec::new(),
faces: Vec::new(),
images: Vec::new(),
}
}
pub fn image(&mut self, corners: [Vec3; 4], uv: [f32; 4], tint: Color) -> &mut Self {
self.images.push(Quad { corners, uv, tint });
self
}
pub fn triangle(&mut self, corners: [Vec3; 3], color: Color) -> &mut Self {
self.faces.push((corners, color));
self
}
pub fn quad(&mut self, corners: [Vec3; 4], color: Color) -> &mut Self {
let [a, b, c, d] = corners;
self.triangle([a, b, c], color).triangle([a, c, d], color)
}
pub fn gradient(&mut self, from: Vec3, to: Vec3, colors: [Color; 2]) -> &mut Self {
self.shaded.push(([from, to], colors));
self
}
pub fn dashed_gradient(
&mut self,
from: Vec3,
to: Vec3,
dashes: usize,
colors: [Color; 2],
) -> &mut Self {
for i in 0..dashes {
let start = i as f32 / dashes as f32;
let end = start + 0.55 / dashes as f32;
self.gradient(
from.lerp(to, start),
from.lerp(to, end),
[
colors[0].mix(&colors[1], start),
colors[0].mix(&colors[1], end),
],
);
}
self
}
pub fn line(&mut self, from: Vec3, to: Vec3) -> &mut Self {
self.segments.push([from, to]);
self
}
pub fn ring(&mut self, middle: Vec3, across: Vec3, down: Vec3, sides: usize) -> &mut Self {
let point = |i: usize| {
let angle = i as f32 / sides as f32 * std::f32::consts::TAU;
middle + across * angle.cos() + down * angle.sin()
};
for i in 0..sides {
self.line(point(i), point((i + 1) % sides));
}
self
}
pub fn dashed(&mut self, from: Vec3, to: Vec3, dashes: usize) -> &mut Self {
for i in 0..dashes {
let start = i as f32 / dashes as f32;
let end = start + 0.55 / dashes as f32;
self.line(from.lerp(to, start), from.lerp(to, end));
}
self
}
}
impl Spawn for Wires {
type Output = Entity;
fn spawn(self, world: &mut World) -> Entity {
world
.spawn((
self,
SceneEntity,
SceneObject::new("Wires", path::SHAPES, Category::Utilities),
))
.id()
}
}
const RING_SIDES: usize = 24;
const RAYS: usize = 8;
const RAY_INNER: f32 = 1.35;
const RAY_OUTER: f32 = 2.1;
pub fn light(light: &Light, focus: Vec3, distance: f32) -> Wires {
let towards = light.direction.normalize_or(Vec3::Y);
let tint = light.color();
let mut wires = Wires::new(Color::linear_rgba(tint.x, tint.y, tint.z, 1.0));
let any = match towards.y.abs() > 0.99 {
true => Vec3::Z,
false => Vec3::Y,
};
let across = towards.cross(any).normalize_or(Vec3::X);
let down = towards.cross(across).normalize_or(Vec3::Z);
let at = focus + towards * distance;
let radius = distance * 0.16;
wires.ring(at, across * radius, down * radius, RING_SIDES);
for i in 0..RAYS {
let angle = i as f32 / RAYS as f32 * std::f32::consts::TAU;
let out = across * angle.cos() + down * angle.sin();
wires.line(at + out * radius * RAY_INNER, at + out * radius * RAY_OUTER);
}
wires.dashed(at - towards * radius * RAY_INNER, focus, 6);
wires
}
pub fn lights(suns: &Suns, focus: Vec3, distance: f32) -> Vec<Wires> {
[suns.shadowed, suns.unshadowed]
.into_iter()
.flatten()
.map(|sun| light(&sun, focus, distance))
.collect()
}
pub fn local_light(light: &LocalLight) -> Wires {
let tint = light.color();
let tint = tint / tint.max_element().max(1.0e-4);
let mut wires = Wires::new(Color::linear_rgba(tint.x, tint.y, tint.z, 1.0));
let at = light.position;
const RADIUS: f32 = 0.22;
let towards = match light.shape {
LightShape::Point => Vec3::NEG_Y,
LightShape::Spot { direction, .. } => direction.normalize_or(Vec3::NEG_Y),
};
let any = match towards.y.abs() > 0.99 {
true => Vec3::Z,
false => Vec3::Y,
};
let across = towards.cross(any).normalize_or(Vec3::X);
let down = towards.cross(across).normalize_or(Vec3::Z);
match light.shape {
LightShape::Point => {
wires.ring(at, across * RADIUS, down * RADIUS, RING_SIDES);
wires.ring(at, across * RADIUS, towards * RADIUS, RING_SIDES);
wires.ring(at, down * RADIUS, towards * RADIUS, RING_SIDES);
}
LightShape::Spot { .. } => {
wires.ring(at, across * RADIUS, down * RADIUS, RING_SIDES);
wires.dashed(at + towards * RADIUS, at + towards * RADIUS * 5.0, 4);
}
}
for i in 0..RAYS {
let angle = i as f32 / RAYS as f32 * std::f32::consts::TAU;
let out = across * angle.cos() + down * angle.sin();
wires.line(at + out * RADIUS * RAY_INNER, at + out * RADIUS * RAY_OUTER);
}
wires
}
#[derive(Resource, Default)]
pub struct WireDrawList {
pub lines: Vec<WireVertex>,
pub faces: Vec<WireVertex>,
pub images: Vec<ImageVertex>,
pub uploads: Vec<Upload>,
}
pub const ATLAS_SIDE: u32 = 4096;
#[derive(Clone, Debug)]
pub struct Upload {
pub x: u32,
pub y: u32,
pub width: u32,
pub height: u32,
pub rgba: Vec<u8>,
}
#[repr(C)]
#[derive(Clone, Copy, Pod, Zeroable)]
pub struct ImageVertex {
position: [f32; 3],
uv: [f32; 2],
tint: [f32; 4],
}
#[repr(C)]
#[derive(Clone, Copy, Pod, Zeroable)]
pub struct WireVertex {
position: [f32; 3],
color: [f32; 4],
}
pub fn collect_wires_system(mut list: ResMut<WireDrawList>, wires: Query<&Wires>) {
list.lines.clear();
list.faces.clear();
list.images.clear();
for drawing in &wires {
for quad in &drawing.images {
let tint = linear_rgba(quad.tint);
let [u0, v0, u1, v1] = quad.uv;
let uvs = [[u0, v0], [u1, v0], [u1, v1], [u0, v1]];
for i in [0, 1, 2, 0, 2, 3] {
list.images.push(ImageVertex {
position: quad.corners[i].to_array(),
uv: uvs[i],
tint,
});
}
}
let color = linear_rgba(drawing.color);
for [from, to] in &drawing.segments {
list.lines.push(WireVertex {
position: from.to_array(),
color,
});
list.lines.push(WireVertex {
position: to.to_array(),
color,
});
}
for ([from, to], [start, end]) in &drawing.shaded {
list.lines.push(WireVertex {
position: from.to_array(),
color: linear_rgba(*start),
});
list.lines.push(WireVertex {
position: to.to_array(),
color: linear_rgba(*end),
});
}
for (corners, color) in &drawing.faces {
let color = linear_rgba(*color);
for corner in corners {
list.faces.push(WireVertex {
position: corner.to_array(),
color,
});
}
}
}
}
#[derive(Resource, Clone, Copy, Default, Debug)]
pub struct ActiveGrid(pub Option<Grid>);
pub fn collect_grid_system(mut active: ResMut<ActiveGrid>, grids: Query<&Grid>) {
active.0 = grids.iter().next().copied();
}
pub struct WireRenderer {
pipeline: wgpu::RenderPipeline,
fill_pipeline: wgpu::RenderPipeline,
image_pipeline: wgpu::RenderPipeline,
uniform_buffer: wgpu::Buffer,
bind_group: wgpu::BindGroup,
atlas: wgpu::Texture,
atlas_bind_group: wgpu::BindGroup,
vertices: Growing,
face_vertices: Growing,
image_vertices: Growing,
}
struct Growing {
buffer: wgpu::Buffer,
capacity: usize,
stride: usize,
label: &'static str,
}
impl Growing {
fn new(device: &wgpu::Device, label: &'static str, capacity: usize, stride: usize) -> Self {
let buffer = device.create_buffer(&wgpu::BufferDescriptor {
label: Some(label),
size: (capacity * stride) as u64,
usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
});
Self {
buffer,
capacity,
stride,
label,
}
}
fn write<T: Pod>(&mut self, device: &wgpu::Device, queue: &wgpu::Queue, vertices: &[T]) {
if vertices.len() > self.capacity {
*self = Self::new(
device,
self.label,
vertices.len().next_power_of_two(),
self.stride,
);
}
if !vertices.is_empty() {
queue.write_buffer(&self.buffer, 0, bytemuck::cast_slice(vertices));
}
}
}
const WIRE_CAPACITY: usize = 1024;
#[repr(C)]
#[derive(Clone, Copy, Pod, Zeroable)]
struct WireUniform {
view_proj: [f32; 16],
}
impl WireRenderer {
pub fn new(device: &wgpu::Device, format: wgpu::TextureFormat) -> Self {
let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
label: Some("wire shader"),
source: wgpu::ShaderSource::Wgsl(include_str!("wire.wgsl").into()),
});
let uniform_buffer = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("wire uniform"),
size: std::mem::size_of::<WireUniform>() as u64,
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
});
let bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label: Some("wire bind group layout"),
entries: &[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 bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some("wire bind group"),
layout: &bind_group_layout,
entries: &[wgpu::BindGroupEntry {
binding: 0,
resource: uniform_buffer.as_entire_binding(),
}],
});
let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: Some("wire pipeline layout"),
bind_group_layouts: &[Some(&bind_group_layout)],
immediate_size: 0,
});
const ATTRS: [wgpu::VertexAttribute; 2] =
wgpu::vertex_attr_array![0 => Float32x3, 1 => Float32x4];
let make = |label: &str, topology: wgpu::PrimitiveTopology| {
device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
label: Some(label),
layout: Some(&pipeline_layout),
vertex: wgpu::VertexState {
module: &shader,
entry_point: Some("vs_main"),
buffers: &[Some(wgpu::VertexBufferLayout {
array_stride: std::mem::size_of::<WireVertex>() as u64,
step_mode: wgpu::VertexStepMode::Vertex,
attributes: &ATTRS,
})],
compilation_options: wgpu::PipelineCompilationOptions::default(),
},
fragment: Some(wgpu::FragmentState {
module: &shader,
entry_point: Some("fs_main"),
targets: &[Some(wgpu::ColorTargetState {
format,
blend: Some(wgpu::BlendState::ALPHA_BLENDING),
write_mask: wgpu::ColorWrites::ALL,
})],
compilation_options: wgpu::PipelineCompilationOptions::default(),
}),
primitive: wgpu::PrimitiveState {
topology,
strip_index_format: None,
front_face: wgpu::FrontFace::Ccw,
cull_mode: None,
polygon_mode: wgpu::PolygonMode::Fill,
unclipped_depth: false,
conservative: false,
},
depth_stencil: Some(wgpu::DepthStencilState {
format: DEPTH_FORMAT,
depth_write_enabled: Some(false),
depth_compare: Some(wgpu::CompareFunction::Less),
stencil: wgpu::StencilState::default(),
bias: wgpu::DepthBiasState::default(),
}),
multisample: wgpu::MultisampleState {
count: crate::render3d::SAMPLES,
..wgpu::MultisampleState::default()
},
multiview_mask: None,
cache: None,
})
};
let pipeline = make("wire pipeline", wgpu::PrimitiveTopology::LineList);
let fill_pipeline = make("wire fill pipeline", wgpu::PrimitiveTopology::TriangleList);
let atlas = device.create_texture(&wgpu::TextureDescriptor {
label: Some("thumbnail atlas"),
size: wgpu::Extent3d {
width: ATLAS_SIDE,
height: ATLAS_SIDE,
depth_or_array_layers: 1,
},
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format: wgpu::TextureFormat::Rgba8UnormSrgb,
usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
view_formats: &[],
});
let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
label: Some("thumbnail sampler"),
mag_filter: wgpu::FilterMode::Linear,
min_filter: wgpu::FilterMode::Linear,
mipmap_filter: wgpu::MipmapFilterMode::Nearest,
..wgpu::SamplerDescriptor::default()
});
let atlas_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label: Some("thumbnail atlas layout"),
entries: &[
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 atlas_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some("thumbnail atlas bind group"),
layout: &atlas_layout,
entries: &[
wgpu::BindGroupEntry {
binding: 0,
resource: wgpu::BindingResource::TextureView(
&atlas.create_view(&wgpu::TextureViewDescriptor::default()),
),
},
wgpu::BindGroupEntry {
binding: 1,
resource: wgpu::BindingResource::Sampler(&sampler),
},
],
});
let image_shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
label: Some("image shader"),
source: wgpu::ShaderSource::Wgsl(include_str!("image.wgsl").into()),
});
let image_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: Some("image pipeline layout"),
bind_group_layouts: &[Some(&bind_group_layout), Some(&atlas_layout)],
immediate_size: 0,
});
const IMAGE_ATTRS: [wgpu::VertexAttribute; 3] =
wgpu::vertex_attr_array![0 => Float32x3, 1 => Float32x2, 2 => Float32x4];
let image_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
label: Some("image pipeline"),
layout: Some(&image_layout),
vertex: wgpu::VertexState {
module: &image_shader,
entry_point: Some("vs_main"),
buffers: &[Some(wgpu::VertexBufferLayout {
array_stride: std::mem::size_of::<ImageVertex>() as u64,
step_mode: wgpu::VertexStepMode::Vertex,
attributes: &IMAGE_ATTRS,
})],
compilation_options: wgpu::PipelineCompilationOptions::default(),
},
fragment: Some(wgpu::FragmentState {
module: &image_shader,
entry_point: Some("fs_main"),
targets: &[Some(wgpu::ColorTargetState {
format,
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: Some(wgpu::DepthStencilState {
format: DEPTH_FORMAT,
depth_write_enabled: Some(false),
depth_compare: Some(wgpu::CompareFunction::Less),
stencil: wgpu::StencilState::default(),
bias: wgpu::DepthBiasState::default(),
}),
multisample: wgpu::MultisampleState {
count: crate::render3d::SAMPLES,
..wgpu::MultisampleState::default()
},
multiview_mask: None,
cache: None,
});
let stride = std::mem::size_of::<WireVertex>();
Self {
pipeline,
fill_pipeline,
image_pipeline,
uniform_buffer,
bind_group,
atlas,
atlas_bind_group,
vertices: Growing::new(device, "wire vertices", WIRE_CAPACITY, stride),
face_vertices: Growing::new(device, "wire face vertices", WIRE_CAPACITY, stride),
image_vertices: Growing::new(
device,
"image vertices",
WIRE_CAPACITY,
std::mem::size_of::<ImageVertex>(),
),
}
}
#[allow(clippy::too_many_arguments)]
pub fn render(
&mut self,
device: &wgpu::Device,
queue: &wgpu::Queue,
encoder: &mut wgpu::CommandEncoder,
view: &wgpu::TextureView,
resolve: &wgpu::TextureView,
depth: &wgpu::TextureView,
width: u32,
height: u32,
scene: &crate::views::View,
wires: &mut WireDrawList,
) {
for upload in std::mem::take(&mut wires.uploads) {
queue.write_texture(
wgpu::TexelCopyTextureInfo {
texture: &self.atlas,
mip_level: 0,
origin: wgpu::Origin3d {
x: upload.x,
y: upload.y,
z: 0,
},
aspect: wgpu::TextureAspect::All,
},
&upload.rgba,
wgpu::TexelCopyBufferLayout {
offset: 0,
bytes_per_row: Some(4 * upload.width),
rows_per_image: Some(upload.height),
},
wgpu::Extent3d {
width: upload.width,
height: upload.height,
depth_or_array_layers: 1,
},
);
}
if wires.lines.is_empty() && wires.faces.is_empty() && wires.images.is_empty() {
return;
}
let uniform = WireUniform {
view_proj: scene.camera.view_proj(scene.aspect()).to_cols_array(),
};
queue.write_buffer(&self.uniform_buffer, 0, bytemuck::bytes_of(&uniform));
self.vertices.write(device, queue, &wires.lines);
self.face_vertices.write(device, queue, &wires.faces);
self.image_vertices.write(device, queue, &wires.images);
let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
label: Some("wire render pass"),
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
view,
resolve_target: Some(resolve),
depth_slice: None,
ops: wgpu::Operations {
load: wgpu::LoadOp::Load,
store: wgpu::StoreOp::Store,
},
})],
depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
view: depth,
depth_ops: Some(wgpu::Operations {
load: wgpu::LoadOp::Load,
store: wgpu::StoreOp::Store,
}),
stencil_ops: None,
}),
timestamp_writes: None,
occlusion_query_set: None,
multiview_mask: None,
});
crate::render3d::set_view(&mut pass, scene, (width, height));
pass.set_bind_group(0, &self.bind_group, &[]);
if !wires.faces.is_empty() {
pass.set_pipeline(&self.fill_pipeline);
pass.set_vertex_buffer(0, self.face_vertices.buffer.slice(..));
pass.draw(0..wires.faces.len() as u32, 0..1);
}
if !wires.images.is_empty() {
pass.set_pipeline(&self.image_pipeline);
pass.set_bind_group(1, &self.atlas_bind_group, &[]);
pass.set_vertex_buffer(0, self.image_vertices.buffer.slice(..));
pass.draw(0..wires.images.len() as u32, 0..1);
}
if !wires.lines.is_empty() {
pass.set_pipeline(&self.pipeline);
pass.set_vertex_buffer(0, self.vertices.buffer.slice(..));
pass.draw(0..wires.lines.len() as u32, 0..1);
}
}
}
pub struct GizmosPlugin;
impl Plugin for GizmosPlugin {
fn build(&self, app: &mut Application) {
app.insert_resource(ActiveGrid::default());
app.insert_resource(WireDrawList::default());
app.add_update_systems((collect_grid_system, collect_wires_system));
}
}
#[repr(C)]
#[derive(Clone, Copy, Pod, Zeroable)]
struct GridUniform {
view_proj: [f32; 16],
inv_view_proj: [f32; 16],
eye: [f32; 4],
params: [f32; 4],
line: [f32; 4],
major: [f32; 4],
x_axis: [f32; 4],
z_axis: [f32; 4],
}
pub struct GridRenderer {
pipeline: wgpu::RenderPipeline,
uniform_buffer: wgpu::Buffer,
bind_group: wgpu::BindGroup,
}
impl GridRenderer {
pub fn new(device: &wgpu::Device, format: wgpu::TextureFormat) -> Self {
let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
label: Some("grid shader"),
source: wgpu::ShaderSource::Wgsl(include_str!("grid.wgsl").into()),
});
let uniform_buffer = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("grid uniform"),
size: std::mem::size_of::<GridUniform>() as u64,
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
});
let bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label: Some("grid bind group layout"),
entries: &[wgpu::BindGroupLayoutEntry {
binding: 0,
visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Uniform,
has_dynamic_offset: false,
min_binding_size: None,
},
count: None,
}],
});
let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some("grid bind group"),
layout: &bind_group_layout,
entries: &[wgpu::BindGroupEntry {
binding: 0,
resource: uniform_buffer.as_entire_binding(),
}],
});
let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: Some("grid pipeline layout"),
bind_group_layouts: &[Some(&bind_group_layout)],
immediate_size: 0,
});
let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
label: Some("grid pipeline"),
layout: Some(&pipeline_layout),
vertex: wgpu::VertexState {
module: &shader,
entry_point: Some("vs_main"),
buffers: &[],
compilation_options: wgpu::PipelineCompilationOptions::default(),
},
fragment: Some(wgpu::FragmentState {
module: &shader,
entry_point: Some("fs_main"),
targets: &[Some(wgpu::ColorTargetState {
format,
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: Some(wgpu::DepthStencilState {
format: DEPTH_FORMAT,
depth_write_enabled: Some(false),
depth_compare: Some(wgpu::CompareFunction::Less),
stencil: wgpu::StencilState::default(),
bias: wgpu::DepthBiasState::default(),
}),
multisample: wgpu::MultisampleState {
count: crate::render3d::SAMPLES,
..wgpu::MultisampleState::default()
},
multiview_mask: None,
cache: None,
});
Self {
pipeline,
uniform_buffer,
bind_group,
}
}
#[allow(clippy::too_many_arguments)]
pub fn render(
&mut self,
queue: &wgpu::Queue,
encoder: &mut wgpu::CommandEncoder,
view: &wgpu::TextureView,
resolve: &wgpu::TextureView,
depth: &wgpu::TextureView,
width: u32,
height: u32,
scene: &crate::views::View,
grid: &Grid,
) {
let view_proj = scene.camera.view_proj(scene.aspect());
let uniform = GridUniform {
view_proj: view_proj.to_cols_array(),
inv_view_proj: view_proj.inverse().to_cols_array(),
eye: [
scene.camera.eye.x,
scene.camera.eye.y,
scene.camera.eye.z,
grid.height,
],
params: [
grid.spacing.max(1e-4),
grid.major_every.max(1.0),
grid.fade_from,
grid.fade_to.max(grid.fade_from + 1e-3),
],
line: linear_rgba(grid.line),
major: linear_rgba(grid.major_line),
x_axis: linear_rgba(grid.x_axis),
z_axis: linear_rgba(grid.z_axis),
};
queue.write_buffer(&self.uniform_buffer, 0, bytemuck::bytes_of(&uniform));
let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
label: Some("grid render pass"),
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
view,
resolve_target: Some(resolve),
depth_slice: None,
ops: wgpu::Operations {
load: wgpu::LoadOp::Load,
store: wgpu::StoreOp::Store,
},
})],
depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
view: depth,
depth_ops: Some(wgpu::Operations {
load: wgpu::LoadOp::Load,
store: wgpu::StoreOp::Store,
}),
stencil_ops: None,
}),
timestamp_writes: None,
occlusion_query_set: None,
multiview_mask: None,
});
crate::render3d::set_view(&mut pass, scene, (width, height));
pass.set_pipeline(&self.pipeline);
pass.set_bind_group(0, &self.bind_group, &[]);
pass.draw(0..3, 0..1);
}
}
const _: () = assert!(std::mem::size_of::<GridUniform>() % 16 == 0);