use crate::render::wgpu::rendergraph::{PassExecutionContext, PassNode};
use std::sync::{Arc, RwLock};
#[repr(C)]
#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)]
pub struct InteriorMappingUniforms {
pub view_proj: [[f32; 4]; 4],
pub wall_freq_and_threshold: [f32; 4],
pub disp_and_alpha: [f32; 4],
pub uv_and_pad: [f32; 4],
pub sun_direction_and_intensity: [f32; 4],
pub sun_color: [f32; 4],
pub ambient_color: [f32; 4],
}
#[repr(C)]
#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)]
pub struct GpuInstanceData {
pub model: [[f32; 4]; 4],
pub camera_pos_object: [f32; 4],
}
#[repr(C)]
#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)]
pub struct InteriorMappingVertex {
pub position: [f32; 3],
pub normal: [f32; 3],
pub texcoord: [f32; 2],
}
impl InteriorMappingVertex {
const ATTRIBS: [wgpu::VertexAttribute; 3] = wgpu::vertex_attr_array![
0 => Float32x3,
1 => Float32x3,
2 => Float32x2,
];
pub fn layout() -> wgpu::VertexBufferLayout<'static> {
wgpu::VertexBufferLayout {
array_stride: std::mem::size_of::<Self>() as wgpu::BufferAddress,
step_mode: wgpu::VertexStepMode::Vertex,
attributes: &Self::ATTRIBS,
}
}
}
pub struct InteriorMappingMeshData {
pub vertices: Vec<InteriorMappingVertex>,
pub indices: Vec<u32>,
}
pub struct InteriorMappingInstance {
pub model_matrix: crate::ecs::world::Mat4,
}
pub struct InteriorMappingState {
pub wall_frequencies: [f32; 3],
pub light_threshold: f32,
pub alpha_plane_distance: f32,
pub displacement_strengths: [f32; 3],
pub uv_multiplier: f32,
pub instances: Vec<InteriorMappingInstance>,
pub mesh_data: Option<InteriorMappingMeshData>,
pub mesh_dirty: bool,
pub enabled: bool,
pub sun_direction: [f32; 3],
pub sun_intensity: f32,
pub sun_color: [f32; 3],
pub ambient_color: [f32; 4],
}
impl Default for InteriorMappingState {
fn default() -> Self {
Self {
wall_frequencies: [2.99, 7.99, 2.99],
light_threshold: 0.6,
alpha_plane_distance: 0.1,
displacement_strengths: [1.0, 0.0, 1.0],
uv_multiplier: 2.0,
instances: Vec::new(),
mesh_data: None,
mesh_dirty: true,
enabled: true,
sun_direction: [0.5, 0.33166, 0.8],
sun_intensity: 1.0,
sun_color: [1.0, 1.0, 0.9],
ambient_color: [0.3, 0.3, 0.4, 1.0],
}
}
}
pub type InteriorMappingStateHandle = Arc<RwLock<InteriorMappingState>>;
pub fn create_interior_mapping_state() -> InteriorMappingStateHandle {
Arc::new(RwLock::new(InteriorMappingState::default()))
}
#[derive(Clone)]
pub struct InteriorMappingTextures {
pub ceiling: Vec<u8>,
pub ceiling_width: u32,
pub ceiling_height: u32,
pub floor: Vec<u8>,
pub floor_width: u32,
pub floor_height: u32,
pub wall_xy: Vec<u8>,
pub wall_xy_width: u32,
pub wall_xy_height: u32,
pub wall_zy: Vec<u8>,
pub wall_zy_width: u32,
pub wall_zy_height: u32,
pub noise: Vec<u8>,
pub noise_width: u32,
pub noise_height: u32,
pub furniture: Vec<u8>,
pub furniture_width: u32,
pub furniture_height: u32,
pub exterior: Vec<u8>,
pub exterior_width: u32,
pub exterior_height: u32,
pub cubemap_faces: [Vec<u8>; 6],
pub cubemap_face_size: u32,
}
pub fn generate_box_mesh(
half_width: f32,
half_height: f32,
half_depth: f32,
) -> InteriorMappingMeshData {
let hw = half_width;
let hh = half_height;
let hd = half_depth;
let positions: [[f32; 3]; 24] = [
[-hw, -hh, hd],
[hw, -hh, hd],
[hw, hh, hd],
[-hw, hh, hd],
[hw, -hh, -hd],
[-hw, -hh, -hd],
[-hw, hh, -hd],
[hw, hh, -hd],
[-hw, -hh, -hd],
[-hw, -hh, hd],
[-hw, hh, hd],
[-hw, hh, -hd],
[hw, -hh, hd],
[hw, -hh, -hd],
[hw, hh, -hd],
[hw, hh, hd],
[-hw, hh, hd],
[hw, hh, hd],
[hw, hh, -hd],
[-hw, hh, -hd],
[-hw, -hh, -hd],
[hw, -hh, -hd],
[hw, -hh, hd],
[-hw, -hh, hd],
];
let normals: [[f32; 3]; 6] = [
[0.0, 0.0, 1.0],
[0.0, 0.0, -1.0],
[-1.0, 0.0, 0.0],
[1.0, 0.0, 0.0],
[0.0, 1.0, 0.0],
[0.0, -1.0, 0.0],
];
let uvs: [[f32; 2]; 4] = [[0.0, 1.0], [1.0, 1.0], [1.0, 0.0], [0.0, 0.0]];
let mut vertices = Vec::with_capacity(24);
for (face, normal) in normals.iter().enumerate() {
for (vertex, uv) in uvs.iter().enumerate() {
let index = face * 4 + vertex;
vertices.push(InteriorMappingVertex {
position: positions[index],
normal: *normal,
texcoord: *uv,
});
}
}
let mut indices = Vec::with_capacity(36);
for face in 0..6u32 {
let base = face * 4;
indices.extend_from_slice(&[base, base + 1, base + 2, base, base + 2, base + 3]);
}
InteriorMappingMeshData { vertices, indices }
}
pub fn generate_cube_mesh() -> InteriorMappingMeshData {
generate_box_mesh(0.5, 0.5, 0.5)
}
pub fn generate_cylinder_mesh(segments: u32, rings: u32) -> InteriorMappingMeshData {
let mut vertices = Vec::new();
let mut indices = Vec::new();
for ring in 0..=rings {
let v = ring as f32 / rings as f32;
let y = v - 0.5;
for segment in 0..=segments {
let u = segment as f32 / segments as f32;
let angle = u * std::f32::consts::TAU;
let x = angle.cos() * 0.5;
let z = angle.sin() * 0.5;
let normal = [angle.cos(), 0.0, angle.sin()];
vertices.push(InteriorMappingVertex {
position: [x, y, z],
normal,
texcoord: [u, 1.0 - v],
});
}
}
for ring in 0..rings {
for segment in 0..segments {
let current = ring * (segments + 1) + segment;
let next = current + segments + 1;
indices.extend_from_slice(&[current, next, current + 1, current + 1, next, next + 1]);
}
}
InteriorMappingMeshData { vertices, indices }
}
pub fn generate_sphere_mesh(slices: u32, stacks: u32) -> InteriorMappingMeshData {
let mut vertices = Vec::new();
let mut indices = Vec::new();
for stack in 0..=stacks {
let v = stack as f32 / stacks as f32;
let phi = v * std::f32::consts::PI;
for slice in 0..=slices {
let u = slice as f32 / slices as f32;
let theta = u * std::f32::consts::TAU;
let x = phi.sin() * theta.cos();
let y = phi.cos();
let z = phi.sin() * theta.sin();
vertices.push(InteriorMappingVertex {
position: [x * 0.5, y * 0.5, z * 0.5],
normal: [x, y, z],
texcoord: [u, 1.0 - v],
});
}
}
for stack in 0..stacks {
for slice in 0..slices {
let current = stack * (slices + 1) + slice;
let next = current + slices + 1;
indices.extend_from_slice(&[current, next, current + 1, current + 1, next, next + 1]);
}
}
InteriorMappingMeshData { vertices, indices }
}
fn upload_texture_2d(
device: &wgpu::Device,
queue: &wgpu::Queue,
data: &[u8],
width: u32,
height: u32,
label: &str,
) -> (wgpu::Texture, wgpu::TextureView) {
let texture = device.create_texture(&wgpu::TextureDescriptor {
label: Some(label),
size: wgpu::Extent3d {
width,
height,
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: &[],
});
queue.write_texture(
wgpu::TexelCopyTextureInfo {
texture: &texture,
mip_level: 0,
origin: wgpu::Origin3d::ZERO,
aspect: wgpu::TextureAspect::All,
},
data,
wgpu::TexelCopyBufferLayout {
offset: 0,
bytes_per_row: Some(4 * width),
rows_per_image: Some(height),
},
wgpu::Extent3d {
width,
height,
depth_or_array_layers: 1,
},
);
let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
(texture, view)
}
fn upload_cubemap(
device: &wgpu::Device,
queue: &wgpu::Queue,
faces: &[Vec<u8>; 6],
face_size: u32,
label: &str,
) -> (wgpu::Texture, wgpu::TextureView) {
let texture = device.create_texture(&wgpu::TextureDescriptor {
label: Some(label),
size: wgpu::Extent3d {
width: face_size,
height: face_size,
depth_or_array_layers: 6,
},
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: &[],
});
for (face_index, face_data) in faces.iter().enumerate() {
queue.write_texture(
wgpu::TexelCopyTextureInfo {
texture: &texture,
mip_level: 0,
origin: wgpu::Origin3d {
x: 0,
y: 0,
z: face_index as u32,
},
aspect: wgpu::TextureAspect::All,
},
face_data,
wgpu::TexelCopyBufferLayout {
offset: 0,
bytes_per_row: Some(4 * face_size),
rows_per_image: Some(face_size),
},
wgpu::Extent3d {
width: face_size,
height: face_size,
depth_or_array_layers: 1,
},
);
}
let view = texture.create_view(&wgpu::TextureViewDescriptor {
dimension: Some(wgpu::TextureViewDimension::Cube),
..Default::default()
});
(texture, view)
}
fn tex_bgl_entry(
binding: u32,
dimension: wgpu::TextureViewDimension,
) -> wgpu::BindGroupLayoutEntry {
wgpu::BindGroupLayoutEntry {
binding,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Texture {
sample_type: wgpu::TextureSampleType::Float { filterable: true },
view_dimension: dimension,
multisampled: false,
},
count: None,
}
}
fn sampler_bgl_entry(binding: u32) -> wgpu::BindGroupLayoutEntry {
wgpu::BindGroupLayoutEntry {
binding,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
count: None,
}
}
struct GpuResources {
pipeline: wgpu::RenderPipeline,
uniform_buffer: wgpu::Buffer,
uniform_bind_group: wgpu::BindGroup,
texture_bind_group: wgpu::BindGroup,
instance_buffer: wgpu::Buffer,
instance_bind_group_layout: wgpu::BindGroupLayout,
instance_bind_group: wgpu::BindGroup,
instance_capacity: usize,
vertex_buffer: Option<wgpu::Buffer>,
index_buffer: Option<wgpu::Buffer>,
index_count: u32,
_textures: Vec<wgpu::Texture>,
}
pub struct InteriorMappingPass {
state: InteriorMappingStateHandle,
color_format: wgpu::TextureFormat,
depth_format: wgpu::TextureFormat,
pending_textures: Option<InteriorMappingTextures>,
gpu: Option<GpuResources>,
}
impl InteriorMappingPass {
pub fn new(
color_format: wgpu::TextureFormat,
depth_format: wgpu::TextureFormat,
textures: InteriorMappingTextures,
state: InteriorMappingStateHandle,
) -> Self {
Self {
state,
color_format,
depth_format,
pending_textures: Some(textures),
gpu: None,
}
}
fn initialize_gpu(&mut self, device: &wgpu::Device, queue: &wgpu::Queue) {
let textures = self.pending_textures.take().unwrap();
let uniform_buffer = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("Interior Mapping Uniform Buffer"),
size: std::mem::size_of::<InteriorMappingUniforms>() as u64,
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
});
let uniform_bind_group_layout =
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label: Some("Interior Mapping Uniform BGL"),
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 uniform_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some("Interior Mapping Uniform BG"),
layout: &uniform_bind_group_layout,
entries: &[wgpu::BindGroupEntry {
binding: 0,
resource: uniform_buffer.as_entire_binding(),
}],
});
let (ceiling_tex, ceiling_view) = upload_texture_2d(
device,
queue,
&textures.ceiling,
textures.ceiling_width,
textures.ceiling_height,
"IM Ceiling",
);
let (floor_tex, floor_view) = upload_texture_2d(
device,
queue,
&textures.floor,
textures.floor_width,
textures.floor_height,
"IM Floor",
);
let (wall_xy_tex, wall_xy_view) = upload_texture_2d(
device,
queue,
&textures.wall_xy,
textures.wall_xy_width,
textures.wall_xy_height,
"IM Wall XY",
);
let (wall_zy_tex, wall_zy_view) = upload_texture_2d(
device,
queue,
&textures.wall_zy,
textures.wall_zy_width,
textures.wall_zy_height,
"IM Wall ZY",
);
let (noise_tex, noise_view) = upload_texture_2d(
device,
queue,
&textures.noise,
textures.noise_width,
textures.noise_height,
"IM Noise",
);
let (furniture_tex, furniture_view) = upload_texture_2d(
device,
queue,
&textures.furniture,
textures.furniture_width,
textures.furniture_height,
"IM Furniture",
);
let (exterior_tex, exterior_view) = upload_texture_2d(
device,
queue,
&textures.exterior,
textures.exterior_width,
textures.exterior_height,
"IM Exterior",
);
let (cubemap_tex, cubemap_view) = upload_cubemap(
device,
queue,
&textures.cubemap_faces,
textures.cubemap_face_size,
"IM Cubemap",
);
let repeat_sampler = device.create_sampler(&wgpu::SamplerDescriptor {
label: Some("IM Repeat Sampler"),
address_mode_u: wgpu::AddressMode::Repeat,
address_mode_v: wgpu::AddressMode::Repeat,
address_mode_w: wgpu::AddressMode::Repeat,
mag_filter: wgpu::FilterMode::Linear,
min_filter: wgpu::FilterMode::Linear,
mipmap_filter: wgpu::MipmapFilterMode::Linear,
..Default::default()
});
let cubemap_sampler = device.create_sampler(&wgpu::SamplerDescriptor {
label: Some("IM Cubemap Sampler"),
address_mode_u: wgpu::AddressMode::ClampToEdge,
address_mode_v: wgpu::AddressMode::ClampToEdge,
address_mode_w: wgpu::AddressMode::ClampToEdge,
mag_filter: wgpu::FilterMode::Linear,
min_filter: wgpu::FilterMode::Linear,
mipmap_filter: wgpu::MipmapFilterMode::Linear,
..Default::default()
});
let texture_bind_group_layout =
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label: Some("Interior Mapping Texture BGL"),
entries: &[
tex_bgl_entry(0, wgpu::TextureViewDimension::D2),
sampler_bgl_entry(1),
tex_bgl_entry(2, wgpu::TextureViewDimension::D2),
sampler_bgl_entry(3),
tex_bgl_entry(4, wgpu::TextureViewDimension::D2),
sampler_bgl_entry(5),
tex_bgl_entry(6, wgpu::TextureViewDimension::D2),
sampler_bgl_entry(7),
tex_bgl_entry(8, wgpu::TextureViewDimension::D2),
sampler_bgl_entry(9),
tex_bgl_entry(10, wgpu::TextureViewDimension::D2),
sampler_bgl_entry(11),
tex_bgl_entry(12, wgpu::TextureViewDimension::D2),
sampler_bgl_entry(13),
tex_bgl_entry(14, wgpu::TextureViewDimension::Cube),
sampler_bgl_entry(15),
],
});
let texture_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some("Interior Mapping Texture BG"),
layout: &texture_bind_group_layout,
entries: &[
wgpu::BindGroupEntry {
binding: 0,
resource: wgpu::BindingResource::TextureView(&ceiling_view),
},
wgpu::BindGroupEntry {
binding: 1,
resource: wgpu::BindingResource::Sampler(&repeat_sampler),
},
wgpu::BindGroupEntry {
binding: 2,
resource: wgpu::BindingResource::TextureView(&floor_view),
},
wgpu::BindGroupEntry {
binding: 3,
resource: wgpu::BindingResource::Sampler(&repeat_sampler),
},
wgpu::BindGroupEntry {
binding: 4,
resource: wgpu::BindingResource::TextureView(&wall_xy_view),
},
wgpu::BindGroupEntry {
binding: 5,
resource: wgpu::BindingResource::Sampler(&repeat_sampler),
},
wgpu::BindGroupEntry {
binding: 6,
resource: wgpu::BindingResource::TextureView(&wall_zy_view),
},
wgpu::BindGroupEntry {
binding: 7,
resource: wgpu::BindingResource::Sampler(&repeat_sampler),
},
wgpu::BindGroupEntry {
binding: 8,
resource: wgpu::BindingResource::TextureView(&noise_view),
},
wgpu::BindGroupEntry {
binding: 9,
resource: wgpu::BindingResource::Sampler(&repeat_sampler),
},
wgpu::BindGroupEntry {
binding: 10,
resource: wgpu::BindingResource::TextureView(&furniture_view),
},
wgpu::BindGroupEntry {
binding: 11,
resource: wgpu::BindingResource::Sampler(&repeat_sampler),
},
wgpu::BindGroupEntry {
binding: 12,
resource: wgpu::BindingResource::TextureView(&exterior_view),
},
wgpu::BindGroupEntry {
binding: 13,
resource: wgpu::BindingResource::Sampler(&repeat_sampler),
},
wgpu::BindGroupEntry {
binding: 14,
resource: wgpu::BindingResource::TextureView(&cubemap_view),
},
wgpu::BindGroupEntry {
binding: 15,
resource: wgpu::BindingResource::Sampler(&cubemap_sampler),
},
],
});
let initial_instance_capacity = 64;
let instance_buffer = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("Interior Mapping Instance Buffer"),
size: (initial_instance_capacity * std::mem::size_of::<GpuInstanceData>()) as u64,
usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
});
let instance_bind_group_layout =
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label: Some("Interior Mapping Instance BGL"),
entries: &[wgpu::BindGroupLayoutEntry {
binding: 0,
visibility: wgpu::ShaderStages::VERTEX,
ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Storage { read_only: true },
has_dynamic_offset: false,
min_binding_size: None,
},
count: None,
}],
});
let instance_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some("Interior Mapping Instance BG"),
layout: &instance_bind_group_layout,
entries: &[wgpu::BindGroupEntry {
binding: 0,
resource: instance_buffer.as_entire_binding(),
}],
});
let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
label: Some("Interior Mapping Shader"),
source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Borrowed(include_str!(
"../../shaders/interior_mapping.wgsl"
))),
});
let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: Some("Interior Mapping Pipeline Layout"),
bind_group_layouts: &[
Some(&uniform_bind_group_layout),
Some(&texture_bind_group_layout),
Some(&instance_bind_group_layout),
],
immediate_size: 0,
});
let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
label: Some("Interior Mapping Pipeline"),
layout: Some(&pipeline_layout),
vertex: wgpu::VertexState {
module: &shader,
entry_point: Some("vertex_main"),
buffers: &[InteriorMappingVertex::layout()],
compilation_options: Default::default(),
},
fragment: Some(wgpu::FragmentState {
module: &shader,
entry_point: Some("fragment_main"),
targets: &[Some(wgpu::ColorTargetState {
format: self.color_format,
blend: Some(wgpu::BlendState::REPLACE),
write_mask: wgpu::ColorWrites::ALL,
})],
compilation_options: Default::default(),
}),
primitive: wgpu::PrimitiveState {
topology: wgpu::PrimitiveTopology::TriangleList,
strip_index_format: None,
front_face: wgpu::FrontFace::Ccw,
cull_mode: Some(wgpu::Face::Back),
unclipped_depth: false,
polygon_mode: wgpu::PolygonMode::Fill,
conservative: false,
},
depth_stencil: Some(wgpu::DepthStencilState {
format: self.depth_format,
depth_write_enabled: Some(true),
depth_compare: Some(wgpu::CompareFunction::GreaterEqual),
stencil: wgpu::StencilState::default(),
bias: wgpu::DepthBiasState::default(),
}),
multisample: wgpu::MultisampleState::default(),
multiview_mask: None,
cache: None,
});
self.gpu = Some(GpuResources {
pipeline,
uniform_buffer,
uniform_bind_group,
texture_bind_group,
instance_buffer,
instance_bind_group_layout,
instance_bind_group,
instance_capacity: initial_instance_capacity,
vertex_buffer: None,
index_buffer: None,
index_count: 0,
_textures: vec![
ceiling_tex,
floor_tex,
wall_xy_tex,
wall_zy_tex,
noise_tex,
furniture_tex,
exterior_tex,
cubemap_tex,
],
});
}
}
impl PassNode<crate::ecs::world::World> for InteriorMappingPass {
fn name(&self) -> &str {
"interior_mapping_pass"
}
fn reads(&self) -> Vec<&str> {
vec![]
}
fn writes(&self) -> Vec<&str> {
vec![]
}
fn reads_writes(&self) -> Vec<&str> {
vec!["color", "depth"]
}
fn prepare(
&mut self,
device: &wgpu::Device,
queue: &wgpu::Queue,
world: &crate::ecs::world::World,
) {
if self.pending_textures.is_some() {
self.initialize_gpu(device, queue);
}
let Some(gpu) = &mut self.gpu else {
return;
};
let Ok(mut state) = self.state.write() else {
return;
};
if !state.enabled {
return;
}
let Some(camera_matrices) =
crate::ecs::camera::queries::query_active_camera_matrices(world)
else {
return;
};
let view_proj: [[f32; 4]; 4] = (camera_matrices.projection * camera_matrices.view).into();
let uniform = InteriorMappingUniforms {
view_proj,
wall_freq_and_threshold: [
state.wall_frequencies[0],
state.wall_frequencies[1],
state.wall_frequencies[2],
state.light_threshold,
],
disp_and_alpha: [
state.displacement_strengths[0],
state.displacement_strengths[1],
state.displacement_strengths[2],
state.alpha_plane_distance,
],
uv_and_pad: [state.uv_multiplier, 0.0, 0.0, 0.0],
sun_direction_and_intensity: [
state.sun_direction[0],
state.sun_direction[1],
state.sun_direction[2],
state.sun_intensity,
],
sun_color: [
state.sun_color[0],
state.sun_color[1],
state.sun_color[2],
1.0,
],
ambient_color: state.ambient_color,
};
queue.write_buffer(&gpu.uniform_buffer, 0, bytemuck::bytes_of(&uniform));
if state.mesh_dirty {
if let Some(mesh_data) = &state.mesh_data {
let vertex_data = bytemuck::cast_slice(&mesh_data.vertices);
let vertex_buffer = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("IM Vertex Buffer"),
size: vertex_data.len() as u64,
usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
});
queue.write_buffer(&vertex_buffer, 0, vertex_data);
gpu.vertex_buffer = Some(vertex_buffer);
let index_data = bytemuck::cast_slice(&mesh_data.indices);
let index_buffer = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("IM Index Buffer"),
size: index_data.len() as u64,
usage: wgpu::BufferUsages::INDEX | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
});
queue.write_buffer(&index_buffer, 0, index_data);
gpu.index_buffer = Some(index_buffer);
gpu.index_count = mesh_data.indices.len() as u32;
}
state.mesh_dirty = false;
}
if !state.instances.is_empty() {
let camera_world_pos = camera_matrices.camera_position;
let gpu_instances: Vec<GpuInstanceData> = state
.instances
.iter()
.map(|instance| {
let model = instance.model_matrix;
let model_inv = nalgebra_glm::inverse(&model);
let camera_obj = model_inv
* nalgebra_glm::Vec4::new(
camera_world_pos.x,
camera_world_pos.y,
camera_world_pos.z,
1.0,
);
GpuInstanceData {
model: model.into(),
camera_pos_object: [camera_obj.x, camera_obj.y, camera_obj.z, 1.0],
}
})
.collect();
let required_size = gpu_instances.len() * std::mem::size_of::<GpuInstanceData>();
if gpu_instances.len() > gpu.instance_capacity {
let new_capacity = gpu_instances.len().next_power_of_two();
gpu.instance_buffer = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("IM Instance Buffer"),
size: (new_capacity * std::mem::size_of::<GpuInstanceData>()) as u64,
usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
});
gpu.instance_capacity = new_capacity;
gpu.instance_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some("IM Instance BG"),
layout: &gpu.instance_bind_group_layout,
entries: &[wgpu::BindGroupEntry {
binding: 0,
resource: gpu.instance_buffer.as_entire_binding(),
}],
});
}
queue.write_buffer(
&gpu.instance_buffer,
0,
&bytemuck::cast_slice(&gpu_instances)[..required_size],
);
}
}
fn execute<'r, 'e>(
&mut self,
context: PassExecutionContext<'r, 'e, crate::ecs::world::World>,
) -> crate::render::wgpu::rendergraph::Result<
Vec<crate::render::wgpu::rendergraph::SubGraphRunCommand<'r>>,
> {
let Some(gpu) = &self.gpu else {
return Ok(context.into_sub_graph_commands());
};
let (enabled, instance_count) = self
.state
.read()
.map(|s| (s.enabled, s.instances.len()))
.unwrap_or((false, 0));
if !enabled || instance_count == 0 || gpu.vertex_buffer.is_none() {
return Ok(context.into_sub_graph_commands());
}
let (color_view, color_load, color_store) = context.get_color_attachment("color")?;
let (depth_view, depth_load, depth_store) = context.get_depth_attachment("depth")?;
let mut render_pass = context
.encoder
.begin_render_pass(&wgpu::RenderPassDescriptor {
label: Some("Interior Mapping Pass"),
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
view: color_view,
resolve_target: None,
ops: wgpu::Operations {
load: color_load,
store: color_store,
},
depth_slice: None,
})],
depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
view: depth_view,
depth_ops: Some(wgpu::Operations {
load: depth_load,
store: depth_store,
}),
stencil_ops: None,
}),
timestamp_writes: None,
occlusion_query_set: None,
multiview_mask: None,
});
render_pass.set_pipeline(&gpu.pipeline);
render_pass.set_bind_group(0, &gpu.uniform_bind_group, &[]);
render_pass.set_bind_group(1, &gpu.texture_bind_group, &[]);
render_pass.set_bind_group(2, &gpu.instance_bind_group, &[]);
render_pass.set_vertex_buffer(0, gpu.vertex_buffer.as_ref().unwrap().slice(..));
render_pass.set_index_buffer(
gpu.index_buffer.as_ref().unwrap().slice(..),
wgpu::IndexFormat::Uint32,
);
render_pass.draw_indexed(0..gpu.index_count, 0, 0..instance_count as u32);
drop(render_pass);
Ok(context.into_sub_graph_commands())
}
}