1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
// src/directx/wireframe.rs
//
// Wireframe view-mode pipeline variants. D3D12 fill mode lives in the PSO, so
// unlike Metal (where it is one encoder flag every inherited indirect draw
// picks up) the mode needs a second pipeline per main-pass path. They are built
// on the first wireframe frame and dropped 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 root signature,
// so the one twin stands in for every bucket's PSO while the mode is on, as
// Metal's encoder-state fill mode does.
use windows::Win32::Graphics::Direct3D12::*;
use super::context::{DxContext, dump_on_err};
use super::init::pipelines::create_main_pso_wireframe;
use super::texture::HDR_FORMAT;
// The Wireframe twin of the GPU-driven main PSO. `None` means the pass it
// mirrors is not live either (or the build failed), in which case the pass
// keeps the solid PSO.
#[derive(Default)]
pub(super) struct DxWireframe {
pub(super) bindless: Option<ID3D12PipelineState>,
// Set once a build has run so a failure is not retried every frame.
built: bool,
}
impl DxContext {
// 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, so the `&self` pass encoders can just read them.
pub(super) fn ensure_wireframe_pipelines(&mut self) {
if self.view.mode != concinnity_core::gfx::view_modes::ViewMode::Wireframe
|| self.wireframe.built
{
return;
}
self.wireframe.built = true;
if let Err(e) = self.build_wireframe_pipelines() {
tracing::warn!("wireframe view: {e}; falling back to solid fill");
}
}
// Drop the built twin so the next wireframe frame rebuilds it against the
// current shaders. Called wherever a main-pass pipeline is rebuilt
// (shader hot-reload, world shader swap).
pub(super) fn invalidate_wireframe_pipelines(&mut self) {
self.wireframe = DxWireframe::default();
}
fn build_wireframe_pipelines(&mut self) -> Result<(), String> {
let device = self.device.clone();
let iq = self.diagnostics.info_queue.clone();
let msaa = self.hdr.msaa_samples;
let mut built = DxWireframe {
built: true,
..Default::default()
};
// The engine's pair is retained on the context past init (the
// world-shader bucket rebuild reads it), so this is a straight PSO create.
if let Some(root_sig) = self.cull.main_bindless_root_sig.as_ref() {
let vs = &self.bindless_main_shaders.vs;
let ps = &self.bindless_main_shaders.ps;
built.bindless = Some(dump_on_err(
iq.as_ref(),
create_main_pso_wireframe(&device, root_sig, vs, ps, HDR_FORMAT, msaa),
)?);
}
self.wireframe = built;
Ok(())
}
// 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::directx) fn wireframe_or<'a>(
&'a self,
solid: &'a ID3D12PipelineState,
twin: Option<&'a ID3D12PipelineState>,
) -> &'a ID3D12PipelineState {
match twin {
Some(w) if self.view.mode == concinnity_core::gfx::view_modes::ViewMode::Wireframe => w,
_ => solid,
}
}
}