1 //
2 // Copyright 2020 Pixar
3 //
4 // Licensed under the Apache License, Version 2.0 (the "Apache License")
5 // with the following modification; you may not use this file except in
6 // compliance with the Apache License and the following modification to it:
7 // Section 6. Trademarks. is deleted and replaced with:
8 //
9 // 6. Trademarks. This License does not grant permission to use the trade
10 // names, trademarks, service marks, or product names of the Licensor
11 // and its affiliates, except as required to comply with Section 4(c) of
12 // the License and to reproduce the content of the NOTICE file.
13 //
14 // You may obtain a copy of the Apache License at
15 //
16 // http://www.apache.org/licenses/LICENSE-2.0
17 //
18 // Unless required by applicable law or agreed to in writing, software
19 // distributed under the Apache License with the above modification is
20 // distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
21 // KIND, either express or implied. See the Apache License for the specific
22 // language governing permissions and limitations under the Apache License.
23 //
24 #include "pxr/imaging/hgiVulkan/commandBuffer.h"
25 #include "pxr/imaging/hgiVulkan/device.h"
26 #include "pxr/imaging/hgiVulkan/diagnostic.h"
27 #include "pxr/imaging/hgiVulkan/vulkan.h"
28 #include "pxr/base/tf/diagnostic.h"
29 #include <string>
30
31 PXR_NAMESPACE_OPEN_SCOPE
32
33
HgiVulkanCommandBuffer(HgiVulkanDevice * device,VkCommandPool pool)34 HgiVulkanCommandBuffer::HgiVulkanCommandBuffer(
35 HgiVulkanDevice* device,
36 VkCommandPool pool)
37 : _device(device)
38 , _vkCommandPool(pool)
39 , _vkCommandBuffer(nullptr)
40 , _vkFence(nullptr)
41 , _vkSemaphore(nullptr)
42 , _isInFlight(false)
43 , _isSubmitted(false)
44 , _inflightId(0)
45 {
46 VkDevice vkDevice = _device->GetVulkanDevice();
47
48 // Create vulkan command buffer
49 VkCommandBufferAllocateInfo allocInfo =
50 {VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO};
51 allocInfo.commandBufferCount = 1;
52 allocInfo.commandPool = pool;
53 allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
54
55 TF_VERIFY(
56 vkAllocateCommandBuffers(
57 vkDevice,
58 &allocInfo,
59 &_vkCommandBuffer) == VK_SUCCESS
60 );
61
62 // Assign a debug label to command buffer
63 uint64_t cmdBufHandle = (uint64_t)_vkCommandBuffer;
64 std::string handleStr = std::to_string(cmdBufHandle);
65 std::string cmdBufLbl = "HgiVulkan Command Buffer " + handleStr;
66
67 HgiVulkanSetDebugName(
68 _device,
69 cmdBufHandle,
70 VK_OBJECT_TYPE_COMMAND_BUFFER,
71 cmdBufLbl.c_str());
72
73 // CPU synchronization fence. So we known when the cmd buffer can be reused.
74 VkFenceCreateInfo fenceInfo = {VK_STRUCTURE_TYPE_FENCE_CREATE_INFO};
75 fenceInfo.flags = 0; // Unsignaled starting state
76
77 TF_VERIFY(
78 vkCreateFence(
79 vkDevice,
80 &fenceInfo,
81 HgiVulkanAllocator(),
82 &_vkFence) == VK_SUCCESS
83 );
84
85 // Create semaphore for GPU-GPU synchronization
86 VkSemaphoreCreateInfo semaCreateInfo =
87 {VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO};
88 TF_VERIFY(
89 vkCreateSemaphore(
90 vkDevice,
91 &semaCreateInfo,
92 HgiVulkanAllocator(),
93 &_vkSemaphore) == VK_SUCCESS
94 );
95
96 // Assign a debug label to fence.
97 std::string fenceLbl = "HgiVulkan Fence for Command Buffer: " + handleStr;
98 HgiVulkanSetDebugName(
99 _device,
100 (uint64_t)_vkFence,
101 VK_OBJECT_TYPE_FENCE,
102 fenceLbl.c_str());
103 }
104
~HgiVulkanCommandBuffer()105 HgiVulkanCommandBuffer::~HgiVulkanCommandBuffer()
106 {
107 VkDevice vkDevice = _device->GetVulkanDevice();
108 vkDestroySemaphore(vkDevice, _vkSemaphore, HgiVulkanAllocator());
109 vkDestroyFence(vkDevice, _vkFence, HgiVulkanAllocator());
110 vkFreeCommandBuffers(vkDevice, _vkCommandPool, 1, &_vkCommandBuffer);
111 }
112
113 void
BeginCommandBuffer(uint8_t inflightId)114 HgiVulkanCommandBuffer::BeginCommandBuffer(uint8_t inflightId)
115 {
116 if (!_isInFlight) {
117
118 VkCommandBufferBeginInfo beginInfo =
119 {VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO};
120 beginInfo.flags |= VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
121
122 TF_VERIFY(
123 vkBeginCommandBuffer(_vkCommandBuffer, &beginInfo) == VK_SUCCESS
124 );
125
126 _inflightId = inflightId;
127 _isInFlight = true;
128 }
129 }
130
131 bool
IsInFlight() const132 HgiVulkanCommandBuffer::IsInFlight() const
133 {
134 return _isInFlight;
135 }
136
137 void
EndCommandBuffer()138 HgiVulkanCommandBuffer::EndCommandBuffer()
139 {
140 if (_isInFlight) {
141 TF_VERIFY(
142 vkEndCommandBuffer(_vkCommandBuffer) == VK_SUCCESS
143 );
144
145 _isSubmitted = true;
146 }
147 }
148
149 bool
ResetIfConsumedByGPU()150 HgiVulkanCommandBuffer::ResetIfConsumedByGPU()
151 {
152 // Command buffer is already available (previously reset).
153 // We do not have to test the fence or reset the cmd buffer.
154 if (!_isInFlight) {
155 return false;
156 }
157
158 // The command buffer is still recording. We should not test its fence until
159 // we have submitted the command buffer to the queue (vulkan requirement).
160 if (!_isSubmitted) {
161 return false;
162 }
163
164 VkDevice vkDevice = _device->GetVulkanDevice();
165
166 // Check the fence to see if the GPU has consumed the command buffer.
167 // We cannnot reuse a command buffer until the GPU is finished with it.
168 if (vkGetFenceStatus(vkDevice, _vkFence) == VK_NOT_READY){
169 return false;
170 }
171
172 // GPU is done with command buffer, execute the custom fns the client wants
173 // to see executed when cmd buf is consumed.
174 RunAndClearCompletedHandlers();
175
176 // GPU is done with command buffer, reset fence and command buffer.
177 TF_VERIFY(
178 vkResetFences(vkDevice, 1, &_vkFence) == VK_SUCCESS
179 );
180
181 // It might be more efficient to reset the cmd pool instead of individual
182 // command buffers. But we may not have a clear 'StartFrame' / 'EndFrame'
183 // sequence in Hydra. If we did, we could reset the command pool(s) during
184 // Beginframe. Instead we choose to reset each command buffer when it has
185 // been consumed by the GPU.
186
187 VkCommandBufferResetFlags flags = _GetCommandBufferResetFlags();
188 TF_VERIFY(
189 vkResetCommandBuffer(_vkCommandBuffer, flags) == VK_SUCCESS
190 );
191
192 // Command buffer may now be reused for new recordings / resource creation.
193 _isInFlight = false;
194 _isSubmitted = false;
195 return true;
196 }
197
198 VkCommandBuffer
GetVulkanCommandBuffer() const199 HgiVulkanCommandBuffer::GetVulkanCommandBuffer() const
200 {
201 return _vkCommandBuffer;
202 }
203
204 VkCommandPool
GetVulkanCommandPool() const205 HgiVulkanCommandBuffer::GetVulkanCommandPool() const
206 {
207 return _vkCommandPool;
208 }
209
210 VkFence
GetVulkanFence() const211 HgiVulkanCommandBuffer::GetVulkanFence() const
212 {
213 return _vkFence;
214 }
215
216 VkSemaphore
GetVulkanSemaphore() const217 HgiVulkanCommandBuffer::GetVulkanSemaphore() const
218 {
219 return _vkSemaphore;
220 }
221
222 uint8_t
GetInflightId() const223 HgiVulkanCommandBuffer::GetInflightId() const
224 {
225 return _inflightId;
226 }
227
228 HgiVulkanDevice*
GetDevice() const229 HgiVulkanCommandBuffer::GetDevice() const
230 {
231 return _device;
232 }
233
234 void
MemoryBarrier(HgiMemoryBarrier barrier)235 HgiVulkanCommandBuffer::MemoryBarrier(HgiMemoryBarrier barrier)
236 {
237 if (!_vkCommandBuffer) {
238 return;
239 }
240
241 VkMemoryBarrier memoryBarrier = {VK_STRUCTURE_TYPE_MEMORY_BARRIER};
242
243 // XXX Flush / stall and invalidate all caches (big hammer!).
244 // Ideally we would set more fine-grained barriers, but we
245 // currently do not get enough information from Hgi to
246 // know what src or dst access there is or what images or
247 // buffers might be affected.
248 TF_VERIFY(barrier==HgiMemoryBarrierAll, "Unsupported barrier");
249
250 // Who might be generating the data we are interested in reading.
251 memoryBarrier.srcAccessMask =
252 VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT;
253
254 // Who might be consuming the data that was writen.
255 memoryBarrier.dstAccessMask =
256 VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT;
257
258 vkCmdPipelineBarrier(
259 _vkCommandBuffer,
260 VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, // producer (what we wait for)
261 VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, // consumer (what must wait)
262 0, // flags
263 1, // memoryBarrierCount
264 &memoryBarrier, // memory barriers
265 0, nullptr, // buffer barriers
266 0, nullptr); // image barriers
267 }
268
269 void
AddCompletedHandler(HgiVulkanCompletedHandler const & fn)270 HgiVulkanCommandBuffer::AddCompletedHandler(HgiVulkanCompletedHandler const& fn)
271 {
272 _completedHandlers.push_back(fn);
273 }
274
275 void
RunAndClearCompletedHandlers()276 HgiVulkanCommandBuffer::RunAndClearCompletedHandlers()
277 {
278 for (HgiVulkanCompletedHandler& fn : _completedHandlers) {
279 fn();
280 }
281 _completedHandlers.clear();
282 }
283
284 VkCommandBufferResetFlags
_GetCommandBufferResetFlags()285 HgiVulkanCommandBuffer::_GetCommandBufferResetFlags()
286 {
287 // For now we do not use VK_COMMAND_BUFFER_RESET_RELEASE_RESOURCES_BIT,
288 // assuming similar memory requirements will be needed each frame.
289 // Releasing resources can come at a performance cost.
290 static const VkCommandBufferResetFlags flags = 0;
291 return flags;
292 }
293
294 PXR_NAMESPACE_CLOSE_SCOPE
295