concinnity-device 0.19.119

GPU backends (Metal, Vulkan, DirectX) behind a device facade for Concinnity
//! Wireframe view-mode pipeline variants. Without
//! `VK_EXT_extended_dynamic_state3` the polygon mode lives in the pipeline, so
//! unlike Metal (where it is one encoder flag) the mode needs a second pipeline
//! per main-pass path. They are built on the first wireframe frame and destroyed
//! whenever the shaders they were built from are rebuilt, so a shipped runtime
//! never pays for them.
//!
//! The twin renders the engine's own bindless pair rather than a world Shader's:
//! a world fragment is free to ignore the fill mode's intent and the edges only
//! need to be visible. Every shader bucket shares the bindless pipeline layout,
//! so the one twin stands in for every bucket's pipeline while the mode is on,
//! as Metal's encoder-state fill mode does. A device without `fillModeNonSolid`
//! gets no twin and keeps solid fill.

use super::context::VkContext;
use super::pipeline::{MeshPipelineTargets, create_main_pipeline_wireframe};
use crate::vulkan::owned::OwnedPipeline;
use concinnity_core::render::error::RenderResult;

// The Wireframe twin of the GPU-driven main pipeline. `None` means the pass
// it mirrors is not live either (or the build failed), in which case the pass
// keeps the solid pipeline.
#[derive(Default)]
pub(super) struct VkWireframe {
    pub(super) bindless: Option<OwnedPipeline>,
    // Set once a build has run so a failure is not retried every frame.
    built: bool,
}

impl VkWireframe {
    // Retire every built pipeline, leaving the set unbuilt.
    pub(super) fn destroy(&mut self) {
        *self = VkWireframe::default();
    }
}

impl VkContext {
    // Build the wireframe twin if the view mode needs it and it is not built
    // yet. Called from `draw_frame` before the frame is
    // recorded.
    pub(super) fn ensure_wireframe_pipelines(&mut self) {
        if self.state.view.mode != concinnity_core::gfx::view_modes::ViewMode::Wireframe
            || self.wireframe.built
        {
            return;
        }
        self.wireframe.built = true;
        // `VK_POLYGON_MODE_LINE` is only legal with `fillModeNonSolid`; without
        // it the mode falls back to solid fill rather than failing the frame.
        // SAFETY: a property query on a live handle; it only reads.
        let supported = unsafe {
            self.hw
                .instance
                .get_physical_device_features(self.hw.physical_device)
        };
        if supported.fill_mode_non_solid == 0 {
            tracing::warn!("wireframe view: device lacks fillModeNonSolid; using solid fill");
            return;
        }
        if let Err(e) = self.build_wireframe_pipelines() {
            tracing::warn!("wireframe view: {e}; falling back to solid fill");
        }
    }

    // Destroy the built twin so the next wireframe frame rebuilds it against
    // the current shaders. Called wherever the main-pass pipeline is rebuilt
    // (shader hot-reload, world shader swap) and at teardown.
    pub(super) fn invalidate_wireframe_pipelines(&mut self) {
        self.wireframe.destroy();
    }

    fn build_wireframe_pipelines(&mut self) -> RenderResult<()> {
        let device = self.hw.device.clone();
        let msaa = self.targets.msaa_samples;
        let format = self.swapchain.format;
        let render_pass = self.targets.main_render_pass.handle();
        let mut built = VkWireframe {
            built: true,
            ..Default::default()
        };
        let mut build = || -> RenderResult<()> {
            if let (Some(_), Some(layout)) = (
                self.cull.bindless_pipeline.as_ref(),
                self.cull.bindless_pipeline_layout.as_ref(),
            ) {
                // The engine's pair, retained on the context past init.
                let (vs, fs) = (
                    &self.cull.bindless_main_spv.vert,
                    &self.cull.bindless_main_spv.frag,
                );
                built.bindless = Some(create_main_pipeline_wireframe(
                    &device,
                    MeshPipelineTargets {
                        render_pass,
                        layout: layout.handle(),
                        vert_spv: vs,
                        frag_spv: fs,
                    },
                    msaa,
                    format,
                )?);
            }

            Ok(())
        };
        match build() {
            Ok(()) => {
                self.wireframe = built;
                Ok(())
            }
            Err(e) => {
                // Keep `built` set so the failure is not retried every frame.
                built.destroy();
                built.built = true;
                self.wireframe = built;
                Err(e)
            }
        }
    }

    // The pipeline the main pass should bind in place of `solid` this frame:
    // the wireframe twin while that view mode is active and the twin built,
    // else the solid pipeline itself.
    pub(in crate::vulkan) fn wireframe_or<'a>(
        &self,
        solid: &'a OwnedPipeline,
        twin: Option<&'a OwnedPipeline>,
    ) -> &'a OwnedPipeline {
        match twin {
            Some(w)
                if self.state.view.mode
                    == concinnity_core::gfx::view_modes::ViewMode::Wireframe =>
            {
                w
            }
            _ => solid,
        }
    }
}