atagen

monolith

public
Star0
Watch0
a1f375a8b4bed2954bd4bb4deb4dc6a6efeeb217
1
ssh://brewery@pls.lobotomise.me:33/atagen/monolith.git

Blame: crates/render-vk/src/pipeline.rs

824bf02b6e55atagen2026-10-16 09:15 UTC1
use crate::Error;
824bf02b6e55atagen2026-10-16 09:15 UTC2
use crate::device::Shared;
43e0e8e33c3eatagen2026-10-19 07:35 UTC3
use crate::format::{SUPPORTED, vk_format};
824bf02b6e55atagen2026-10-16 09:15 UTC4
use ash::vk;
824bf02b6e55atagen2026-10-16 09:15 UTC5
use std::collections::HashMap;
43e0e8e33c3eatagen2026-10-19 07:35 UTC6
use std::path::PathBuf;
824bf02b6e55atagen2026-10-16 09:15 UTC7
use std::sync::Arc;
43e0e8e33c3eatagen2026-10-19 07:35 UTC8
use std::sync::atomic::{AtomicU32, Ordering};
43e0e8e33c3eatagen2026-10-19 07:35 UTC9
use std::time::Instant;
824bf02b6e55atagen2026-10-16 09:15 UTC10
824bf02b6e55atagen2026-10-16 09:15 UTC11
/// Push constants shared by every shader, see `shaders/push.glsl`.
824bf02b6e55atagen2026-10-16 09:15 UTC12
/// `dst` is the quad in normalized device coordinates as (x, y, w, h),
824bf02b6e55atagen2026-10-16 09:15 UTC13
/// `uv` the texture coordinate at each corner (TL, TR, BL, BR).
824bf02b6e55atagen2026-10-16 09:15 UTC14
#[repr(C)]
824bf02b6e55atagen2026-10-16 09:15 UTC15
#[derive(Clone, Copy, Debug, Default)]
824bf02b6e55atagen2026-10-16 09:15 UTC16
pub(crate) struct Push {
824bf02b6e55atagen2026-10-16 09:15 UTC17
    pub color: [f32; 4],
824bf02b6e55atagen2026-10-16 09:15 UTC18
    pub dst: [f32; 4],
824bf02b6e55atagen2026-10-16 09:15 UTC19
    pub uv: [[f32; 2]; 4],
96db497859dcatagen2026-10-16 11:27 UTC20
    /// Transition progress and kind, read only by `shaders/effect.frag`.
96db497859dcatagen2026-10-16 11:27 UTC21
    pub effect: [f32; 4],
47259c39793fatagen2026-10-17 10:13 UTC22
    /// See `shaders/clip.glsl`. A zero width turns clipping off.
47259c39793fatagen2026-10-17 10:13 UTC23
    pub clip: [f32; 4],
47259c39793fatagen2026-10-17 10:13 UTC24
    pub radii: [f32; 4],
47259c39793fatagen2026-10-17 10:13 UTC25
    pub ring: [f32; 4],
824bf02b6e55atagen2026-10-16 09:15 UTC26
}
824bf02b6e55atagen2026-10-16 09:15 UTC27
824bf02b6e55atagen2026-10-16 09:15 UTC28
impl Push {
824bf02b6e55atagen2026-10-16 09:15 UTC29
    pub fn bytes(&self) -> &[u8] {
35d6f20f8eb6atagen2026-10-17 05:07 UTC30
        // SAFETY: `Push` is `repr(C)` and all f32, so it has no padding bytes.
824bf02b6e55atagen2026-10-16 09:15 UTC31
        unsafe { std::slice::from_raw_parts((self as *const Self).cast::<u8>(), size_of::<Self>()) }
824bf02b6e55atagen2026-10-16 09:15 UTC32
    }
824bf02b6e55atagen2026-10-16 09:15 UTC33
}
824bf02b6e55atagen2026-10-16 09:15 UTC34
824bf02b6e55atagen2026-10-16 09:15 UTC35
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
824bf02b6e55atagen2026-10-16 09:15 UTC36
pub(crate) enum Kind {
824bf02b6e55atagen2026-10-16 09:15 UTC37
    Solid,
824bf02b6e55atagen2026-10-16 09:15 UTC38
    Texture,
96db497859dcatagen2026-10-16 11:27 UTC39
    Effect,
824bf02b6e55atagen2026-10-16 09:15 UTC40
}
824bf02b6e55atagen2026-10-16 09:15 UTC41
824bf02b6e55atagen2026-10-16 09:15 UTC42
pub(crate) struct Pipelines {
824bf02b6e55atagen2026-10-16 09:15 UTC43
    shared: Arc<Shared>,
824bf02b6e55atagen2026-10-16 09:15 UTC44
    pub set_layout: vk::DescriptorSetLayout,
824bf02b6e55atagen2026-10-16 09:15 UTC45
    pub layout: vk::PipelineLayout,
824bf02b6e55atagen2026-10-16 09:15 UTC46
    vert: vk::ShaderModule,
824bf02b6e55atagen2026-10-16 09:15 UTC47
    solid: vk::ShaderModule,
824bf02b6e55atagen2026-10-16 09:15 UTC48
    texture: vk::ShaderModule,
96db497859dcatagen2026-10-16 11:27 UTC49
    effect: vk::ShaderModule,
c0caa658decfatagen2026-10-19 07:37 UTC50
    cache: HashMap<(vk::Format, Kind, bool), vk::Pipeline>,
43e0e8e33c3eatagen2026-10-19 07:35 UTC51
    vk_cache: vk::PipelineCache,
43e0e8e33c3eatagen2026-10-19 07:35 UTC52
    cache_path: Option<PathBuf>,
43e0e8e33c3eatagen2026-10-19 07:35 UTC53
    loaded_len: usize,
824bf02b6e55atagen2026-10-16 09:15 UTC54
}
824bf02b6e55atagen2026-10-16 09:15 UTC55
824bf02b6e55atagen2026-10-16 09:15 UTC56
const QUAD_VERT: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/quad.vert.spv"));
824bf02b6e55atagen2026-10-16 09:15 UTC57
const SOLID_FRAG: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/solid.frag.spv"));
824bf02b6e55atagen2026-10-16 09:15 UTC58
const TEXTURE_FRAG: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/texture.frag.spv"));
96db497859dcatagen2026-10-16 11:27 UTC59
const EFFECT_FRAG: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/effect.frag.spv"));
824bf02b6e55atagen2026-10-16 09:15 UTC60
824bf02b6e55atagen2026-10-16 09:15 UTC61
impl Pipelines {
824bf02b6e55atagen2026-10-16 09:15 UTC62
    pub fn new(shared: &Arc<Shared>) -> Result<Self, Error> {
824bf02b6e55atagen2026-10-16 09:15 UTC63
        let d = &shared.device.device;
824bf02b6e55atagen2026-10-16 09:15 UTC64
        let bindings = [vk::DescriptorSetLayoutBinding::default()
824bf02b6e55atagen2026-10-16 09:15 UTC65
            .binding(0)
824bf02b6e55atagen2026-10-16 09:15 UTC66
            .descriptor_type(vk::DescriptorType::COMBINED_IMAGE_SAMPLER)
824bf02b6e55atagen2026-10-16 09:15 UTC67
            .descriptor_count(1)
824bf02b6e55atagen2026-10-16 09:15 UTC68
            .stage_flags(vk::ShaderStageFlags::FRAGMENT)];
824bf02b6e55atagen2026-10-16 09:15 UTC69
        let set_layout = unsafe {
824bf02b6e55atagen2026-10-16 09:15 UTC70
            d.create_descriptor_set_layout(
824bf02b6e55atagen2026-10-16 09:15 UTC71
                &vk::DescriptorSetLayoutCreateInfo::default()
824bf02b6e55atagen2026-10-16 09:15 UTC72
                    .flags(vk::DescriptorSetLayoutCreateFlags::PUSH_DESCRIPTOR_KHR)
824bf02b6e55atagen2026-10-16 09:15 UTC73
                    .bindings(&bindings),
824bf02b6e55atagen2026-10-16 09:15 UTC74
                None,
824bf02b6e55atagen2026-10-16 09:15 UTC75
            )?
824bf02b6e55atagen2026-10-16 09:15 UTC76
        };
824bf02b6e55atagen2026-10-16 09:15 UTC77
        let ranges = [vk::PushConstantRange::default()
824bf02b6e55atagen2026-10-16 09:15 UTC78
            .stage_flags(vk::ShaderStageFlags::VERTEX | vk::ShaderStageFlags::FRAGMENT)
824bf02b6e55atagen2026-10-16 09:15 UTC79
            .size(size_of::<Push>() as u32)];
824bf02b6e55atagen2026-10-16 09:15 UTC80
        let set_layouts = [set_layout];
824bf02b6e55atagen2026-10-16 09:15 UTC81
        let layout = unsafe {
824bf02b6e55atagen2026-10-16 09:15 UTC82
            d.create_pipeline_layout(
824bf02b6e55atagen2026-10-16 09:15 UTC83
                &vk::PipelineLayoutCreateInfo::default()
824bf02b6e55atagen2026-10-16 09:15 UTC84
                    .set_layouts(&set_layouts)
824bf02b6e55atagen2026-10-16 09:15 UTC85
                    .push_constant_ranges(&ranges),
824bf02b6e55atagen2026-10-16 09:15 UTC86
                None,
824bf02b6e55atagen2026-10-16 09:15 UTC87
            )?
824bf02b6e55atagen2026-10-16 09:15 UTC88
        };
824bf02b6e55atagen2026-10-16 09:15 UTC89
        let module = |code: &[u8]| -> Result<vk::ShaderModule, Error> {
824bf02b6e55atagen2026-10-16 09:15 UTC90
            let words = ash::util::read_spv(&mut std::io::Cursor::new(code))
824bf02b6e55atagen2026-10-16 09:15 UTC91
                .map_err(|_| vk::Result::ERROR_INVALID_SHADER_NV)?;
824bf02b6e55atagen2026-10-16 09:15 UTC92
            Ok(unsafe {
824bf02b6e55atagen2026-10-16 09:15 UTC93
                d.create_shader_module(&vk::ShaderModuleCreateInfo::default().code(&words), None)?
824bf02b6e55atagen2026-10-16 09:15 UTC94
            })
824bf02b6e55atagen2026-10-16 09:15 UTC95
        };
43e0e8e33c3eatagen2026-10-19 07:35 UTC96
        let cache_path = cache_path();
43e0e8e33c3eatagen2026-10-19 07:35 UTC97
        let props = unsafe {
43e0e8e33c3eatagen2026-10-19 07:35 UTC98
            shared
43e0e8e33c3eatagen2026-10-19 07:35 UTC99
                .device
43e0e8e33c3eatagen2026-10-19 07:35 UTC100
                .instance
43e0e8e33c3eatagen2026-10-19 07:35 UTC101
                .get_physical_device_properties(shared.device.physical)
43e0e8e33c3eatagen2026-10-19 07:35 UTC102
        };
43e0e8e33c3eatagen2026-10-19 07:35 UTC103
        let loaded = cache_path
43e0e8e33c3eatagen2026-10-19 07:35 UTC104
            .as_ref()
43e0e8e33c3eatagen2026-10-19 07:35 UTC105
            .and_then(|p| std::fs::read(p).ok())
43e0e8e33c3eatagen2026-10-19 07:35 UTC106
            .filter(|data| header_matches(data, &props))
43e0e8e33c3eatagen2026-10-19 07:35 UTC107
            .unwrap_or_default();
43e0e8e33c3eatagen2026-10-19 07:35 UTC108
        let create = |data: &[u8]| unsafe {
43e0e8e33c3eatagen2026-10-19 07:35 UTC109
            d.create_pipeline_cache(
43e0e8e33c3eatagen2026-10-19 07:35 UTC110
                &vk::PipelineCacheCreateInfo::default().initial_data(data),
43e0e8e33c3eatagen2026-10-19 07:35 UTC111
                None,
43e0e8e33c3eatagen2026-10-19 07:35 UTC112
            )
43e0e8e33c3eatagen2026-10-19 07:35 UTC113
        };
43e0e8e33c3eatagen2026-10-19 07:35 UTC114
        let vk_cache = create(&loaded).or_else(|_| create(&[])).unwrap_or_default();
824bf02b6e55atagen2026-10-16 09:15 UTC115
        Ok(Self {
824bf02b6e55atagen2026-10-16 09:15 UTC116
            shared: shared.clone(),
824bf02b6e55atagen2026-10-16 09:15 UTC117
            set_layout,
824bf02b6e55atagen2026-10-16 09:15 UTC118
            layout,
824bf02b6e55atagen2026-10-16 09:15 UTC119
            vert: module(QUAD_VERT)?,
824bf02b6e55atagen2026-10-16 09:15 UTC120
            solid: module(SOLID_FRAG)?,
824bf02b6e55atagen2026-10-16 09:15 UTC121
            texture: module(TEXTURE_FRAG)?,
96db497859dcatagen2026-10-16 11:27 UTC122
            effect: module(EFFECT_FRAG)?,
824bf02b6e55atagen2026-10-16 09:15 UTC123
            cache: HashMap::new(),
43e0e8e33c3eatagen2026-10-19 07:35 UTC124
            vk_cache,
43e0e8e33c3eatagen2026-10-19 07:35 UTC125
            cache_path,
43e0e8e33c3eatagen2026-10-19 07:35 UTC126
            loaded_len: loaded.len(),
824bf02b6e55atagen2026-10-16 09:15 UTC127
        })
824bf02b6e55atagen2026-10-16 09:15 UTC128
    }
824bf02b6e55atagen2026-10-16 09:15 UTC129
43e0e8e33c3eatagen2026-10-19 07:35 UTC130
    /// Compiles the pipelines a frame draws with for every render format, so
43e0e8e33c3eatagen2026-10-19 07:35 UTC131
    /// the first spawn animation does not stall on the driver compiler.
43e0e8e33c3eatagen2026-10-19 07:35 UTC132
    pub fn warm(&mut self) {
43e0e8e33c3eatagen2026-10-19 07:35 UTC133
        let began = Instant::now();
43e0e8e33c3eatagen2026-10-19 07:35 UTC134
        for fourcc in SUPPORTED {
43e0e8e33c3eatagen2026-10-19 07:35 UTC135
            let Some(format) = vk_format(fourcc) else {
43e0e8e33c3eatagen2026-10-19 07:35 UTC136
                continue;
43e0e8e33c3eatagen2026-10-19 07:35 UTC137
            };
c0caa658decfatagen2026-10-19 07:37 UTC138
            for (kind, opaque) in [
c0caa658decfatagen2026-10-19 07:37 UTC139
                (Kind::Solid, false),
c0caa658decfatagen2026-10-19 07:37 UTC140
                (Kind::Solid, true),
c0caa658decfatagen2026-10-19 07:37 UTC141
                (Kind::Texture, false),
c0caa658decfatagen2026-10-19 07:37 UTC142
                (Kind::Texture, true),
c0caa658decfatagen2026-10-19 07:37 UTC143
                (Kind::Effect, false),
c0caa658decfatagen2026-10-19 07:37 UTC144
            ] {
c0caa658decfatagen2026-10-19 07:37 UTC145
                if let Err(e) = self.get(format.format, kind, opaque) {
c0caa658decfatagen2026-10-19 07:37 UTC146
                    tracing::warn!(?kind, opaque, "cannot warm a pipeline: {e}");
43e0e8e33c3eatagen2026-10-19 07:35 UTC147
                    return;
43e0e8e33c3eatagen2026-10-19 07:35 UTC148
                }
43e0e8e33c3eatagen2026-10-19 07:35 UTC149
            }
43e0e8e33c3eatagen2026-10-19 07:35 UTC150
        }
43e0e8e33c3eatagen2026-10-19 07:35 UTC151
        tracing::debug!(elapsed = ?began.elapsed(), "warmed pipelines");
43e0e8e33c3eatagen2026-10-19 07:35 UTC152
    }
43e0e8e33c3eatagen2026-10-19 07:35 UTC153
43e0e8e33c3eatagen2026-10-19 07:35 UTC154
    fn save_cache(&self) {
43e0e8e33c3eatagen2026-10-19 07:35 UTC155
        let Some(path) = &self.cache_path else {
43e0e8e33c3eatagen2026-10-19 07:35 UTC156
            return;
43e0e8e33c3eatagen2026-10-19 07:35 UTC157
        };
43e0e8e33c3eatagen2026-10-19 07:35 UTC158
        if self.vk_cache == vk::PipelineCache::null() {
43e0e8e33c3eatagen2026-10-19 07:35 UTC159
            return;
43e0e8e33c3eatagen2026-10-19 07:35 UTC160
        }
43e0e8e33c3eatagen2026-10-19 07:35 UTC161
        let Ok(data) = (unsafe {
43e0e8e33c3eatagen2026-10-19 07:35 UTC162
            self.shared
43e0e8e33c3eatagen2026-10-19 07:35 UTC163
                .device
43e0e8e33c3eatagen2026-10-19 07:35 UTC164
                .device
43e0e8e33c3eatagen2026-10-19 07:35 UTC165
                .get_pipeline_cache_data(self.vk_cache)
43e0e8e33c3eatagen2026-10-19 07:35 UTC166
        }) else {
43e0e8e33c3eatagen2026-10-19 07:35 UTC167
            return;
43e0e8e33c3eatagen2026-10-19 07:35 UTC168
        };
43e0e8e33c3eatagen2026-10-19 07:35 UTC169
        if data.len() == self.loaded_len {
43e0e8e33c3eatagen2026-10-19 07:35 UTC170
            return;
43e0e8e33c3eatagen2026-10-19 07:35 UTC171
        }
43e0e8e33c3eatagen2026-10-19 07:35 UTC172
        static WRITERS: AtomicU32 = AtomicU32::new(0);
43e0e8e33c3eatagen2026-10-19 07:35 UTC173
        let tmp = path.with_extension(format!(
43e0e8e33c3eatagen2026-10-19 07:35 UTC174
            "{}.{}.tmp",
43e0e8e33c3eatagen2026-10-19 07:35 UTC175
            std::process::id(),
43e0e8e33c3eatagen2026-10-19 07:35 UTC176
            WRITERS.fetch_add(1, Ordering::Relaxed)
43e0e8e33c3eatagen2026-10-19 07:35 UTC177
        ));
43e0e8e33c3eatagen2026-10-19 07:35 UTC178
        let written = path
43e0e8e33c3eatagen2026-10-19 07:35 UTC179
            .parent()
43e0e8e33c3eatagen2026-10-19 07:35 UTC180
            .map_or(Ok(()), std::fs::create_dir_all)
43e0e8e33c3eatagen2026-10-19 07:35 UTC181
            .and_then(|()| std::fs::write(&tmp, &data))
43e0e8e33c3eatagen2026-10-19 07:35 UTC182
            .and_then(|()| std::fs::rename(&tmp, path));
43e0e8e33c3eatagen2026-10-19 07:35 UTC183
        if let Err(e) = written {
43e0e8e33c3eatagen2026-10-19 07:35 UTC184
            tracing::debug!("cannot save the pipeline cache: {e}");
43e0e8e33c3eatagen2026-10-19 07:35 UTC185
            let _ = std::fs::remove_file(&tmp);
43e0e8e33c3eatagen2026-10-19 07:35 UTC186
        }
43e0e8e33c3eatagen2026-10-19 07:35 UTC187
    }
43e0e8e33c3eatagen2026-10-19 07:35 UTC188
c0caa658decfatagen2026-10-19 07:37 UTC189
    /// The pipeline for `kind` into `format`. An `opaque` one replaces the
c0caa658decfatagen2026-10-19 07:37 UTC190
    /// destination instead of blending, for draws known to cover it.
c0caa658decfatagen2026-10-19 07:37 UTC191
    pub fn get(
c0caa658decfatagen2026-10-19 07:37 UTC192
        &mut self,
c0caa658decfatagen2026-10-19 07:37 UTC193
        format: vk::Format,
c0caa658decfatagen2026-10-19 07:37 UTC194
        kind: Kind,
c0caa658decfatagen2026-10-19 07:37 UTC195
        opaque: bool,
c0caa658decfatagen2026-10-19 07:37 UTC196
    ) -> Result<vk::Pipeline, Error> {
c0caa658decfatagen2026-10-19 07:37 UTC197
        if let Some(&p) = self.cache.get(&(format, kind, opaque)) {
824bf02b6e55atagen2026-10-16 09:15 UTC198
            return Ok(p);
824bf02b6e55atagen2026-10-16 09:15 UTC199
        }
824bf02b6e55atagen2026-10-16 09:15 UTC200
        let d = &self.shared.device.device;
824bf02b6e55atagen2026-10-16 09:15 UTC201
        let frag = match kind {
824bf02b6e55atagen2026-10-16 09:15 UTC202
            Kind::Solid => self.solid,
824bf02b6e55atagen2026-10-16 09:15 UTC203
            Kind::Texture => self.texture,
96db497859dcatagen2026-10-16 11:27 UTC204
            Kind::Effect => self.effect,
824bf02b6e55atagen2026-10-16 09:15 UTC205
        };
824bf02b6e55atagen2026-10-16 09:15 UTC206
        let stages = [
824bf02b6e55atagen2026-10-16 09:15 UTC207
            vk::PipelineShaderStageCreateInfo::default()
824bf02b6e55atagen2026-10-16 09:15 UTC208
                .stage(vk::ShaderStageFlags::VERTEX)
824bf02b6e55atagen2026-10-16 09:15 UTC209
                .module(self.vert)
824bf02b6e55atagen2026-10-16 09:15 UTC210
                .name(c"main"),
824bf02b6e55atagen2026-10-16 09:15 UTC211
            vk::PipelineShaderStageCreateInfo::default()
824bf02b6e55atagen2026-10-16 09:15 UTC212
                .stage(vk::ShaderStageFlags::FRAGMENT)
824bf02b6e55atagen2026-10-16 09:15 UTC213
                .module(frag)
824bf02b6e55atagen2026-10-16 09:15 UTC214
                .name(c"main"),
824bf02b6e55atagen2026-10-16 09:15 UTC215
        ];
824bf02b6e55atagen2026-10-16 09:15 UTC216
        let vertex = vk::PipelineVertexInputStateCreateInfo::default();
824bf02b6e55atagen2026-10-16 09:15 UTC217
        let assembly = vk::PipelineInputAssemblyStateCreateInfo::default()
824bf02b6e55atagen2026-10-16 09:15 UTC218
            .topology(vk::PrimitiveTopology::TRIANGLE_STRIP);
824bf02b6e55atagen2026-10-16 09:15 UTC219
        let viewport = vk::PipelineViewportStateCreateInfo::default()
824bf02b6e55atagen2026-10-16 09:15 UTC220
            .viewport_count(1)
824bf02b6e55atagen2026-10-16 09:15 UTC221
            .scissor_count(1);
824bf02b6e55atagen2026-10-16 09:15 UTC222
        let raster = vk::PipelineRasterizationStateCreateInfo::default()
824bf02b6e55atagen2026-10-16 09:15 UTC223
            .polygon_mode(vk::PolygonMode::FILL)
824bf02b6e55atagen2026-10-16 09:15 UTC224
            .cull_mode(vk::CullModeFlags::NONE)
824bf02b6e55atagen2026-10-16 09:15 UTC225
            .line_width(1.0);
824bf02b6e55atagen2026-10-16 09:15 UTC226
        let multisample = vk::PipelineMultisampleStateCreateInfo::default()
824bf02b6e55atagen2026-10-16 09:15 UTC227
            .rasterization_samples(vk::SampleCountFlags::TYPE_1);
824bf02b6e55atagen2026-10-16 09:15 UTC228
        let attachments = [vk::PipelineColorBlendAttachmentState::default()
c0caa658decfatagen2026-10-19 07:37 UTC229
            .blend_enable(!opaque)
824bf02b6e55atagen2026-10-16 09:15 UTC230
            .src_color_blend_factor(vk::BlendFactor::ONE)
824bf02b6e55atagen2026-10-16 09:15 UTC231
            .dst_color_blend_factor(vk::BlendFactor::ONE_MINUS_SRC_ALPHA)
824bf02b6e55atagen2026-10-16 09:15 UTC232
            .color_blend_op(vk::BlendOp::ADD)
824bf02b6e55atagen2026-10-16 09:15 UTC233
            .src_alpha_blend_factor(vk::BlendFactor::ONE)
824bf02b6e55atagen2026-10-16 09:15 UTC234
            .dst_alpha_blend_factor(vk::BlendFactor::ONE_MINUS_SRC_ALPHA)
824bf02b6e55atagen2026-10-16 09:15 UTC235
            .alpha_blend_op(vk::BlendOp::ADD)
824bf02b6e55atagen2026-10-16 09:15 UTC236
            .color_write_mask(vk::ColorComponentFlags::RGBA)];
824bf02b6e55atagen2026-10-16 09:15 UTC237
        let blend = vk::PipelineColorBlendStateCreateInfo::default().attachments(&attachments);
824bf02b6e55atagen2026-10-16 09:15 UTC238
        let dynamic_states = [vk::DynamicState::VIEWPORT, vk::DynamicState::SCISSOR];
824bf02b6e55atagen2026-10-16 09:15 UTC239
        let dynamic = vk::PipelineDynamicStateCreateInfo::default().dynamic_states(&dynamic_states);
824bf02b6e55atagen2026-10-16 09:15 UTC240
        let formats = [format];
824bf02b6e55atagen2026-10-16 09:15 UTC241
        let mut rendering =
824bf02b6e55atagen2026-10-16 09:15 UTC242
            vk::PipelineRenderingCreateInfo::default().color_attachment_formats(&formats);
824bf02b6e55atagen2026-10-16 09:15 UTC243
        let info = vk::GraphicsPipelineCreateInfo::default()
824bf02b6e55atagen2026-10-16 09:15 UTC244
            .stages(&stages)
824bf02b6e55atagen2026-10-16 09:15 UTC245
            .vertex_input_state(&vertex)
824bf02b6e55atagen2026-10-16 09:15 UTC246
            .input_assembly_state(&assembly)
824bf02b6e55atagen2026-10-16 09:15 UTC247
            .viewport_state(&viewport)
824bf02b6e55atagen2026-10-16 09:15 UTC248
            .rasterization_state(&raster)
824bf02b6e55atagen2026-10-16 09:15 UTC249
            .multisample_state(&multisample)
824bf02b6e55atagen2026-10-16 09:15 UTC250
            .color_blend_state(&blend)
824bf02b6e55atagen2026-10-16 09:15 UTC251
            .dynamic_state(&dynamic)
824bf02b6e55atagen2026-10-16 09:15 UTC252
            .layout(self.layout)
824bf02b6e55atagen2026-10-16 09:15 UTC253
            .push_next(&mut rendering);
43e0e8e33c3eatagen2026-10-19 07:35 UTC254
        let pipeline = unsafe { d.create_graphics_pipelines(self.vk_cache, &[info], None) }
43e0e8e33c3eatagen2026-10-19 07:35 UTC255
            .map_err(|(_, e)| e)?[0];
c0caa658decfatagen2026-10-19 07:37 UTC256
        self.cache.insert((format, kind, opaque), pipeline);
824bf02b6e55atagen2026-10-16 09:15 UTC257
        Ok(pipeline)
824bf02b6e55atagen2026-10-16 09:15 UTC258
    }
824bf02b6e55atagen2026-10-16 09:15 UTC259
}
824bf02b6e55atagen2026-10-16 09:15 UTC260
824bf02b6e55atagen2026-10-16 09:15 UTC261
impl Drop for Pipelines {
824bf02b6e55atagen2026-10-16 09:15 UTC262
    fn drop(&mut self) {
43e0e8e33c3eatagen2026-10-19 07:35 UTC263
        self.save_cache();
824bf02b6e55atagen2026-10-16 09:15 UTC264
        let d = &self.shared.device.device;
824bf02b6e55atagen2026-10-16 09:15 UTC265
        unsafe {
824bf02b6e55atagen2026-10-16 09:15 UTC266
            for (_, p) in self.cache.drain() {
824bf02b6e55atagen2026-10-16 09:15 UTC267
                d.destroy_pipeline(p, None);
824bf02b6e55atagen2026-10-16 09:15 UTC268
            }
43e0e8e33c3eatagen2026-10-19 07:35 UTC269
            d.destroy_pipeline_cache(self.vk_cache, None);
824bf02b6e55atagen2026-10-16 09:15 UTC270
            d.destroy_shader_module(self.vert, None);
824bf02b6e55atagen2026-10-16 09:15 UTC271
            d.destroy_shader_module(self.solid, None);
824bf02b6e55atagen2026-10-16 09:15 UTC272
            d.destroy_shader_module(self.texture, None);
96db497859dcatagen2026-10-16 11:27 UTC273
            d.destroy_shader_module(self.effect, None);
824bf02b6e55atagen2026-10-16 09:15 UTC274
            d.destroy_pipeline_layout(self.layout, None);
824bf02b6e55atagen2026-10-16 09:15 UTC275
            d.destroy_descriptor_set_layout(self.set_layout, None);
824bf02b6e55atagen2026-10-16 09:15 UTC276
        }
824bf02b6e55atagen2026-10-16 09:15 UTC277
    }
824bf02b6e55atagen2026-10-16 09:15 UTC278
}
43e0e8e33c3eatagen2026-10-19 07:35 UTC279
43e0e8e33c3eatagen2026-10-19 07:35 UTC280
fn cache_path() -> Option<PathBuf> {
43e0e8e33c3eatagen2026-10-19 07:35 UTC281
    let base = std::env::var_os("XDG_CACHE_HOME")
43e0e8e33c3eatagen2026-10-19 07:35 UTC282
        .filter(|v| !v.is_empty())
43e0e8e33c3eatagen2026-10-19 07:35 UTC283
        .map(PathBuf::from)
43e0e8e33c3eatagen2026-10-19 07:35 UTC284
        .or_else(|| std::env::var_os("HOME").map(|home| PathBuf::from(home).join(".cache")))?;
43e0e8e33c3eatagen2026-10-19 07:35 UTC285
    Some(base.join("monolith").join("pipelines.bin"))
43e0e8e33c3eatagen2026-10-19 07:35 UTC286
}
43e0e8e33c3eatagen2026-10-19 07:35 UTC287
43e0e8e33c3eatagen2026-10-19 07:35 UTC288
/// Whether `data` opens with a cache header written by this driver on this
43e0e8e33c3eatagen2026-10-19 07:35 UTC289
/// device. The spec lets a driver reject a foreign cache but some crash on
43e0e8e33c3eatagen2026-10-19 07:35 UTC290
/// one instead, so the header is checked first.
43e0e8e33c3eatagen2026-10-19 07:35 UTC291
fn header_matches(data: &[u8], props: &vk::PhysicalDeviceProperties) -> bool {
43e0e8e33c3eatagen2026-10-19 07:35 UTC292
    let word = |at: usize| {
43e0e8e33c3eatagen2026-10-19 07:35 UTC293
        data.get(at..at + 4)
43e0e8e33c3eatagen2026-10-19 07:35 UTC294
            .and_then(|b| b.try_into().ok())
43e0e8e33c3eatagen2026-10-19 07:35 UTC295
            .map(u32::from_ne_bytes)
43e0e8e33c3eatagen2026-10-19 07:35 UTC296
    };
43e0e8e33c3eatagen2026-10-19 07:35 UTC297
    word(0).is_some_and(|size| size >= 32 && size as usize <= data.len())
43e0e8e33c3eatagen2026-10-19 07:35 UTC298
        && word(4) == Some(vk::PipelineCacheHeaderVersion::ONE.as_raw() as u32)
43e0e8e33c3eatagen2026-10-19 07:35 UTC299
        && word(8) == Some(props.vendor_id)
43e0e8e33c3eatagen2026-10-19 07:35 UTC300
        && word(12) == Some(props.device_id)
43e0e8e33c3eatagen2026-10-19 07:35 UTC301
        && data.get(16..32) == Some(&props.pipeline_cache_uuid[..])
43e0e8e33c3eatagen2026-10-19 07:35 UTC302
}
43e0e8e33c3eatagen2026-10-19 07:35 UTC303
43e0e8e33c3eatagen2026-10-19 07:35 UTC304
#[cfg(test)]
43e0e8e33c3eatagen2026-10-19 07:35 UTC305
mod tests {
43e0e8e33c3eatagen2026-10-19 07:35 UTC306
    use super::*;
43e0e8e33c3eatagen2026-10-19 07:35 UTC307
43e0e8e33c3eatagen2026-10-19 07:35 UTC308
    fn props() -> vk::PhysicalDeviceProperties {
43e0e8e33c3eatagen2026-10-19 07:35 UTC309
        vk::PhysicalDeviceProperties {
43e0e8e33c3eatagen2026-10-19 07:35 UTC310
            vendor_id: 0x10de,
43e0e8e33c3eatagen2026-10-19 07:35 UTC311
            device_id: 0x2487,
43e0e8e33c3eatagen2026-10-19 07:35 UTC312
            pipeline_cache_uuid: [7; 16],
43e0e8e33c3eatagen2026-10-19 07:35 UTC313
            ..Default::default()
43e0e8e33c3eatagen2026-10-19 07:35 UTC314
        }
43e0e8e33c3eatagen2026-10-19 07:35 UTC315
    }
43e0e8e33c3eatagen2026-10-19 07:35 UTC316
43e0e8e33c3eatagen2026-10-19 07:35 UTC317
    fn header(vendor: u32, uuid: u8) -> Vec<u8> {
43e0e8e33c3eatagen2026-10-19 07:35 UTC318
        let words = [32u32, 1, vendor, 0x2487];
43e0e8e33c3eatagen2026-10-19 07:35 UTC319
        let mut data: Vec<u8> = words.iter().flat_map(|w| w.to_ne_bytes()).collect();
43e0e8e33c3eatagen2026-10-19 07:35 UTC320
        data.extend([uuid; 16]);
43e0e8e33c3eatagen2026-10-19 07:35 UTC321
        data
43e0e8e33c3eatagen2026-10-19 07:35 UTC322
    }
43e0e8e33c3eatagen2026-10-19 07:35 UTC323
43e0e8e33c3eatagen2026-10-19 07:35 UTC324
    #[test]
43e0e8e33c3eatagen2026-10-19 07:35 UTC325
    fn cache_header_must_match_the_device_and_driver() {
43e0e8e33c3eatagen2026-10-19 07:35 UTC326
        assert!(header_matches(&header(0x10de, 7), &props()));
43e0e8e33c3eatagen2026-10-19 07:35 UTC327
        assert!(!header_matches(&header(0x1002, 7), &props()));
43e0e8e33c3eatagen2026-10-19 07:35 UTC328
        assert!(!header_matches(&header(0x10de, 8), &props()));
43e0e8e33c3eatagen2026-10-19 07:35 UTC329
        assert!(!header_matches(&header(0x10de, 7)[..20], &props()));
43e0e8e33c3eatagen2026-10-19 07:35 UTC330
        assert!(!header_matches(&[], &props()));
43e0e8e33c3eatagen2026-10-19 07:35 UTC331
    }
43e0e8e33c3eatagen2026-10-19 07:35 UTC332
}