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
//! Command recording infrastructure: the context's command pool, the
//! timestamp query reset, the per-frame and per-pass command buffers, and the
//! frame sync objects.
use ash::vk;
use concinnity_core::render::error::RenderResult;
use concinnity_core::render::pass_timing;
use concinnity_core::render::render_graph;
use super::InitGpu;
use crate::vulkan::context::{SwapchainState, VkCommands, VkFrameSync, VkHardware};
use crate::vulkan::owned::VkDevice;
pub(super) fn create_command_pool(hw: &VkHardware) -> RenderResult<vk::CommandPool> {
let info = vk::CommandPoolCreateInfo::default()
.flags(vk::CommandPoolCreateFlags::RESET_COMMAND_BUFFER)
.queue_family_index(hw.graphics_family);
// SAFETY: the create-info and every slice it borrows are live for the call, and each
// handle it names belongs to this device.
let command_pool = unsafe { hw.device.create_command_pool(&info, None) }
.map_err(|e| crate::vulkan::error::map_vk_result(e, "command pool"))?;
Ok(command_pool)
}
// Initial reset of every timestamp query slot. Without this the first
// `vkGetQueryPoolResults` call on each slot (before that slot has
// ever been written) hits an uninitialized query and the validation
// layer emits a "query not reset" error. After the reset, the slot
// is in "unavailable" state, so `get_query_pool_results` returns
// NOT_READY → 0 cleanly until `record_frame` writes the first pair.
pub(super) fn reset_timestamp_queries(gpu: &InitGpu<'_>) -> RenderResult<()> {
let InitGpu {
hw,
command_pool,
frames,
..
} = *gpu;
let device = &hw.device;
if let Some(pool) = hw.timestamp_query_pool {
crate::vulkan::texture::one_shot_submit(
device,
command_pool,
hw.graphics_queue,
// SAFETY: `cmd` is a command buffer in the recording state, and every handle and
// slice these commands name is live for the call.
|cmd| unsafe {
device.cmd_reset_query_pool(
cmd,
pool,
0,
(pass_timing::SLOTS_PER_FRAME * frames) as u32,
);
},
)?;
}
Ok(())
}
pub(super) fn build_frame_commands(
gpu: &InitGpu<'_>,
swapchain: &SwapchainState,
) -> RenderResult<(VkCommands, VkFrameSync)> {
let InitGpu {
hw,
command_pool,
frames,
..
} = *gpu;
let (device, graphics_family) = (&hw.device, hw.graphics_family);
let alloc_info = vk::CommandBufferAllocateInfo::default()
.command_pool(command_pool)
.level(vk::CommandBufferLevel::PRIMARY)
.command_buffer_count(frames as u32);
// SAFETY: the create-info and every slice it borrows are live for the call, and each handle
// it names belongs to this device.
let command_buffers = unsafe { device.allocate_command_buffers(&alloc_info) }
.map_err(|e| crate::vulkan::error::map_vk_result(e, "allocate command buffers"))?;
// Parallel command-buffer recording: a `start` outer buffer per frame
// (leading timestamp) plus one command pool + primary buffer per
// (frame, pass) slot. Vulkan command pools are externally
// synchronized, so every slot gets its own pool - the rayon workers in
// `execute_graph` never share a pool. `RESET_COMMAND_BUFFER` so each
// buffer can be reset + re-recorded per frame (the per-frame
// `in_flight` fence gates reuse). Indexed `frame * PASS_COUNT + pass`.
let pass_pool_flags =
vk::CommandPoolCreateFlags::RESET_COMMAND_BUFFER | vk::CommandPoolCreateFlags::TRANSIENT;
let make_pool_with_buffer =
|device: &VkDevice| -> RenderResult<(vk::CommandPool, vk::CommandBuffer)> {
// SAFETY: the create-info and every slice it borrows are live for the call, and
// each handle it names belongs to this device.
let pool = unsafe {
device.create_command_pool(
&vk::CommandPoolCreateInfo::default()
.flags(pass_pool_flags)
.queue_family_index(graphics_family),
None,
)
}
.map_err(|e| crate::vulkan::error::map_vk_result(e, "per-pass command pool"))?;
// SAFETY: the create-info and every slice it borrows are live for the call, and
// each handle it names belongs to this device.
let buf = unsafe {
device.allocate_command_buffers(
&vk::CommandBufferAllocateInfo::default()
.command_pool(pool)
.level(vk::CommandBufferLevel::PRIMARY)
.command_buffer_count(1),
)
}
.map_err(|e| crate::vulkan::error::map_vk_result(e, "per-pass command buffer"))?[0];
Ok((pool, buf))
};
let mut start_command_pools = Vec::with_capacity(frames);
let mut start_command_buffers = Vec::with_capacity(frames);
for _ in 0..frames {
let (pool, buf) = make_pool_with_buffer(device)?;
start_command_pools.push(pool);
start_command_buffers.push(buf);
}
let pass_pool_count = frames * render_graph::PASS_COUNT;
let mut pass_command_pools = Vec::with_capacity(pass_pool_count);
let mut pass_command_buffers = Vec::with_capacity(pass_pool_count);
for _ in 0..pass_pool_count {
let (pool, buf) = make_pool_with_buffer(device)?;
pass_command_pools.push(pool);
pass_command_buffers.push(buf);
}
// `image_available` + `in_flight` are per-frame-in-flight. The
// render-finished semaphore is signaled by submit and waited on by
// present, so it must be one-per-swapchain-image (indexed by the
// acquired image index): a per-frame semaphore can still be queued
// for presentation when its frame slot comes round again.
let sem_info = vk::SemaphoreCreateInfo::default();
let fence_info = vk::FenceCreateInfo::default().flags(vk::FenceCreateFlags::SIGNALED);
let mut image_available = Vec::with_capacity(frames);
let mut in_flight = Vec::with_capacity(frames);
let mut render_finished = Vec::with_capacity(swapchain.images.len());
for _ in 0..swapchain.images.len() {
render_finished.push(
// SAFETY: the create-info and every slice it borrows are live for the call, and
// each handle it names belongs to this device.
unsafe { device.create_semaphore(&sem_info, None) }
.map_err(|e| crate::vulkan::error::map_vk_result(e, "semaphore"))?,
);
}
for _ in 0..frames {
image_available.push(
// SAFETY: the create-info and every slice it borrows are live for the call, and
// each handle it names belongs to this device.
unsafe { device.create_semaphore(&sem_info, None) }
.map_err(|e| crate::vulkan::error::map_vk_result(e, "semaphore"))?,
);
in_flight.push(
// SAFETY: the create-info and every slice it borrows are live for the call, and
// each handle it names belongs to this device.
unsafe { device.create_fence(&fence_info, None) }
.map_err(|e| crate::vulkan::error::map_vk_result(e, "fence"))?,
);
}
Ok((
VkCommands {
command_pool,
command_buffers,
start_command_pools,
start_command_buffers,
pass_command_pools,
pass_command_buffers,
},
VkFrameSync {
image_available,
render_finished,
in_flight,
},
))
}