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
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
// Runtime residency of the material-referenced world shader pipelines.
//
// Init builds a pipeline for every world Shader whose payload it decoded and
// leaves a `None` in `world_pipelines` for each one it deferred (a Shader owned
// by a scene other than the start scene). The streaming pump calls in here as
// those scenes pin and unpin, so the pipeline build lands behind the loading
// screen rather than on the frame that first draws the material.
//
// The bucket regions of the GPU-culled command buffer are issued here too: the
// cull kernel wrote every record's command into exactly one region, so each
// region is one `ExecuteIndirect` under that bucket's pipeline. Mirrors
// `metal/world_shaders.rs` + the bucket loop in `metal/draw/main.rs`.
//
// Unlike Metal there is no on-disk GPU-binary cache behind the build: see
// `docs/todos.md` for why the D3D12 pipeline-library equivalent is still open.
use windows::Win32::Graphics::Direct3D12::*;
use super::context::DxContext;
use super::init::pipelines::build_bucket_pipeline;
impl DxContext {
// Build the bindless main-pass pipeline for one shader bucket. Replaces
// whatever the bucket currently holds, so a re-pin after an eviction
// rebuilds cleanly.
pub(in crate::directx) fn install_world_shader(
&mut self,
bucket: u32,
shader: crate::gfx::backend_init::ShaderBytes<'_>,
) -> Result<(), String> {
let slot = self.world_pipeline_slot(bucket)?;
let root_sig = self
.cull
.main_bindless_root_sig
.clone()
.ok_or_else(|| "shader buckets need the bindless main pass".to_string())?;
let pso = build_bucket_pipeline(
&self.device,
self.diagnostics.info_queue.as_ref(),
&root_sig,
bucket as usize,
shader,
self.hdr.msaa_samples,
&self.bindless_main_shaders,
)?;
// A re-pin over a slot that still holds a pipeline has the same in-flight
// hazard as an evict, so retire the old one the same way.
self.evict_world_shader(bucket);
self.cull.world_pipelines[slot] = Some(pso);
Ok(())
}
// Release one bucket's pipeline. D3D12 command lists do not keep a pipeline
// state alive, so the in-flight frames that recorded against it have to
// finish before the last reference drops -- otherwise the GPU reads a freed
// pipeline and the device falls over. An evict only happens when a scene
// unpins, which is already a loading-screen stall, so draining the queue here
// costs nothing a player sees.
pub(in crate::directx) fn evict_world_shader(&mut self, bucket: u32) {
let Ok(slot) = self.world_pipeline_slot(bucket) else {
return;
};
if self.cull.world_pipelines[slot].is_none() {
return;
}
self.wait_idle();
self.cull.world_pipelines[slot] = None;
}
// Whether a bucket's draws can render this frame: bucket 0 is the world
// default program, every other bucket needs its pipeline installed.
pub(in crate::directx) fn world_shader_resident(&self, bucket: usize) -> bool {
bucket == 0
|| matches!(
self.cull.world_pipelines.get(bucket.wrapping_sub(1)),
Some(Some(_))
)
}
pub(in crate::directx) fn world_pipeline(&self, bucket: usize) -> Option<&ID3D12PipelineState> {
self.cull
.world_pipelines
.get(bucket.checked_sub(1)?)?
.as_ref()
}
// Issue the bucket 1.. regions of `indirect`, each under its own material
// shader's pipeline. Bucket 0's region is issued by the caller (it runs under
// the pipeline the pass already bound), so this covers only the
// material-referenced shaders. `max_count` is the record prefix each region
// draws, matching bucket 0's. Returns the number of `ExecuteIndirect` calls
// issued, and leaves the last bucket's pipeline bound.
pub(in crate::directx) fn execute_bucket_regions(
&self,
cmd: &ID3D12GraphicsCommandList,
cull_sig: &ID3D12CommandSignature,
indirect: &ID3D12Resource,
max_count: u32,
) -> u32 {
self.for_each_resident_bucket(|bucket| {
let Some(pso) = self.world_pipeline(bucket) else {
return;
};
// SAFETY: the command list is in the recording state, and every resource, descriptor
// and slice these commands name is live for the call.
unsafe { cmd.SetPipelineState(pso) };
self.execute_bucket_region(cmd, cull_sig, indirect, max_count, bucket);
})
}
// Issue the bucket 1.. regions of `indirect` under the pipeline the caller
// already bound. Used by the depth / velocity pre-pass, which shades nothing
// and so runs every bucket through its own single pipeline -- but still has to
// skip a non-resident bucket, or the pre-pass would lay down depth and motion
// for geometry the colour pass omits.
pub(in crate::directx) fn execute_bucket_regions_shared_pso(
&self,
cmd: &ID3D12GraphicsCommandList,
cull_sig: &ID3D12CommandSignature,
indirect: &ID3D12Resource,
max_count: u32,
) -> u32 {
self.for_each_resident_bucket(|bucket| {
self.execute_bucket_region(cmd, cull_sig, indirect, max_count, bucket)
})
}
// Run `f` for every bucket past the default whose Shader is resident,
// returning how many ran. A bucket whose scene has not pinned yet has no
// pipeline: skip it until warmup builds one rather than drawing it with the
// wrong program.
fn for_each_resident_bucket(&self, mut f: impl FnMut(usize)) -> u32 {
let mut issued = 0;
for bucket in 1..self.shader_bucket_count() {
if !self.world_shader_resident(bucket) {
continue;
}
f(bucket);
issued += 1;
}
issued
}
fn execute_bucket_region(
&self,
cmd: &ID3D12GraphicsCommandList,
cull_sig: &ID3D12CommandSignature,
indirect: &ID3D12Resource,
max_count: u32,
bucket: usize,
) {
// SAFETY: the command list is in the recording state, and every resource, descriptor and
// slice these commands name is live for the call.
unsafe {
cmd.ExecuteIndirect(
cull_sig,
max_count,
indirect,
self.bucket_region_offset(bucket),
None::<&ID3D12Resource>,
0,
);
}
}
fn world_pipeline_slot(&self, bucket: u32) -> Result<usize, String> {
let slot = (bucket as usize)
.checked_sub(1)
.ok_or_else(|| "shader bucket 0 is the world default program".to_string())?;
if slot >= self.cull.world_pipelines.len() {
return Err(format!(
"shader bucket {bucket} is past the world's {} shader pipeline(s)",
self.cull.world_pipelines.len()
));
}
Ok(slot)
}
}