use std::sync::{
Arc, OnceLock,
atomic::{AtomicBool, Ordering},
};
use cranpose_core::collections::map::HashMap;
use smallvec::SmallVec;
use crate::{
lazy_resource::Pending,
pipeline_compiler::{CompileLane, CompilerSend, CompilerSync, PipelineCompiler},
render::{ShapePipelineKey, create_shape_pipeline},
run_store::RunBufferMode,
shared_shader::SharedShader,
};
#[derive(Clone)]
pub(crate) struct ShapePipelineFactory {
pub(crate) device: Arc<wgpu::Device>,
pub(crate) cache: Option<wgpu::PipelineCache>,
pub(crate) format: wgpu::TextureFormat,
pub(crate) shader: SharedShader,
pub(crate) mode: RunBufferMode,
}
pub(crate) trait KeyedBuild: CompilerSend + CompilerSync + 'static {
type Output: CompilerSend + CompilerSync + 'static;
fn build(&self, key: ShapePipelineKey) -> Self::Output;
fn landed(_output: &Self::Output) -> bool {
true
}
}
impl KeyedBuild for ShapePipelineFactory {
type Output = wgpu::RenderPipeline;
fn landed(pipeline: &wgpu::RenderPipeline) -> bool {
pipeline.is_ready()
}
fn build(&self, key: ShapePipelineKey) -> wgpu::RenderPipeline {
create_shape_pipeline(
&self.device,
self.cache.as_ref(),
self.format,
&self.shader,
key,
self.mode,
)
}
}
pub(crate) struct ShapePipelines<B: KeyedBuild = ShapePipelineFactory> {
slots: Slots<B>,
stands_in: bool,
background_builds: bool,
draws_when_ready: bool,
recorder: crate::pipeline_recorder::PipelineRecorder,
}
impl<B: KeyedBuild> ShapePipelines<B> {
pub(crate) fn new(
factory: B,
backend: wgpu::Backend,
compiler: &PipelineCompiler,
first_screen: impl IntoIterator<Item = ShapePipelineKey>,
recorder: crate::pipeline_recorder::PipelineRecorder,
) -> Self {
let mut slots = Slots::new(compiler, factory);
for key in first_screen {
slots.warm(key);
}
Self {
slots,
stands_in: backend == wgpu::Backend::BrowserWebGpu,
background_builds: false,
draws_when_ready: compiler.is_active(),
recorder,
}
}
#[cfg_attr(not(any(test, target_arch = "wasm32")), expect(dead_code))]
pub(crate) fn build_in_background(&mut self) {
self.background_builds = true;
}
pub(crate) fn begin_frame(&mut self) {
self.slots.begin_frame();
}
#[cfg(not(target_arch = "wasm32"))]
pub(crate) fn warm(&mut self, key: ShapePipelineKey) {
self.slots.warm(key);
}
pub(crate) fn warm_later(
&mut self,
key: ShapePipelineKey,
awaited: &mut impl FnMut(Pending<B::Output>),
) {
for key in std::iter::once(key).chain(key.interior()) {
if let Some(pending) = self.slots.warm_on(key, CompileLane::Later) {
awaited(pending);
}
}
}
pub(crate) fn build_wanted(&mut self) {
if self.background_builds {
self.slots.build_wanted();
}
}
pub(crate) fn build_ahead(&mut self, key: ShapePipelineKey) {
let key = if self.stands_in { key.universal() } else { key };
self.slots.build(key);
}
pub(crate) fn ensure(&mut self, key: ShapePipelineKey) {
let need = self.slots.need(key);
if need.first {
self.recorder
.note_shape(key.to_bits(), self.recorder.in_first_screen());
}
if need.ready && (!self.stands_in || self.slots.get(key).is_some_and(B::landed)) {
return;
}
if self.draws_when_ready {
self.slots.warm(key);
return;
}
self.recorder.during_demand(|| {
if self.stands_in && key != key.universal() {
self.slots.build(key.universal());
self.slots.want(key);
return;
}
self.slots.build(key);
});
}
pub(crate) fn get(&self, key: ShapePipelineKey) -> Option<(&B::Output, bool)> {
let stand_in = if self.stands_in {
key.universal()
} else {
key.general()
};
let general = || self.slots.get(stand_in).map(|pipeline| (pipeline, true));
match self.slots.get(key) {
Some(pipeline) if !self.stands_in || B::landed(pipeline) => Some((pipeline, false)),
Some(pipeline) => general().or(Some((pipeline, false))),
None => general(),
}
}
}
#[derive(Default)]
pub(crate) struct PipelineDemand(SmallVec<[ShapePipelineKey; 8]>);
impl PipelineDemand {
pub(crate) fn add(&mut self, key: ShapePipelineKey) {
if !self.0.contains(&key) {
self.0.push(key);
}
}
pub(crate) fn keys(&self) -> impl Iterator<Item = ShapePipelineKey> + '_ {
self.0.iter().copied()
}
pub(crate) fn drain(&mut self) -> impl Iterator<Item = ShapePipelineKey> + '_ {
self.0.drain(..)
}
}
pub(crate) struct Need {
pub(crate) ready: bool,
pub(crate) first: bool,
}
struct Entry<T> {
value: Arc<OnceLock<T>>,
needed: bool,
queued: bool,
}
pub(crate) struct Slots<B: KeyedBuild> {
entries: HashMap<ShapePipelineKey, Entry<B::Output>>,
builder: Arc<B>,
compiler: PipelineCompiler,
wanted: PipelineDemand,
stopped: Arc<AtomicBool>,
}
impl<B: KeyedBuild> Slots<B> {
pub(crate) fn new(compiler: &PipelineCompiler, builder: B) -> Self {
Self {
entries: HashMap::default(),
builder: Arc::new(builder),
compiler: compiler.clone(),
wanted: PipelineDemand::default(),
stopped: Arc::new(AtomicBool::new(false)),
}
}
fn entry(
entries: &mut HashMap<ShapePipelineKey, Entry<B::Output>>,
key: ShapePipelineKey,
) -> &mut Entry<B::Output> {
entries.entry(key).or_insert_with(|| Entry {
value: Arc::new(OnceLock::new()),
needed: false,
queued: false,
})
}
pub(crate) fn need(&mut self, key: ShapePipelineKey) -> Need {
let entry = Self::entry(&mut self.entries, key);
let first = !entry.needed;
entry.needed = true;
Need {
ready: entry.value.get().is_some(),
first,
}
}
pub(crate) fn warm(&mut self, key: ShapePipelineKey) {
self.warm_on(key, CompileLane::WarmUp);
}
pub(crate) fn warm_on(
&mut self,
key: ShapePipelineKey,
lane: CompileLane,
) -> Option<Pending<B::Output>> {
if !self.compiler.is_active() {
return None;
}
let builder = Arc::clone(&self.builder);
let stopped = Arc::clone(&self.stopped);
let entry = Self::entry(&mut self.entries, key);
if entry.value.get().is_some() {
return None;
}
let value = Arc::clone(&entry.value);
if !std::mem::replace(&mut entry.queued, true) {
let building = Arc::clone(&value);
self.compiler.enqueue(lane, move || {
if !stopped.load(Ordering::Acquire) {
building.get_or_init(|| builder.build(key));
}
});
}
Some(Pending(value))
}
pub(crate) fn build(&mut self, key: ShapePipelineKey) {
Self::entry(&mut self.entries, key)
.value
.get_or_init(|| self.builder.build(key));
}
pub(crate) fn get(&self, key: ShapePipelineKey) -> Option<&B::Output> {
self.entries.get(&key)?.value.get()
}
pub(crate) fn want(&mut self, key: ShapePipelineKey) {
self.wanted.add(key);
}
pub(crate) fn build_wanted(&mut self) {
for key in self.wanted.drain() {
Self::entry(&mut self.entries, key)
.value
.get_or_init(|| self.builder.build(key));
}
}
pub(crate) fn begin_frame(&mut self) {
self.wanted.0.clear();
}
}
impl<B: KeyedBuild> Drop for Slots<B> {
fn drop(&mut self) {
self.stopped.store(true, Ordering::Release);
}
}
#[cfg(all(test, not(target_arch = "wasm32")))]
#[path = "tests/shape_pipelines_slots_tests.rs"]
mod slots_tests;
#[cfg(test)]
#[path = "tests/shape_pipelines_tests.rs"]
mod tests;
#[cfg(all(test, not(target_arch = "wasm32")))]
#[path = "tests/shape_pipelines_stand_in_tests.rs"]
mod stand_in_tests;