use crate::resources::mesh_store::MeshId;
use crate::resources::{DualPipeline, ViewportGpuResources};
use wgpu::util::DeviceExt as _;
#[repr(C)]
#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
pub(crate) struct DecalUniformRaw {
pub inv_transform: [[f32; 4]; 4], pub blend_mode: u32, pub alpha: f32, pub normal_blend_strength: f32, pub has_normal: u32, pub roughness: f32, pub metallic: f32, pub has_roughness_tex: u32, pub has_metallic_tex: u32, pub uv_offset: [f32; 2], pub uv_scale: [f32; 2], pub emissive: f32, pub has_emissive_tex: u32, pub edge_fade: f32, pub _pad: u32, pub projection: u32, pub tri_blend_sharpness: f32, pub _pad2: u32, pub _pad3: u32, }
pub(crate) struct DecalGpuItem {
pub blend_mode: crate::renderer::DecalBlendMode,
pub _uniform_buf: wgpu::Buffer,
pub bind_group: wgpu::BindGroup,
}
pub(crate) struct DecalExcludeGpuItem {
pub mesh_id: MeshId,
pub _uniform_buf: wgpu::Buffer,
pub bind_group: wgpu::BindGroup,
}
impl ViewportGpuResources {
pub(crate) fn ensure_decal_pipeline(&mut self, device: &wgpu::Device) {
if self.decal_replace_pipeline.is_some() {
return;
}
let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
label: Some("decal_shader"),
source: wgpu::ShaderSource::Wgsl(
include_str!(concat!(env!("OUT_DIR"), "/decal.wgsl")).into(),
),
});
let tex2d_entry = |binding: u32| wgpu::BindGroupLayoutEntry {
binding,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Texture {
sample_type: wgpu::TextureSampleType::Float { filterable: true },
view_dimension: wgpu::TextureViewDimension::D2,
multisampled: false,
},
count: None,
};
let item_bgl = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label: Some("decal_item_bgl"),
entries: &[
wgpu::BindGroupLayoutEntry {
binding: 0,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Uniform,
has_dynamic_offset: false,
min_binding_size: None,
},
count: None,
},
tex2d_entry(1),
wgpu::BindGroupLayoutEntry {
binding: 2,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
count: None,
},
tex2d_entry(3), tex2d_entry(4), tex2d_entry(5), tex2d_entry(6), ],
});
let depth_bgl = self
.decal_depth_bgl
.as_ref()
.expect("decal_depth_bgl must exist before ensure_decal_pipeline");
let layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: Some("decal_pipeline_layout"),
bind_group_layouts: &[&self.camera_bind_group_layout, depth_bgl, &item_bgl],
push_constant_ranges: &[],
});
let make = |fmt: wgpu::TextureFormat, blend: wgpu::BlendState| {
device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
label: Some("decal_pipeline"),
layout: Some(&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: fmt,
blend: Some(blend),
write_mask: wgpu::ColorWrites::ALL,
})],
compilation_options: wgpu::PipelineCompilationOptions::default(),
}),
primitive: wgpu::PrimitiveState {
topology: wgpu::PrimitiveTopology::TriangleList,
cull_mode: None,
..Default::default()
},
depth_stencil: None,
multisample: wgpu::MultisampleState::default(),
multiview: None,
cache: None,
})
};
let replace_blend = wgpu::BlendState::ALPHA_BLENDING;
let multiply_blend = wgpu::BlendState {
color: wgpu::BlendComponent {
src_factor: wgpu::BlendFactor::Zero,
dst_factor: wgpu::BlendFactor::Src,
operation: wgpu::BlendOperation::Add,
},
alpha: wgpu::BlendComponent {
src_factor: wgpu::BlendFactor::Zero,
dst_factor: wgpu::BlendFactor::One,
operation: wgpu::BlendOperation::Add,
},
};
let additive_blend = wgpu::BlendState {
color: wgpu::BlendComponent {
src_factor: wgpu::BlendFactor::SrcAlpha,
dst_factor: wgpu::BlendFactor::One,
operation: wgpu::BlendOperation::Add,
},
alpha: wgpu::BlendComponent {
src_factor: wgpu::BlendFactor::Zero,
dst_factor: wgpu::BlendFactor::One,
operation: wgpu::BlendOperation::Add,
},
};
self.decal_item_bgl = Some(item_bgl);
self.decal_replace_pipeline = Some(DualPipeline {
ldr: make(self.target_format, replace_blend),
hdr: make(wgpu::TextureFormat::Rgba16Float, replace_blend),
});
self.decal_multiply_pipeline = Some(DualPipeline {
ldr: make(self.target_format, multiply_blend),
hdr: make(wgpu::TextureFormat::Rgba16Float, multiply_blend),
});
self.decal_additive_pipeline = Some(DualPipeline {
ldr: make(self.target_format, additive_blend),
hdr: make(wgpu::TextureFormat::Rgba16Float, additive_blend),
});
}
pub(crate) fn create_decal_depth_bg(
&self,
device: &wgpu::Device,
depth_only_view: &wgpu::TextureView,
stencil_only_view: &wgpu::TextureView,
) -> wgpu::BindGroup {
let bgl = self
.decal_depth_bgl
.as_ref()
.expect("decal_depth_bgl not created");
device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some("decal_depth_bg"),
layout: bgl,
entries: &[
wgpu::BindGroupEntry {
binding: 0,
resource: wgpu::BindingResource::TextureView(depth_only_view),
},
wgpu::BindGroupEntry {
binding: 1,
resource: wgpu::BindingResource::TextureView(stencil_only_view),
},
],
})
}
pub(crate) fn upload_decal_item(
&self,
device: &wgpu::Device,
item: &crate::renderer::DecalItem,
) -> DecalGpuItem {
let model = glam::Mat4::from_cols_array_2d(&item.transform);
let inv_transform = model.inverse().to_cols_array_2d();
let blend_mode_u32 = match item.blend_mode {
crate::renderer::DecalBlendMode::Replace => 0u32,
crate::renderer::DecalBlendMode::Multiply => 1u32,
crate::renderer::DecalBlendMode::Additive => 0u32,
};
let (projection_u32, tri_blend_sharpness) = match item.projection {
crate::renderer::DecalProjection::Planar => (0u32, 1.0f32),
crate::renderer::DecalProjection::TriPlanar { blend_sharpness } => {
(1u32, blend_sharpness.max(0.1))
}
crate::renderer::DecalProjection::Cylindrical { facing } => {
let code = match facing {
crate::renderer::CylindricalFacing::Outward => 2u32,
crate::renderer::CylindricalFacing::Inward => 3u32,
};
(code, 0.0f32)
}
};
let has_normal = item.normal_texture_id.is_some() as u32;
let has_roughness_tex = item.roughness_texture_id.is_some() as u32;
let has_metallic_tex = item.metallic_texture_id.is_some() as u32;
let has_emissive_tex = item.emissive_texture_id.is_some() as u32;
let raw = DecalUniformRaw {
inv_transform,
blend_mode: blend_mode_u32,
alpha: item.alpha,
normal_blend_strength: if has_normal != 0 {
item.normal_blend_strength
} else {
0.0
},
has_normal,
roughness: item.roughness,
metallic: item.metallic,
has_roughness_tex,
has_metallic_tex,
uv_offset: item.uv_offset,
uv_scale: item.uv_scale,
emissive: item.emissive,
has_emissive_tex,
edge_fade: item.edge_fade.clamp(0.0, 0.5),
_pad: 0,
projection: projection_u32,
tri_blend_sharpness,
_pad2: 0,
_pad3: 0,
};
let uniform_buf = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("decal_uniform_buf"),
contents: bytemuck::bytes_of(&raw),
usage: wgpu::BufferUsages::UNIFORM,
});
let resolve_tex = |id: Option<u64>| -> &wgpu::TextureView {
id.and_then(|i| self.textures.get(i as usize))
.map(|t| &t.view)
.unwrap_or(&self.fallback_texture.view)
};
let tex_view = self
.textures
.get(item.texture_id as usize)
.map(|t| &t.view)
.unwrap_or(&self.fallback_texture.view);
let normal_view = resolve_tex(item.normal_texture_id);
let roughness_view = resolve_tex(item.roughness_texture_id);
let metallic_view = resolve_tex(item.metallic_texture_id);
let emissive_view = resolve_tex(item.emissive_texture_id);
let bgl = self
.decal_item_bgl
.as_ref()
.expect("ensure_decal_pipeline not called");
let sampler = self
.decal_sampler
.as_ref()
.expect("decal_sampler not created");
let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some("decal_item_bg"),
layout: bgl,
entries: &[
wgpu::BindGroupEntry {
binding: 0,
resource: uniform_buf.as_entire_binding(),
},
wgpu::BindGroupEntry {
binding: 1,
resource: wgpu::BindingResource::TextureView(tex_view),
},
wgpu::BindGroupEntry {
binding: 2,
resource: wgpu::BindingResource::Sampler(sampler),
},
wgpu::BindGroupEntry {
binding: 3,
resource: wgpu::BindingResource::TextureView(normal_view),
},
wgpu::BindGroupEntry {
binding: 4,
resource: wgpu::BindingResource::TextureView(roughness_view),
},
wgpu::BindGroupEntry {
binding: 5,
resource: wgpu::BindingResource::TextureView(metallic_view),
},
wgpu::BindGroupEntry {
binding: 6,
resource: wgpu::BindingResource::TextureView(emissive_view),
},
],
});
DecalGpuItem {
blend_mode: item.blend_mode,
_uniform_buf: uniform_buf,
bind_group,
}
}
pub(crate) fn ensure_decal_exclude_pipeline(&mut self, device: &wgpu::Device) {
if self.decal_exclude_pipeline.is_some() {
return;
}
let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
label: Some("decal_exclude_shader"),
source: wgpu::ShaderSource::Wgsl(
include_str!(concat!(env!("OUT_DIR"), "/decal_exclude.wgsl")).into(),
),
});
let obj_bgl = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label: Some("decal_exclude_obj_bgl"),
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 layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: Some("decal_exclude_pipeline_layout"),
bind_group_layouts: &[&self.camera_bind_group_layout, &obj_bgl],
push_constant_ranges: &[],
});
let vertex_layout = wgpu::VertexBufferLayout {
array_stride: 64,
step_mode: wgpu::VertexStepMode::Vertex,
attributes: &[wgpu::VertexAttribute {
format: wgpu::VertexFormat::Float32x3,
offset: 0,
shader_location: 0,
}],
};
let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
label: Some("decal_exclude_pipeline"),
layout: Some(&layout),
vertex: wgpu::VertexState {
module: &shader,
entry_point: Some("vs_main"),
buffers: &[vertex_layout],
compilation_options: wgpu::PipelineCompilationOptions::default(),
},
fragment: Some(wgpu::FragmentState {
module: &shader,
entry_point: Some("fs_main"),
targets: &[],
compilation_options: wgpu::PipelineCompilationOptions::default(),
}),
primitive: wgpu::PrimitiveState {
topology: wgpu::PrimitiveTopology::TriangleList,
cull_mode: None,
..Default::default()
},
depth_stencil: Some(wgpu::DepthStencilState {
format: wgpu::TextureFormat::Depth24PlusStencil8,
depth_write_enabled: false,
depth_compare: wgpu::CompareFunction::LessEqual,
stencil: wgpu::StencilState {
front: wgpu::StencilFaceState {
compare: wgpu::CompareFunction::Always,
fail_op: wgpu::StencilOperation::Keep,
depth_fail_op: wgpu::StencilOperation::Keep,
pass_op: wgpu::StencilOperation::Replace,
},
back: wgpu::StencilFaceState {
compare: wgpu::CompareFunction::Always,
fail_op: wgpu::StencilOperation::Keep,
depth_fail_op: wgpu::StencilOperation::Keep,
pass_op: wgpu::StencilOperation::Replace,
},
read_mask: 0xff,
write_mask: 0xff,
},
bias: wgpu::DepthBiasState {
constant: -2,
slope_scale: 0.0,
clamp: 0.0,
},
}),
multisample: wgpu::MultisampleState::default(),
multiview: None,
cache: None,
});
self.decal_exclude_obj_bgl = Some(obj_bgl);
self.decal_exclude_pipeline = Some(pipeline);
}
pub(crate) fn upload_decal_exclude_item(
&self,
device: &wgpu::Device,
mesh_id: MeshId,
model: [[f32; 4]; 4],
) -> DecalExcludeGpuItem {
let uniform_buf = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("decal_exclude_uniform_buf"),
contents: bytemuck::cast_slice(&model),
usage: wgpu::BufferUsages::UNIFORM,
});
let bgl = self
.decal_exclude_obj_bgl
.as_ref()
.expect("ensure_decal_exclude_pipeline not called");
let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some("decal_exclude_obj_bg"),
layout: bgl,
entries: &[wgpu::BindGroupEntry {
binding: 0,
resource: uniform_buf.as_entire_binding(),
}],
});
DecalExcludeGpuItem {
mesh_id,
_uniform_buf: uniform_buf,
bind_group,
}
}
}