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
//! Frame commands: the start, per-pass and end command lists with their
//! allocators, the fence that paces the frame slots, and the timestamp queries.
use concinnity_core::render::error::RenderResult;
use concinnity_core::render::render_graph;
use windows::Win32::Graphics::Direct3D12::*;
use windows::Win32::System::Threading::CreateEventW;
use super::InitGpu;
use crate::directx::context::{
DxCommands, DxFrameSync, FRAMES, TimestampState, build_timestamp_resources,
};
use crate::directx::error::map_hresult;
pub(super) fn build_commands(gpu: &InitGpu<'_>) -> RenderResult<DxCommands> {
let hw = gpu.hw;
// Per-frame command infrastructure
let mut command_allocators = Vec::with_capacity(FRAMES);
let mut command_lists: Vec<ID3D12GraphicsCommandList> = Vec::with_capacity(FRAMES);
for _ in 0..FRAMES {
let alloc: ID3D12CommandAllocator =
// SAFETY: the create descriptor and every pointer it borrows are live for the call,
// and the new COM object lands in a binding that owns it.
unsafe { hw.device.CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT) }
.map_err(|e| map_hresult(e.code(), "command allocator"))?;
// SAFETY: the create descriptor and every pointer it borrows are live for the call, and
// the new COM object lands in a binding that owns it.
let list: ID3D12GraphicsCommandList = unsafe {
hw.device
.CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, &alloc, None)
}
.map_err(|e| map_hresult(e.code(), "command list"))?;
// Close immediately; we re-open each frame.
// SAFETY: the command list is live and in the recording state, which is what `Close`
// requires.
unsafe { list.Close() }.map_err(|e| map_hresult(e.code(), "close cmd list"))?;
command_allocators.push(alloc);
command_lists.push(list);
}
// Per-pass command allocator + cmd list pool for the parallel-
// encoding path. Sized FRAMES * PASS_COUNT so each pass owns its
// own allocator + cmd list per in-flight slot; workers reset
// their own allocator + cmd list before recording, so multiple
// workers can encode in parallel without contending. Allocators
// are very lightweight (a few KB of CPU-side bookkeeping each);
// a 21-pass x 3-frame pool is ~63 entries.
let pass_pool_size = FRAMES * render_graph::PASS_COUNT;
let mut pass_allocators: Vec<ID3D12CommandAllocator> = Vec::with_capacity(pass_pool_size);
let mut pass_cmd_lists: Vec<ID3D12GraphicsCommandList> = Vec::with_capacity(pass_pool_size);
for _ in 0..pass_pool_size {
let alloc: ID3D12CommandAllocator =
// SAFETY: the create descriptor and every pointer it borrows are live for the call,
// and the new COM object lands in a binding that owns it.
unsafe { hw.device.CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT) }
.map_err(|e| map_hresult(e.code(), "per-pass command allocator"))?;
// SAFETY: the create descriptor and every pointer it borrows are live for the call, and
// the new COM object lands in a binding that owns it.
let list: ID3D12GraphicsCommandList = unsafe {
hw.device
.CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, &alloc, None)
}
.map_err(|e| map_hresult(e.code(), "per-pass command list"))?;
// Close immediately; we re-open per-pass each frame as needed.
// SAFETY: the command list is live and in the recording state, which is what `Close`
// requires.
unsafe { list.Close() }.map_err(|e| map_hresult(e.code(), "close per-pass cmd list"))?;
pass_allocators.push(alloc);
pass_cmd_lists.push(list);
}
// End-of-frame outer cmd list pair (composite + final timestamp +
// resolve). Submitted last so its `ResolveQueryData` reads every
// per-pass `EndQuery` write.
let mut end_command_allocators: Vec<ID3D12CommandAllocator> = Vec::with_capacity(FRAMES);
let mut end_command_lists: Vec<ID3D12GraphicsCommandList> = Vec::with_capacity(FRAMES);
for _ in 0..FRAMES {
let alloc: ID3D12CommandAllocator =
// SAFETY: the create descriptor and every pointer it borrows are live for the call,
// and the new COM object lands in a binding that owns it.
unsafe { hw.device.CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT) }
.map_err(|e| map_hresult(e.code(), "end command allocator"))?;
// SAFETY: the create descriptor and every pointer it borrows are live for the call, and
// the new COM object lands in a binding that owns it.
let list: ID3D12GraphicsCommandList = unsafe {
hw.device
.CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, &alloc, None)
}
.map_err(|e| map_hresult(e.code(), "end command list"))?;
// SAFETY: the command list is live and in the recording state, which is what `Close`
// requires.
unsafe { list.Close() }.map_err(|e| map_hresult(e.code(), "close end cmd list"))?;
end_command_allocators.push(alloc);
end_command_lists.push(list);
}
Ok(DxCommands {
command_allocators,
command_lists,
pass_allocators,
pass_cmd_lists,
end_command_allocators,
end_command_lists,
})
}
pub(super) fn build_frame_sync(gpu: &InitGpu<'_>) -> RenderResult<DxFrameSync> {
// SAFETY: the create descriptor and every pointer it borrows are live for the call, and the
// new COM object lands in a binding that owns it.
let fence: ID3D12Fence = unsafe { gpu.hw.device.CreateFence(0, D3D12_FENCE_FLAG_NONE) }
.map_err(|e| map_hresult(e.code(), "create fence"))?;
// SAFETY: an auto-reset, initially unsignaled event with no name and no security
// attributes; the call borrows nothing.
let fence_event = unsafe { CreateEventW(None, false, false, None) }
.map_err(|e| map_hresult(e.code(), "create fence event"))?;
Ok(DxFrameSync {
fence,
fence_values: vec![0u64; FRAMES],
next_fence_value: std::cell::Cell::new(1),
fence_event,
})
}
// Timestamp infrastructure for the per-frame GPU time chip. Falls back
// to `None`s with frequency 0 when the queue does not support
// timestamps (every WDDM 2.0+ direct queue does, but the fallback keeps
// the rest of the overlay working on adapters that don't).
pub(super) fn build_timestamps(gpu: &InitGpu<'_>) -> TimestampState {
let (query_heap, readback, readback_ptr, frequency) = build_timestamp_resources(&gpu.hw.alloc);
TimestampState {
query_heap,
readback,
readback_ptr,
frequency,
}
}