#[cfg(feature = "debug_view")]
use myth_scene::DebugViewMode;
use rustc_hash::FxHashMap;
use smallvec::SmallVec;
use crate::core::BindGroupContext;
use crate::core::gpu::{GpuGlobalState, GpuMaterial, Tracked};
use crate::graph::extracted::SceneFeatures;
use crate::pipeline::pipeline_id::{ComputePipelineId, RenderPipelineId};
use crate::pipeline::pipeline_key::{
ComputePipelineKey, FullscreenPipelineKey, GraphicsPipelineKey, PipelineFlags,
SimpleGeometryPipelineKey, fx_hash_key,
};
use crate::pipeline::shader_gen::ShaderCompilationOptions;
use crate::pipeline::shader_manager::{ShaderManager, ShaderSource};
use crate::pipeline::vertex::GeneratedVertexLayout;
use myth_assets::{GeometryHandle, MaterialHandle};
use myth_resources::uniforms::{clustered_lighting_structs_wgsl, scene_lighting_structs_wgsl};
#[derive(Debug, Clone, PartialEq, Eq, Hash, Copy)]
pub struct FastPipelineKey {
pub material_handle: MaterialHandle,
pub material_version: u64,
pub geometry_handle: GeometryHandle,
pub geometry_version: u64,
pub instance_variants: u32,
pub global_state_id: u32,
pub scene_variants: SceneFeatures,
pub taa_enabled: bool,
pub pipeline_settings_version: u64,
#[cfg(feature = "debug_view")]
pub debug_view_mode: DebugViewMode,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Copy)]
pub struct FastShadowPipelineKey {
pub material_handle: MaterialHandle,
pub material_version: u64,
pub geometry_handle: GeometryHandle,
pub geometry_version: u64,
pub instance_variants: u32,
pub pipeline_settings_version: u64,
}
pub struct PipelineCache {
render_pipelines: Vec<wgpu::RenderPipeline>,
compute_pipelines: Vec<wgpu::ComputePipeline>,
render_layouts: Vec<Option<SmallVec<[Tracked<wgpu::BindGroupLayout>; 4]>>>,
compute_layouts: Vec<Option<SmallVec<[Tracked<wgpu::BindGroupLayout>; 4]>>>,
graphics_lookup: FxHashMap<u64, RenderPipelineId>,
fullscreen_lookup: FxHashMap<u64, RenderPipelineId>,
simple_geometry_lookup: FxHashMap<u64, RenderPipelineId>,
compute_lookup: FxHashMap<u64, ComputePipelineId>,
fast_cache: FxHashMap<FastPipelineKey, RenderPipelineId>,
fast_shadow_cache: FxHashMap<FastShadowPipelineKey, RenderPipelineId>,
}
impl Default for PipelineCache {
fn default() -> Self {
Self::new()
}
}
impl PipelineCache {
#[must_use]
pub fn new() -> Self {
Self {
render_pipelines: Vec::with_capacity(64),
compute_pipelines: Vec::with_capacity(8),
render_layouts: Vec::with_capacity(64),
compute_layouts: Vec::with_capacity(8),
graphics_lookup: FxHashMap::default(),
fullscreen_lookup: FxHashMap::default(),
simple_geometry_lookup: FxHashMap::default(),
compute_lookup: FxHashMap::default(),
fast_cache: FxHashMap::default(),
fast_shadow_cache: FxHashMap::default(),
}
}
#[inline]
#[must_use]
pub fn get_render_pipeline(&self, id: RenderPipelineId) -> &wgpu::RenderPipeline {
&self.render_pipelines[id.index()]
}
#[inline]
#[must_use]
pub fn get_compute_pipeline(&self, id: ComputePipelineId) -> &wgpu::ComputePipeline {
&self.compute_pipelines[id.index()]
}
pub fn register_render_layouts(
&mut self,
id: RenderPipelineId,
layouts: SmallVec<[Tracked<wgpu::BindGroupLayout>; 4]>,
) {
let idx = id.index();
if idx >= self.render_layouts.len() {
self.render_layouts.resize_with(idx + 1, || None);
}
self.render_layouts[idx] = Some(layouts);
}
pub fn register_compute_layouts(
&mut self,
id: ComputePipelineId,
layouts: SmallVec<[Tracked<wgpu::BindGroupLayout>; 4]>,
) {
let idx = id.index();
if idx >= self.compute_layouts.len() {
self.compute_layouts.resize_with(idx + 1, || None);
}
self.compute_layouts[idx] = Some(layouts);
}
#[inline]
#[must_use]
pub fn get_tracked_layout(
&self,
id: RenderPipelineId,
group_index: usize,
) -> &Tracked<wgpu::BindGroupLayout> {
self.render_layouts[id.index()]
.as_ref()
.expect("No layouts registered for this pipeline")
.get(group_index)
.expect("group_index out of range")
}
#[inline]
#[must_use]
pub fn get_tracked_compute_layout(
&self,
id: ComputePipelineId,
group_index: usize,
) -> &Tracked<wgpu::BindGroupLayout> {
self.compute_layouts[id.index()]
.as_ref()
.expect("No layouts registered for this compute pipeline")
.get(group_index)
.expect("group_index out of range")
}
#[deprecated(note = "Prefer targeted invalidation via version increments")]
pub fn clear(&mut self) {
self.render_pipelines.clear();
self.compute_pipelines.clear();
self.graphics_lookup.clear();
self.fullscreen_lookup.clear();
self.simple_geometry_lookup.clear();
self.compute_lookup.clear();
self.fast_cache.clear();
self.fast_shadow_cache.clear();
}
#[must_use]
pub fn get_pipeline_fast(&self, fast_key: FastPipelineKey) -> Option<RenderPipelineId> {
self.fast_cache.get(&fast_key).copied()
}
pub fn insert_pipeline_fast(&mut self, fast_key: FastPipelineKey, id: RenderPipelineId) {
self.fast_cache.insert(fast_key, id);
}
#[must_use]
pub fn get_shadow_pipeline_fast(
&self,
fast_key: FastShadowPipelineKey,
) -> Option<RenderPipelineId> {
self.fast_shadow_cache.get(&fast_key).copied()
}
pub fn insert_shadow_pipeline_fast(
&mut self,
fast_key: FastShadowPipelineKey,
id: RenderPipelineId,
) {
self.fast_shadow_cache.insert(fast_key, id);
}
#[allow(clippy::too_many_arguments, clippy::too_many_lines)]
pub fn get_or_create_graphics(
&mut self,
device: &wgpu::Device,
shader_manager: &mut ShaderManager,
shader_source: ShaderSource<'_>,
canonical_key: &GraphicsPipelineKey,
options: &ShaderCompilationOptions,
vertex_layout: &GeneratedVertexLayout,
gpu_material: &GpuMaterial,
object_bind_group: &BindGroupContext,
gpu_world: &GpuGlobalState,
screen_bind_group_layout: &wgpu::BindGroupLayout,
) -> RenderPipelineId {
let hash = fx_hash_key(&canonical_key);
if let Some(&id) = self.graphics_lookup.get(&hash) {
return id;
}
let binding_code = format!(
"{}\n{}\n{}",
&gpu_world.binding_wgsl, &gpu_material.binding_wgsl, &object_bind_group.binding_wgsl
);
let mut opts = options.clone();
opts.inject_code("vertex_input_code", &vertex_layout.vertex_input_code);
opts.inject_code("binding_code", binding_code);
opts.inject_code("scene_lighting_structs", scene_lighting_structs_wgsl());
opts.inject_code(
"clustered_lighting_structs",
clustered_lighting_structs_wgsl(),
);
let (shader_module, _code_hash) =
shader_manager.get_or_compile(device, shader_source, &opts);
let layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: Some("Render Pipeline Layout"),
bind_group_layouts: &[
Some(&gpu_world.layout),
Some(&gpu_material.layout),
Some(&object_bind_group.layout),
Some(screen_bind_group_layout),
],
immediate_size: 0,
});
let vertex_buffers_layout: Vec<_> =
vertex_layout.buffers.iter().map(|l| l.as_wgpu()).collect();
let color_targets: Vec<Option<wgpu::ColorTargetState>> = canonical_key
.color_targets
.iter()
.map(|target| {
Some(wgpu::ColorTargetState {
format: target.format,
blend: target.blend.as_ref().map(|bk| wgpu::BlendState {
color: wgpu::BlendComponent {
src_factor: bk.color.src_factor,
dst_factor: bk.color.dst_factor,
operation: bk.color.operation,
},
alpha: wgpu::BlendComponent {
src_factor: bk.alpha.src_factor,
dst_factor: bk.alpha.dst_factor,
operation: bk.alpha.operation,
},
}),
write_mask: wgpu::ColorWrites::from_bits_retain(target.write_mask),
})
})
.collect();
let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
label: Some("Scene Render Pipeline"),
layout: Some(&layout),
vertex: wgpu::VertexState {
module: shader_module,
entry_point: Some("vs_main"),
buffers: &vertex_buffers_layout,
compilation_options: wgpu::PipelineCompilationOptions::default(),
},
fragment: Some(wgpu::FragmentState {
module: shader_module,
entry_point: Some("fs_main"),
targets: &color_targets,
compilation_options: wgpu::PipelineCompilationOptions::default(),
}),
primitive: wgpu::PrimitiveState {
topology: canonical_key.topology,
front_face: canonical_key.front_face,
cull_mode: canonical_key.cull_mode,
..Default::default()
},
depth_stencil: Some(wgpu::DepthStencilState {
format: canonical_key.depth_format,
depth_write_enabled: Some(canonical_key.flags.contains(PipelineFlags::DEPTH_WRITE)),
depth_compare: Some(canonical_key.depth_compare),
stencil: wgpu::StencilState::default(),
bias: wgpu::DepthBiasState::default(),
}),
multisample: wgpu::MultisampleState {
count: canonical_key.sample_count,
mask: !0,
alpha_to_coverage_enabled: canonical_key
.flags
.contains(PipelineFlags::ALPHA_TO_COVERAGE),
},
multiview_mask: None,
cache: None,
});
let id = self.push_render_pipeline(pipeline);
self.graphics_lookup.insert(hash, id);
id
}
pub fn get_or_create_fullscreen(
&mut self,
device: &wgpu::Device,
shader_module: &wgpu::ShaderModule,
pipeline_layout: &wgpu::PipelineLayout,
canonical_key: &FullscreenPipelineKey,
label: &str,
) -> RenderPipelineId {
let hash = fx_hash_key(canonical_key);
if let Some(&id) = self.fullscreen_lookup.get(&hash) {
return id;
}
let color_targets: Vec<Option<wgpu::ColorTargetState>> = canonical_key
.color_targets
.iter()
.map(|ct| {
Some(wgpu::ColorTargetState {
format: ct.format,
blend: ct.blend.map(|bk| wgpu::BlendState {
color: wgpu::BlendComponent {
src_factor: bk.color.src_factor,
dst_factor: bk.color.dst_factor,
operation: bk.color.operation,
},
alpha: wgpu::BlendComponent {
src_factor: bk.alpha.src_factor,
dst_factor: bk.alpha.dst_factor,
operation: bk.alpha.operation,
},
}),
write_mask: wgpu::ColorWrites::from_bits_truncate(ct.write_mask),
})
})
.collect();
let depth_stencil = canonical_key
.depth_stencil
.map(|dk| wgpu::DepthStencilState {
format: dk.format,
depth_write_enabled: dk.depth_write_enabled,
depth_compare: dk.depth_compare,
stencil: wgpu::StencilState {
front: wgpu::StencilFaceState {
compare: dk.stencil.front.compare,
fail_op: dk.stencil.front.fail_op,
depth_fail_op: dk.stencil.front.depth_fail_op,
pass_op: dk.stencil.front.pass_op,
},
back: wgpu::StencilFaceState {
compare: dk.stencil.back.compare,
fail_op: dk.stencil.back.fail_op,
depth_fail_op: dk.stencil.back.depth_fail_op,
pass_op: dk.stencil.back.pass_op,
},
read_mask: dk.stencil.read_mask,
write_mask: dk.stencil.write_mask,
},
bias: wgpu::DepthBiasState {
constant: dk.bias.constant,
slope_scale: f32::from_bits(dk.bias.slope_scale_bits),
clamp: f32::from_bits(dk.bias.clamp_bits),
},
});
let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
label: Some(label),
layout: Some(pipeline_layout),
vertex: wgpu::VertexState {
module: shader_module,
entry_point: Some("vs_main"),
buffers: &[],
compilation_options: wgpu::PipelineCompilationOptions::default(),
},
fragment: Some(wgpu::FragmentState {
module: shader_module,
entry_point: Some("fs_main"),
targets: &color_targets,
compilation_options: wgpu::PipelineCompilationOptions::default(),
}),
primitive: wgpu::PrimitiveState {
topology: wgpu::PrimitiveTopology::TriangleList,
front_face: wgpu::FrontFace::Ccw,
cull_mode: None,
..Default::default()
},
depth_stencil,
multisample: wgpu::MultisampleState {
count: canonical_key.multisample.count,
mask: canonical_key.multisample.mask,
alpha_to_coverage_enabled: canonical_key.multisample.alpha_to_coverage_enabled,
},
multiview_mask: None,
cache: None,
});
let id = self.push_render_pipeline(pipeline);
self.fullscreen_lookup.insert(hash, id);
id
}
pub fn get_or_create_simple_geometry(
&mut self,
device: &wgpu::Device,
shader_module: &wgpu::ShaderModule,
pipeline_layout: &wgpu::PipelineLayout,
canonical_key: &SimpleGeometryPipelineKey,
label: &str,
vertex_buffers: &[wgpu::VertexBufferLayout<'_>],
) -> RenderPipelineId {
let hash = fx_hash_key(canonical_key);
if let Some(&id) = self.simple_geometry_lookup.get(&hash) {
return id;
}
let color_targets: Vec<Option<wgpu::ColorTargetState>> = canonical_key
.color_targets
.iter()
.map(|ct| {
Some(wgpu::ColorTargetState {
format: ct.format,
blend: ct.blend.map(|bk| wgpu::BlendState {
color: wgpu::BlendComponent {
src_factor: bk.color.src_factor,
dst_factor: bk.color.dst_factor,
operation: bk.color.operation,
},
alpha: wgpu::BlendComponent {
src_factor: bk.alpha.src_factor,
dst_factor: bk.alpha.dst_factor,
operation: bk.alpha.operation,
},
}),
write_mask: wgpu::ColorWrites::from_bits_truncate(ct.write_mask),
})
})
.collect();
let dk = &canonical_key.depth_stencil;
let depth_stencil = Some(wgpu::DepthStencilState {
format: dk.format,
depth_write_enabled: dk.depth_write_enabled,
depth_compare: dk.depth_compare,
stencil: wgpu::StencilState {
front: wgpu::StencilFaceState {
compare: dk.stencil.front.compare,
fail_op: dk.stencil.front.fail_op,
depth_fail_op: dk.stencil.front.depth_fail_op,
pass_op: dk.stencil.front.pass_op,
},
back: wgpu::StencilFaceState {
compare: dk.stencil.back.compare,
fail_op: dk.stencil.back.fail_op,
depth_fail_op: dk.stencil.back.depth_fail_op,
pass_op: dk.stencil.back.pass_op,
},
read_mask: dk.stencil.read_mask,
write_mask: dk.stencil.write_mask,
},
bias: wgpu::DepthBiasState {
constant: dk.bias.constant,
slope_scale: f32::from_bits(dk.bias.slope_scale_bits),
clamp: f32::from_bits(dk.bias.clamp_bits),
},
});
let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
label: Some(label),
layout: Some(pipeline_layout),
vertex: wgpu::VertexState {
module: shader_module,
entry_point: Some("vs_main"),
buffers: vertex_buffers,
compilation_options: wgpu::PipelineCompilationOptions::default(),
},
fragment: Some(wgpu::FragmentState {
module: shader_module,
entry_point: Some("fs_main"),
targets: &color_targets,
compilation_options: wgpu::PipelineCompilationOptions::default(),
}),
primitive: wgpu::PrimitiveState {
topology: canonical_key.topology,
front_face: canonical_key.front_face,
cull_mode: canonical_key.cull_mode,
..Default::default()
},
depth_stencil,
multisample: wgpu::MultisampleState {
count: canonical_key.sample_count,
mask: !0,
alpha_to_coverage_enabled: false,
},
multiview_mask: None,
cache: None,
});
let id = self.push_render_pipeline(pipeline);
self.simple_geometry_lookup.insert(hash, id);
id
}
pub fn get_or_create_compute(
&mut self,
device: &wgpu::Device,
shader_module: &wgpu::ShaderModule,
pipeline_layout: &wgpu::PipelineLayout,
canonical_key: &ComputePipelineKey,
compilation_options: &wgpu::PipelineCompilationOptions<'_>,
label: &str,
) -> ComputePipelineId {
let hash = fx_hash_key(canonical_key);
if let Some(&id) = self.compute_lookup.get(&hash) {
return id;
}
let pipeline = device.create_compute_pipeline(&wgpu::ComputePipelineDescriptor {
label: Some(label),
layout: Some(pipeline_layout),
module: shader_module,
entry_point: Some("main"),
compilation_options: compilation_options.clone(),
cache: None,
});
let id = self.push_compute_pipeline(pipeline);
self.compute_lookup.insert(hash, id);
id
}
#[must_use]
pub fn render_pipeline_count(&self) -> usize {
self.render_pipelines.len()
}
#[must_use]
pub fn compute_pipeline_count(&self) -> usize {
self.compute_pipelines.len()
}
fn push_render_pipeline(&mut self, pipeline: wgpu::RenderPipeline) -> RenderPipelineId {
let id = RenderPipelineId(self.render_pipelines.len() as u32);
self.render_pipelines.push(pipeline);
self.render_layouts.push(None);
id
}
fn push_compute_pipeline(&mut self, pipeline: wgpu::ComputePipeline) -> ComputePipelineId {
let id = ComputePipelineId(self.compute_pipelines.len() as u32);
self.compute_pipelines.push(pipeline);
self.compute_layouts.push(None);
id
}
}