rafx_framework/resources/
pipeline_cache.rs

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//TODO: Allow caching for N frames
16//TODO: Return a kind of ResourceArc for a cached pipeline. Allow dropping after N frames pass with
17// nothing request/using it
18//TODO: vulkan pipeline cache object
19
20//TODO: Remove Serialize/Deserialize
21#[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    // index by render phase index
107    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        // 0 keeps to end of current frame
158        // 1 keeps to end of next frame
159        // None keeps pipelines forever
160        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            // add one for this call
256            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        // This is just removing from cache, not destroying. Resource lookup will keep them alive
264        // for however long is necessary
265        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    // Register a renderpass as being part of a particular phase. This will a pipeline is created
277    // for all appropriate renderpass/material pass pairs.
278    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                // Nothing to do here, the previous ref is still valid
320                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        //TODO: Do we need to mark this as a dirty renderpass that may need to build additional
333        // pipelines?
334    }
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        // May be caused by not registering a render phase before using it
357        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                // Nothing to do here, the previous ref is still valid
363                return;
364            }
365        }
366
367        inner.material_pass_assignments[render_phase_index as usize]
368            .insert(material_pass.resource_hash(), material_pass.downgrade());
369        //TODO: Do we need to mark this as a dirty material that may need to build additional
370        // pipelines?
371    }
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        // RafxResult is always Ok if returning cached pipelines
381        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        // graphics_pipeline never returns none if create_if_missing is true
399        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 binding_descriptions = Vec::default();
464            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 attribute_descriptions = Vec::default();
474            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        //TODO: Avoid iterating everything all the time
564        //TODO: This will have to be reworked to include vertex layout as part of the key. Current
565        // plan is to register vertex types in code with the registry and have materials reference
566        // them by name
567        /*
568        for render_phase_index in 0..MAX_RENDER_PHASE_COUNT {
569            for (render_target_meta, renderpass) in
570                &inner.render_target_meta_assignments[render_phase_index as usize]
571            {
572                for (material_pass_hash, material_pass) in
573                    &inner.material_pass_assignments[render_phase_index as usize]
574                {
575                    let key = CachedGraphicsPipelineKey {
576                        renderpass: *renderpass_hash,
577                        material_pass: *material_pass_hash,
578                    };
579
580                    if !inner.cached_pipelines.contains_key(&key) {
581                        if let Some(renderpass) = renderpass.renderpass.upgrade() {
582                            if let Some(material_pass) = material_pass.upgrade() {
583                                #[cfg(debug_assertions)]
584                                {
585                                    guard.pipeline_create_count += 1;
586                                }
587
588                                let pipeline = inner
589                                    .resource_lookup_set
590                                    .get_or_create_graphics_pipeline(&material_pass, &renderpass)?;
591                                inner.cached_pipelines.insert(
592                                    key,
593                                    CachedGraphicsPipeline {
594                                        graphics_pipeline: pipeline,
595                                        renderpass_resource: renderpass.downgrade(),
596                                        material_pass_resource: material_pass.downgrade(),
597                                    },
598                                );
599                            }
600                        }
601                    }
602                }
603            }
604        }
605        */
606
607        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        //TODO: Could do something smarter than this to track when the last one is dropped
624        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            // Never drop a pipeline if it has been recently used
637            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}