1use crate::render_features::{
2 RenderPhase, RenderPhaseIndex, RenderRegistry, MAX_RENDER_PHASE_COUNT,
3};
4use crate::resources::resource_arc::{ResourceId, WeakResourceArc};
5use crate::resources::vertex_data::{VertexDataSetLayout, VertexDataSetLayoutHash};
6use crate::{GraphicsPipelineResource, MaterialPassResource, ResourceArc, ResourceLookupSet};
7use fnv::{FnvHashMap, FnvHashSet, FnvHasher};
8use rafx_api::{
9 RafxFormat, RafxResult, RafxSampleCount, RafxVertexLayout, RafxVertexLayoutAttribute,
10 RafxVertexLayoutBuffer,
11};
12use std::hash::{Hash, Hasher};
13use std::sync::{Arc, Mutex};
14
15#[derive(Debug, Clone, PartialEq, Eq)]
22pub struct GraphicsPipelineRenderTargetMeta {
23 color_formats: Vec<RafxFormat>,
24 depth_stencil_format: Option<RafxFormat>,
25 sample_count: RafxSampleCount,
26 hash: GraphicsPipelineRenderTargetMetaHash,
27}
28
29impl GraphicsPipelineRenderTargetMeta {
30 pub fn new(
31 color_formats: Vec<RafxFormat>,
32 depth_stencil_format: Option<RafxFormat>,
33 sample_count: RafxSampleCount,
34 ) -> Self {
35 let hash = GraphicsPipelineRenderTargetMetaHash::new(
36 &color_formats,
37 depth_stencil_format,
38 sample_count,
39 );
40 GraphicsPipelineRenderTargetMeta {
41 color_formats,
42 depth_stencil_format,
43 sample_count,
44 hash,
45 }
46 }
47
48 pub fn color_formats(&self) -> &[RafxFormat] {
49 &self.color_formats
50 }
51
52 pub fn depth_stencil_format(&self) -> Option<RafxFormat> {
53 self.depth_stencil_format
54 }
55
56 pub fn sample_count(&self) -> RafxSampleCount {
57 self.sample_count
58 }
59
60 pub fn render_target_meta_hash(&self) -> GraphicsPipelineRenderTargetMetaHash {
61 self.hash
62 }
63}
64
65#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
66pub struct GraphicsPipelineRenderTargetMetaHash(u64);
67impl GraphicsPipelineRenderTargetMetaHash {
68 fn new(
69 color_formats: &[RafxFormat],
70 depth_stencil_format: Option<RafxFormat>,
71 sample_count: RafxSampleCount,
72 ) -> Self {
73 let mut hasher = FnvHasher::default();
74 color_formats.hash(&mut hasher);
75 depth_stencil_format.hash(&mut hasher);
76 sample_count.hash(&mut hasher);
77 let hash = hasher.finish();
78 GraphicsPipelineRenderTargetMetaHash(hash)
79 }
80}
81
82#[derive(PartialEq, Eq, Hash)]
83struct CachedGraphicsPipelineKey {
84 material_pass: ResourceId,
85 render_target_meta_hash: GraphicsPipelineRenderTargetMetaHash,
86 vertex_data_set_layout: VertexDataSetLayoutHash,
87}
88
89#[derive(PartialEq, Eq)]
90struct CachedGraphicsPipeline {
91 material_pass_resource: WeakResourceArc<MaterialPassResource>,
92 graphics_pipeline: ResourceArc<GraphicsPipelineResource>,
93 keep_until_frame: u64,
94}
95
96#[derive(Debug)]
97struct RegisteredRenderTargetMeta {
98 keep_until_frame: u64,
99 #[allow(unused)]
100 meta: GraphicsPipelineRenderTargetMeta,
101}
102
103pub struct GraphicsPipelineCacheInner {
104 resource_lookup_set: ResourceLookupSet,
105
106 render_target_meta_assignments:
108 Vec<FnvHashMap<GraphicsPipelineRenderTargetMetaHash, RegisteredRenderTargetMeta>>,
109 material_pass_assignments: Vec<FnvHashMap<ResourceId, WeakResourceArc<MaterialPassResource>>>,
110
111 cached_pipelines: FnvHashMap<CachedGraphicsPipelineKey, CachedGraphicsPipeline>,
112
113 current_frame_index: u64,
114 frames_to_persist: Option<u64>,
115
116 #[cfg(debug_assertions)]
117 vertex_data_set_layouts: FnvHashMap<VertexDataSetLayoutHash, VertexDataSetLayout>,
118 #[cfg(debug_assertions)]
119 render_target_metas:
120 FnvHashMap<GraphicsPipelineRenderTargetMetaHash, GraphicsPipelineRenderTargetMeta>,
121
122 #[cfg(debug_assertions)]
123 lock_call_count_previous_frame: u64,
124 #[cfg(debug_assertions)]
125 lock_call_count: u64,
126
127 #[cfg(debug_assertions)]
128 pipeline_create_count_previous_frame: u64,
129 #[cfg(debug_assertions)]
130 pipeline_create_count: u64,
131}
132
133#[derive(Debug)]
134pub struct GraphicsPipelineCacheMetrics {
135 #[allow(unused)]
136 pipeline_count: usize,
137
138 #[allow(unused)]
139 #[cfg(debug_assertions)]
140 lock_call_count_previous_frame: u64,
141 #[allow(unused)]
142 #[cfg(debug_assertions)]
143 pipeline_create_count_previous_frame: u64,
144}
145
146#[derive(Clone)]
147pub struct GraphicsPipelineCache {
148 render_registry: RenderRegistry,
149 inner: Arc<Mutex<GraphicsPipelineCacheInner>>,
150}
151
152impl GraphicsPipelineCache {
153 pub fn new(
154 render_registry: &RenderRegistry,
155 resource_lookup_set: ResourceLookupSet,
156 ) -> Self {
157 const DEFAULT_FRAMES_TO_PERSIST: Option<u64> = None;
161
162 let mut render_target_meta_assignments =
163 Vec::with_capacity(MAX_RENDER_PHASE_COUNT as usize);
164 render_target_meta_assignments
165 .resize_with(MAX_RENDER_PHASE_COUNT as usize, Default::default);
166
167 let mut material_pass_assignments = Vec::with_capacity(MAX_RENDER_PHASE_COUNT as usize);
168 material_pass_assignments.resize_with(MAX_RENDER_PHASE_COUNT as usize, Default::default);
169
170 let inner = GraphicsPipelineCacheInner {
171 resource_lookup_set,
172 render_target_meta_assignments,
173 material_pass_assignments,
174 cached_pipelines: Default::default(),
175 current_frame_index: 0,
176 frames_to_persist: DEFAULT_FRAMES_TO_PERSIST,
177 #[cfg(debug_assertions)]
178 vertex_data_set_layouts: Default::default(),
179 #[cfg(debug_assertions)]
180 render_target_metas: Default::default(),
181 #[cfg(debug_assertions)]
182 lock_call_count_previous_frame: 0,
183 #[cfg(debug_assertions)]
184 lock_call_count: 0,
185 #[cfg(debug_assertions)]
186 pipeline_create_count_previous_frame: 0,
187 #[cfg(debug_assertions)]
188 pipeline_create_count: 0,
189 };
190
191 GraphicsPipelineCache {
192 render_registry: render_registry.clone(),
193 inner: Arc::new(Mutex::new(inner)),
194 }
195 }
196
197 pub fn metrics(&self) -> GraphicsPipelineCacheMetrics {
198 let mut guard = self.inner.lock().unwrap();
199 let inner = &mut *guard;
200 #[cfg(debug_assertions)]
201 {
202 inner.lock_call_count += 1;
203 }
204
205 GraphicsPipelineCacheMetrics {
206 pipeline_count: inner.cached_pipelines.len(),
207 #[cfg(debug_assertions)]
208 lock_call_count_previous_frame: inner.lock_call_count_previous_frame,
209 #[cfg(debug_assertions)]
210 pipeline_create_count_previous_frame: inner.pipeline_create_count_previous_frame,
211 }
212 }
213
214 #[cfg(debug_assertions)]
215 fn verify_data_set_layout_hash_unique(
216 inner: &mut GraphicsPipelineCacheInner,
217 layout: &VertexDataSetLayout,
218 ) {
219 if let Some(previous_layout) = inner.vertex_data_set_layouts.get(&layout.hash()) {
220 assert_eq!(*previous_layout, *layout);
221 return;
222 }
223
224 let old = inner
225 .vertex_data_set_layouts
226 .insert(layout.hash(), layout.clone());
227 assert!(old.is_none());
228 }
229
230 #[cfg(debug_assertions)]
231 fn verify_render_target_meta_hash_unique(
232 inner: &mut GraphicsPipelineCacheInner,
233 render_target_meta: &GraphicsPipelineRenderTargetMeta,
234 ) {
235 if let Some(previous_render_target_meta) = inner
236 .render_target_metas
237 .get(&render_target_meta.render_target_meta_hash())
238 {
239 assert_eq!(*previous_render_target_meta, *render_target_meta);
240 return;
241 }
242
243 let old = inner.render_target_metas.insert(
244 render_target_meta.render_target_meta_hash(),
245 render_target_meta.clone(),
246 );
247 assert!(old.is_none());
248 }
249
250 #[profiling::function]
251 pub fn on_frame_complete(&self) {
252 let mut guard = self.inner.lock().unwrap();
253 #[cfg(debug_assertions)]
254 {
255 guard.lock_call_count_previous_frame = guard.lock_call_count + 1;
257 guard.lock_call_count = 0;
258
259 guard.pipeline_create_count_previous_frame = guard.pipeline_create_count;
260 guard.pipeline_create_count = 0;
261 }
262
263 Self::drop_stale_pipelines(&mut *guard);
266 guard.current_frame_index += 1;
267 }
268
269 pub fn get_render_phase_by_name(
270 &self,
271 name: &str,
272 ) -> Option<RenderPhaseIndex> {
273 self.render_registry.render_phase_index_from_name(name)
274 }
275
276 pub fn register_renderpass_to_phase_per_frame<T: RenderPhase>(
279 &self,
280 render_target_meta: &GraphicsPipelineRenderTargetMeta,
281 ) {
282 self.register_renderpass_to_phase_index_per_frame(
283 render_target_meta,
284 T::render_phase_index(),
285 )
286 }
287
288 pub fn register_renderpass_to_phase_index_per_frame(
289 &self,
290 render_target_meta: &GraphicsPipelineRenderTargetMeta,
291 render_phase_index: RenderPhaseIndex,
292 ) {
293 let mut guard = self.inner.lock().unwrap();
294 let inner = &mut *guard;
295 #[cfg(debug_assertions)]
296 {
297 inner.lock_call_count += 1;
298 }
299
300 Self::do_register_renderpass_to_phase_index_per_frame(
301 inner,
302 render_target_meta,
303 render_phase_index,
304 );
305 }
306
307 pub fn do_register_renderpass_to_phase_index_per_frame(
308 inner: &mut GraphicsPipelineCacheInner,
309 render_target_meta: &GraphicsPipelineRenderTargetMeta,
310 render_phase_index: RenderPhaseIndex,
311 ) {
312 assert!(render_phase_index < MAX_RENDER_PHASE_COUNT);
313 if let Some(frames_to_persist) = inner.frames_to_persist {
314 if let Some(existing) = inner.render_target_meta_assignments
315 [render_phase_index as usize]
316 .get_mut(&render_target_meta.render_target_meta_hash())
317 {
318 existing.keep_until_frame = inner.current_frame_index + frames_to_persist;
319 return;
321 }
322
323 inner.render_target_meta_assignments[render_phase_index as usize].insert(
324 render_target_meta.render_target_meta_hash(),
325 RegisteredRenderTargetMeta {
326 keep_until_frame: inner.current_frame_index + frames_to_persist,
327 meta: render_target_meta.clone(),
328 },
329 );
330 }
331
332 }
335
336 pub fn register_material_to_phase_index(
337 &self,
338 material_pass: &ResourceArc<MaterialPassResource>,
339 render_phase_index: RenderPhaseIndex,
340 ) {
341 let mut guard = self.inner.lock().unwrap();
342 let inner = &mut *guard;
343 #[cfg(debug_assertions)]
344 {
345 inner.lock_call_count += 1;
346 }
347
348 Self::do_register_material_to_phase_index(inner, material_pass, render_phase_index);
349 }
350
351 pub fn do_register_material_to_phase_index(
352 inner: &mut GraphicsPipelineCacheInner,
353 material_pass: &ResourceArc<MaterialPassResource>,
354 render_phase_index: RenderPhaseIndex,
355 ) {
356 assert!(render_phase_index < MAX_RENDER_PHASE_COUNT);
358 if let Some(existing) = inner.material_pass_assignments[render_phase_index as usize]
359 .get(&material_pass.resource_hash())
360 {
361 if existing.upgrade().is_some() {
362 return;
364 }
365 }
366
367 inner.material_pass_assignments[render_phase_index as usize]
368 .insert(material_pass.resource_hash(), material_pass.downgrade());
369 }
372
373 pub fn try_get_graphics_pipeline(
374 &self,
375 render_phase_index: Option<RenderPhaseIndex>,
376 material_pass: &ResourceArc<MaterialPassResource>,
377 render_target_meta: &GraphicsPipelineRenderTargetMeta,
378 vertex_data_set_layout: &VertexDataSetLayout,
379 ) -> Option<ResourceArc<GraphicsPipelineResource>> {
380 self.graphics_pipeline(
382 render_phase_index,
383 material_pass,
384 render_target_meta,
385 vertex_data_set_layout,
386 false,
387 )
388 .map(|x| x.unwrap())
389 }
390
391 pub fn get_or_create_graphics_pipeline(
392 &self,
393 render_phase_index: Option<RenderPhaseIndex>,
394 material_pass: &ResourceArc<MaterialPassResource>,
395 render_target_meta: &GraphicsPipelineRenderTargetMeta,
396 vertex_data_set_layout: &VertexDataSetLayout,
397 ) -> RafxResult<ResourceArc<GraphicsPipelineResource>> {
398 self.graphics_pipeline(
400 render_phase_index,
401 material_pass,
402 render_target_meta,
403 vertex_data_set_layout,
404 true,
405 )
406 .ok_or("Failed to create graphics pipeline")?
407 }
408
409 pub fn graphics_pipeline(
410 &self,
411 render_phase_index: Option<RenderPhaseIndex>,
412 material_pass: &ResourceArc<MaterialPassResource>,
413 render_target_meta: &GraphicsPipelineRenderTargetMeta,
414 vertex_data_set_layout: &VertexDataSetLayout,
415 create_if_missing: bool,
416 ) -> Option<RafxResult<ResourceArc<GraphicsPipelineResource>>> {
417 let key = CachedGraphicsPipelineKey {
418 material_pass: material_pass.resource_hash(),
419 render_target_meta_hash: render_target_meta.render_target_meta_hash(),
420 vertex_data_set_layout: vertex_data_set_layout.hash(),
421 };
422
423 let mut guard = self.inner.lock().unwrap();
424 let inner = &mut *guard;
425 #[cfg(debug_assertions)]
426 {
427 Self::verify_data_set_layout_hash_unique(inner, vertex_data_set_layout);
428 Self::verify_render_target_meta_hash_unique(inner, render_target_meta);
429 inner.lock_call_count += 1;
430 }
431
432 if let Some(render_phase_index) = render_phase_index {
433 Self::do_register_renderpass_to_phase_index_per_frame(
434 inner,
435 render_target_meta,
436 render_phase_index,
437 );
438 Self::do_register_material_to_phase_index(inner, material_pass, render_phase_index);
439 }
440
441 let keep_until_frame = inner
442 .frames_to_persist
443 .map(|x| x + inner.current_frame_index)
444 .unwrap_or(u64::MAX);
445 let cached_pipeline = inner
446 .cached_pipelines
447 .get_mut(&key)
448 .map(|x| {
449 if x.material_pass_resource.upgrade().is_some() {
450 x.keep_until_frame = x.keep_until_frame.max(keep_until_frame);
451 Some(x.graphics_pipeline.clone())
452 } else {
453 None
454 }
455 })
456 .flatten();
457
458 if cached_pipeline.is_some() {
459 cached_pipeline.map(|x| Ok(x))
460 } else if create_if_missing {
461 log::debug!("Creating graphics pipeline");
462 profiling::scope!("Create Pipeline");
463 let mut vertex_layout_buffers =
465 Vec::with_capacity(vertex_data_set_layout.bindings().len());
466 for binding in vertex_data_set_layout.bindings() {
467 vertex_layout_buffers.push(RafxVertexLayoutBuffer {
468 rate: binding.vertex_rate(),
469 stride: binding.vertex_stride() as u32,
470 })
471 }
472
473 let mut vertex_layout_attributes =
475 Vec::with_capacity(material_pass.get_raw().vertex_inputs.len());
476
477 for vertex_input in &*material_pass.get_raw().vertex_inputs {
478 let member = vertex_data_set_layout
479 .member(&vertex_input.semantic)
480 .ok_or_else(|| {
481 let error_message = format!(
482 "Vertex data does not support this material. Missing data {}",
483 vertex_input.semantic
484 );
485 log::error!("{}", error_message);
486 log::info!(
487 " required inputs:\n{:#?}",
488 material_pass.get_raw().vertex_inputs
489 );
490 log::info!(
491 " available inputs:\n{:#?}",
492 vertex_data_set_layout.members()
493 );
494 error_message
495 })
496 .ok()?;
497
498 vertex_layout_attributes.push(RafxVertexLayoutAttribute {
499 location: vertex_input.location,
500 byte_offset: member.byte_offset as u32,
501 buffer_index: member.binding as u32,
502 format: member.format,
503 hlsl_semantic: vertex_input.semantic.clone(),
504 gl_attribute_name: Some(vertex_input.gl_attribute_name.clone()),
505 });
506 }
507
508 let vertex_layout = RafxVertexLayout {
509 attributes: vertex_layout_attributes,
510 buffers: vertex_layout_buffers,
511 };
512
513 log::trace!("Creating graphics pipeline. Setting up vertex formats:");
514 log::trace!(
515 " required inputs:\n{:#?}",
516 material_pass.get_raw().vertex_inputs
517 );
518 log::trace!(
519 " available inputs:\n{:#?}",
520 vertex_data_set_layout.members()
521 );
522
523 #[cfg(debug_assertions)]
524 {
525 inner.pipeline_create_count += 1;
526 }
527
528 log::trace!("Create vertex layout {:#?}", vertex_layout);
529 let pipeline = inner.resource_lookup_set.get_or_create_graphics_pipeline(
530 &material_pass,
531 render_target_meta,
532 vertex_data_set_layout.primitive_topology(),
533 &vertex_layout,
534 );
535
536 if let Ok(pipeline) = pipeline {
537 inner.cached_pipelines.insert(
538 key,
539 CachedGraphicsPipeline {
540 graphics_pipeline: pipeline.clone(),
541 material_pass_resource: material_pass.downgrade(),
542 keep_until_frame,
543 },
544 );
545
546 Some(Ok(pipeline))
547 } else {
548 Some(pipeline)
549 }
550 } else {
551 None
552 }
553 }
554
555 pub fn precache_pipelines_for_all_phases(&self) -> RafxResult<()> {
556 let mut guard = self.inner.lock().unwrap();
557 let _inner = &mut *guard;
558 #[cfg(debug_assertions)]
559 {
560 _inner.lock_call_count += 1;
561 }
562
563 Ok(())
608 }
609
610 fn drop_stale_pipelines(inner: &mut GraphicsPipelineCacheInner) {
611 let current_frame_index = inner.current_frame_index;
612
613 if inner.frames_to_persist.is_some() {
614 for phase in &mut inner.render_target_meta_assignments {
615 phase.retain(|_k, v| v.keep_until_frame > current_frame_index);
616 }
617 }
618
619 for phase in &mut inner.material_pass_assignments {
620 phase.retain(|_k, v| v.upgrade().is_some());
621 }
622
623 let mut all_render_target_meta = FnvHashSet::default();
625 for phase in &inner.render_target_meta_assignments {
626 for key in phase.keys() {
627 all_render_target_meta.insert(key);
628 }
629 }
630
631 let frames_to_persist = inner.frames_to_persist;
632 inner.cached_pipelines.retain(|k, v| {
633 let render_target_meta_still_exists = all_render_target_meta.contains(&k.render_target_meta_hash);
634 let material_pass_still_exists = v.material_pass_resource.upgrade().is_some();
635
636 if frames_to_persist.is_none() || v.keep_until_frame > current_frame_index {
638 return true;
639 }
640
641 if !render_target_meta_still_exists || !material_pass_still_exists {
642 log::debug!(
643 "Dropping pipeline from cache, render_target_meta_still_exists: {}, material_pass_still_exists: {}",
644 render_target_meta_still_exists,
645 material_pass_still_exists
646 );
647 }
648
649 render_target_meta_still_exists && material_pass_still_exists
650 })
651 }
652
653 pub fn clear_all_pipelines(&self) {
654 let mut guard = self.inner.lock().unwrap();
655 #[cfg(debug_assertions)]
656 {
657 guard.lock_call_count += 1;
658 }
659
660 guard.cached_pipelines.clear();
661 }
662}