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
25 #include "pxr/imaging/hgiVulkan/blitCmds.h"
26 #include "pxr/imaging/hgiVulkan/buffer.h"
27 #include "pxr/imaging/hgiVulkan/commandBuffer.h"
28 #include "pxr/imaging/hgiVulkan/commandQueue.h"
29 #include "pxr/imaging/hgiVulkan/conversions.h"
30 #include "pxr/imaging/hgiVulkan/device.h"
31 #include "pxr/imaging/hgiVulkan/diagnostic.h"
32 #include "pxr/imaging/hgiVulkan/hgi.h"
33 #include "pxr/imaging/hgiVulkan/texture.h"
34 #include "pxr/imaging/hgi/blitCmdsOps.h"
35
36
37 PXR_NAMESPACE_OPEN_SCOPE
38
HgiVulkanBlitCmds(HgiVulkan * hgi)39 HgiVulkanBlitCmds::HgiVulkanBlitCmds(HgiVulkan* hgi)
40 : _hgi(hgi)
41 , _commandBuffer(nullptr)
42 {
43 // We do not acquire the command buffer here, because the Cmds object may
44 // have been created on the main thread, but used on a secondary thread.
45 // We need to acquire a command buffer for the thread that is doing the
46 // recording so we postpone acquiring cmd buffer until first use of Cmds.
47 }
48
49 HgiVulkanBlitCmds::~HgiVulkanBlitCmds() = default;
50
51 void
PushDebugGroup(const char * label)52 HgiVulkanBlitCmds::PushDebugGroup(const char* label)
53 {
54 _CreateCommandBuffer();
55 HgiVulkanBeginLabel(_hgi->GetPrimaryDevice(), _commandBuffer, label);
56 }
57
58 void
PopDebugGroup()59 HgiVulkanBlitCmds::PopDebugGroup()
60 {
61 _CreateCommandBuffer();
62 HgiVulkanEndLabel(_hgi->GetPrimaryDevice(), _commandBuffer);
63 }
64
65 void
CopyTextureGpuToCpu(HgiTextureGpuToCpuOp const & copyOp)66 HgiVulkanBlitCmds::CopyTextureGpuToCpu(
67 HgiTextureGpuToCpuOp const& copyOp)
68 {
69 _CreateCommandBuffer();
70
71 HgiVulkanTexture* srcTexture =
72 static_cast<HgiVulkanTexture*>(copyOp.gpuSourceTexture.Get());
73
74 if (!TF_VERIFY(srcTexture && srcTexture->GetImage(),
75 "Invalid texture handle")) {
76 return;
77 }
78
79 if (copyOp.destinationBufferByteSize == 0) {
80 TF_WARN("The size of the data to copy was zero (aborted)");
81 return;
82 }
83
84 HgiTextureDesc const& texDesc = srcTexture->GetDescriptor();
85
86 bool isTexArray = texDesc.layerCount>1;
87 int depthOffset = isTexArray ? 0 : copyOp.sourceTexelOffset[2];
88
89 VkOffset3D origin;
90 origin.x = copyOp.sourceTexelOffset[0];
91 origin.y = copyOp.sourceTexelOffset[1];
92 origin.z = depthOffset;
93
94 VkExtent3D size;
95 size.width = texDesc.dimensions[0] - copyOp.sourceTexelOffset[0];
96 size.height = texDesc.dimensions[1] - copyOp.sourceTexelOffset[1];
97 size.depth = texDesc.dimensions[2] - depthOffset;
98
99 VkImageSubresourceLayers imageSub;
100 imageSub.aspectMask = HgiVulkanConversions::GetImageAspectFlag(texDesc.usage);
101 imageSub.baseArrayLayer = isTexArray ? copyOp.sourceTexelOffset[2] : 0;
102 imageSub.layerCount = 1;
103 imageSub.mipLevel = copyOp.mipLevel;
104
105 // See vulkan docs: Copying Data Between Buffers and Images
106 VkBufferImageCopy region;
107 region.bufferImageHeight = 0; // Buffer is tightly packed, like image
108 region.bufferRowLength = 0; // Buffer is tightly packed, like image
109 region.bufferOffset = 0; // We offset cpuDestinationBuffer. Not here.
110 region.imageExtent = size;
111 region.imageOffset = origin;
112 region.imageSubresource = imageSub;
113
114 // Transition image to TRANSFER_READ
115 VkImageLayout oldLayout = srcTexture->GetImageLayout();
116 srcTexture->TransitionImageBarrier(
117 _commandBuffer,
118 srcTexture,
119 VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, // transition tex to this layout
120 HgiVulkanTexture::NO_PENDING_WRITES, // no pending writes
121 VK_ACCESS_TRANSFER_READ_BIT, // type of access
122 VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, // producer stage
123 VK_PIPELINE_STAGE_TRANSFER_BIT); // consumer stage
124
125 // Copy gpu texture to gpu staging buffer.
126 // We reuse the texture's staging buffer, assuming that any new texel
127 // uploads this frame will have been consumed from the staging buffer
128 // before any downloads (read backs) overwrite the staging buffer texels.
129 uint8_t* src = static_cast<uint8_t*>(srcTexture->GetCPUStagingAddress());
130 HgiVulkanBuffer* stagingBuffer = srcTexture->GetStagingBuffer();
131 TF_VERIFY(src && stagingBuffer);
132
133 vkCmdCopyImageToBuffer(
134 _commandBuffer->GetVulkanCommandBuffer(),
135 srcTexture->GetImage(),
136 srcTexture->GetImageLayout(),
137 stagingBuffer->GetVulkanBuffer(),
138 1,
139 ®ion);
140
141 // Transition image back to what it was.
142 VkAccessFlags access = HgiVulkanTexture::GetDefaultAccessFlags(
143 srcTexture->GetDescriptor().usage);
144
145 srcTexture->TransitionImageBarrier(
146 _commandBuffer,
147 srcTexture,
148 oldLayout, // transition tex to this layout
149 HgiVulkanTexture::NO_PENDING_WRITES, // no pending writes
150 access, // type of access
151 VK_PIPELINE_STAGE_TRANSFER_BIT, // producer stage
152 VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT); // consumer stage
153
154 // Offset into the dst buffer
155 char* dst = ((char*) copyOp.cpuDestinationBuffer) +
156 copyOp.destinationByteOffset;
157
158 // bytes to copy
159 size_t byteSize = copyOp.destinationBufferByteSize;
160
161 // Copy to cpu buffer when cmd buffer has been executed
162 _commandBuffer->AddCompletedHandler(
163 [dst, src, byteSize]{ memcpy(dst, src, byteSize);}
164 );
165 }
166
167 void
CopyTextureCpuToGpu(HgiTextureCpuToGpuOp const & copyOp)168 HgiVulkanBlitCmds::CopyTextureCpuToGpu(
169 HgiTextureCpuToGpuOp const& copyOp)
170 {
171 _CreateCommandBuffer();
172
173 HgiVulkanTexture* dstTexture = static_cast<HgiVulkanTexture*>(
174 copyOp.gpuDestinationTexture.Get());
175 HgiTextureDesc const& texDesc = dstTexture->GetDescriptor();
176
177 // If we used GetCPUStagingAddress as the cpuSourceBuffer when the copyOp
178 // was created, we can skip the memcpy since the src and dst buffer are
179 // the same and dst staging buffer already contains the desired data.
180 // See also: HgiVulkanTexture::GetCPUStagingAddress.
181 if (!dstTexture->IsCPUStagingAddress(copyOp.cpuSourceBuffer)) {
182
183 // We need to memcpy at the mip's location in the staging buffer.
184 // It is possible we CopyTextureCpuToGpu a bunch of mips in a row
185 // before submitting the cmd buf. So we can't just use the start
186 // of the staging buffer each time.
187 const std::vector<HgiMipInfo> mipInfos =
188 HgiGetMipInfos(
189 texDesc.format,
190 texDesc.dimensions,
191 1 /*HgiTextureCpuToGpuOp does one layer at a time*/);
192
193 if (mipInfos.size() > copyOp.mipLevel) {
194 HgiMipInfo const& mipInfo = mipInfos[copyOp.mipLevel];
195
196 uint8_t* dst = static_cast<uint8_t*>(
197 dstTexture->GetCPUStagingAddress());
198 TF_VERIFY(dst && dstTexture->GetStagingBuffer());
199
200 dst += mipInfo.byteOffset;
201 const size_t size =
202 std::min(copyOp.bufferByteSize, 1 * mipInfo.byteSizePerLayer);
203 memcpy(dst, copyOp.cpuSourceBuffer, size);
204 }
205 }
206
207 // Schedule transfer from staging buffer to device-local texture
208 HgiVulkanBuffer* stagingBuffer = dstTexture->GetStagingBuffer();
209 if (TF_VERIFY(stagingBuffer, "Invalid staging buffer for texture")) {
210 dstTexture->CopyBufferToTexture(
211 _commandBuffer,
212 dstTexture->GetStagingBuffer(),
213 copyOp.destinationTexelOffset,
214 copyOp.mipLevel);
215 }
216 }
217
CopyBufferGpuToGpu(HgiBufferGpuToGpuOp const & copyOp)218 void HgiVulkanBlitCmds::CopyBufferGpuToGpu(
219 HgiBufferGpuToGpuOp const& copyOp)
220 {
221 _CreateCommandBuffer();
222
223 HgiBufferHandle const& srcBufHandle = copyOp.gpuSourceBuffer;
224 HgiVulkanBuffer* srcBuffer =
225 static_cast<HgiVulkanBuffer*>(srcBufHandle.Get());
226
227 if (!TF_VERIFY(srcBuffer && srcBuffer->GetVulkanBuffer(),
228 "Invalid source buffer handle")) {
229 return;
230 }
231
232 HgiBufferHandle const& dstBufHandle = copyOp.gpuDestinationBuffer;
233 HgiVulkanBuffer* dstBuffer =
234 static_cast<HgiVulkanBuffer*>(dstBufHandle.Get());
235
236 if (!TF_VERIFY(dstBuffer && dstBuffer->GetVulkanBuffer(),
237 "Invalid destination buffer handle")) {
238 return;
239 }
240
241 if (copyOp.byteSize == 0) {
242 TF_WARN("The size of the data to copy was zero (aborted)");
243 return;
244 }
245
246 // Copy data from staging buffer to destination (gpu) buffer
247 VkBufferCopy copyRegion = {};
248 copyRegion.srcOffset = copyOp.sourceByteOffset;
249 copyRegion.dstOffset = copyOp.destinationByteOffset;
250 copyRegion.size = copyOp.byteSize;
251
252 vkCmdCopyBuffer(
253 _commandBuffer->GetVulkanCommandBuffer(),
254 srcBuffer->GetVulkanBuffer(),
255 dstBuffer->GetVulkanBuffer(),
256 1, // regionCount
257 ©Region);
258 }
259
CopyBufferCpuToGpu(HgiBufferCpuToGpuOp const & copyOp)260 void HgiVulkanBlitCmds::CopyBufferCpuToGpu(
261 HgiBufferCpuToGpuOp const& copyOp)
262 {
263 _CreateCommandBuffer();
264
265 if (copyOp.byteSize == 0 ||
266 !copyOp.cpuSourceBuffer ||
267 !copyOp.gpuDestinationBuffer)
268 {
269 return;
270 }
271
272 HgiVulkanBuffer* buffer = static_cast<HgiVulkanBuffer*>(
273 copyOp.gpuDestinationBuffer.Get());
274
275 // If we used GetCPUStagingAddress as the cpuSourceBuffer when the copyOp
276 // was created, we can skip the memcpy since the src and dst buffer are
277 // the same and dst staging buffer already contains the desired data.
278 // See also: HgiBuffer::GetCPUStagingAddress.
279 if (!buffer->IsCPUStagingAddress(copyOp.cpuSourceBuffer) ||
280 copyOp.sourceByteOffset != copyOp.destinationByteOffset) {
281
282 uint8_t* dst = static_cast<uint8_t*>(buffer->GetCPUStagingAddress());
283 size_t dstOffset = copyOp.destinationByteOffset;
284
285 // Offset into the src buffer
286 uint8_t* src = ((uint8_t*) copyOp.cpuSourceBuffer) +
287 copyOp.sourceByteOffset;
288
289 // Offset into the dst buffer
290 memcpy(dst + dstOffset, src, copyOp.byteSize);
291 }
292
293 // Schedule copy data from staging buffer to device-local buffer.
294 HgiVulkanBuffer* stagingBuffer = buffer->GetStagingBuffer();
295
296 if (TF_VERIFY(stagingBuffer)) {
297 VkBufferCopy copyRegion = {};
298 copyRegion.srcOffset = copyOp.sourceByteOffset;
299 copyRegion.dstOffset = copyOp.destinationByteOffset;
300 copyRegion.size = copyOp.byteSize;
301
302 vkCmdCopyBuffer(
303 _commandBuffer->GetVulkanCommandBuffer(),
304 stagingBuffer->GetVulkanBuffer(),
305 buffer->GetVulkanBuffer(),
306 1,
307 ©Region);
308 }
309 }
310
311 void
CopyBufferGpuToCpu(HgiBufferGpuToCpuOp const & copyOp)312 HgiVulkanBlitCmds::CopyBufferGpuToCpu(HgiBufferGpuToCpuOp const& copyOp)
313 {
314 _CreateCommandBuffer();
315
316 if (copyOp.byteSize == 0 ||
317 !copyOp.cpuDestinationBuffer ||
318 !copyOp.gpuSourceBuffer)
319 {
320 return;
321 }
322
323 HgiVulkanBuffer* buffer = static_cast<HgiVulkanBuffer*>(
324 copyOp.gpuSourceBuffer.Get());
325
326 // Make sure there is a staging buffer in the buffer by asking for cpuAddr.
327 void* cpuAddress = buffer->GetCPUStagingAddress();
328 HgiVulkanBuffer* stagingBuffer = buffer->GetStagingBuffer();
329 if (!TF_VERIFY(stagingBuffer)) {
330 return;
331 }
332
333 // Copy from device-local GPU buffer into GPU staging buffer
334 VkBufferCopy copyRegion = {};
335 copyRegion.srcOffset = copyOp.sourceByteOffset;
336 copyRegion.dstOffset = copyOp.destinationByteOffset;
337 copyRegion.size = copyOp.byteSize;
338 vkCmdCopyBuffer(
339 _commandBuffer->GetVulkanCommandBuffer(),
340 buffer->GetVulkanBuffer(),
341 stagingBuffer->GetVulkanBuffer(),
342 1,
343 ©Region);
344
345 // Next schedule a callback when the above GPU-GPU copy completes.
346
347 // Offset into the dst buffer
348 char* dst = ((char*) copyOp.cpuDestinationBuffer) +
349 copyOp.destinationByteOffset;
350
351 // Offset into the src buffer
352 const char* src = ((const char*) cpuAddress) + copyOp.sourceByteOffset;
353
354 // bytes to copy
355 size_t size = copyOp.byteSize;
356
357 // Copy to cpu buffer when cmd buffer has been executed
358 _commandBuffer->AddCompletedHandler(
359 [dst, src, size]{ memcpy(dst, src, size);}
360 );
361 }
362
363 void
CopyTextureToBuffer(HgiTextureToBufferOp const & copyOp)364 HgiVulkanBlitCmds::CopyTextureToBuffer(HgiTextureToBufferOp const& copyOp)
365 {
366 TF_CODING_ERROR("Missing Implementation");
367 }
368
369 void
CopyBufferToTexture(HgiBufferToTextureOp const & copyOp)370 HgiVulkanBlitCmds::CopyBufferToTexture(HgiBufferToTextureOp const& copyOp)
371 {
372 TF_CODING_ERROR("Missing Implementation");
373 }
374
375 void
GenerateMipMaps(HgiTextureHandle const & texture)376 HgiVulkanBlitCmds::GenerateMipMaps(HgiTextureHandle const& texture)
377 {
378 _CreateCommandBuffer();
379
380 HgiVulkanTexture* vkTex = static_cast<HgiVulkanTexture*>(texture.Get());
381 HgiVulkanDevice* device = vkTex->GetDevice();
382
383 HgiTextureDesc const& desc = texture->GetDescriptor();
384 VkFormat format = HgiVulkanConversions::GetFormat(desc.format);
385 int32_t width = desc.dimensions[0];
386 int32_t height = desc.dimensions[1];
387
388 if (desc.layerCount > 1) {
389 TF_CODING_ERROR("Missing implementation generating mips for layers");
390 }
391
392 // Ensure texture format supports blit src&dst required for mips
393 VkFormatProperties formatProps;
394 vkGetPhysicalDeviceFormatProperties(
395 device->GetVulkanPhysicalDevice(), format, &formatProps);
396 if (!(formatProps.optimalTilingFeatures & VK_FORMAT_FEATURE_BLIT_SRC_BIT) ||
397 !(formatProps.optimalTilingFeatures & VK_FORMAT_FEATURE_BLIT_DST_BIT))
398 {
399 TF_CODING_ERROR("Texture format does not support "
400 "blit source and destination");
401 return;
402 }
403
404 // Transition first mip to TRANSFER_SRC so we can read it
405 HgiVulkanTexture::TransitionImageBarrier(
406 _commandBuffer,
407 vkTex,
408 VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
409 HgiVulkanTexture::NO_PENDING_WRITES,
410 VK_ACCESS_TRANSFER_READ_BIT,
411 VK_PIPELINE_STAGE_TRANSFER_BIT,
412 VK_PIPELINE_STAGE_TRANSFER_BIT,
413 0);
414
415 // Copy down the whole mip chain doing a blit from mip-1 to mip
416 for (uint32_t i = 1; i < desc.mipLevels; i++) {
417 VkImageBlit imageBlit{};
418
419 // Source
420 imageBlit.srcSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
421 imageBlit.srcSubresource.layerCount = 1;
422 imageBlit.srcSubresource.mipLevel = i - 1;
423 imageBlit.srcOffsets[1].x = width >> (i - 1);
424 imageBlit.srcOffsets[1].y = height >> (i - 1);
425 imageBlit.srcOffsets[1].z = 1;
426
427 // Destination
428 imageBlit.dstSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
429 imageBlit.dstSubresource.layerCount = 1;
430 imageBlit.dstSubresource.mipLevel = i;
431 imageBlit.dstOffsets[1].x = width >> i;
432 imageBlit.dstOffsets[1].y = height >> i;
433 imageBlit.dstOffsets[1].z = 1;
434
435 // Transition current mip level to image blit destination
436 HgiVulkanTexture::TransitionImageBarrier(
437 _commandBuffer,
438 vkTex,
439 VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
440 HgiVulkanTexture::NO_PENDING_WRITES,
441 VK_ACCESS_TRANSFER_WRITE_BIT,
442 VK_PIPELINE_STAGE_TRANSFER_BIT,
443 VK_PIPELINE_STAGE_TRANSFER_BIT,
444 i);
445
446 // Blit from previous level
447 vkCmdBlitImage(
448 _commandBuffer->GetVulkanCommandBuffer(),
449 vkTex->GetImage(),
450 VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
451 vkTex->GetImage(),
452 VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
453 1,
454 &imageBlit,
455 VK_FILTER_LINEAR);
456
457 // Prepare current mip level as image blit source for next level
458 HgiVulkanTexture::TransitionImageBarrier(
459 _commandBuffer,
460 vkTex,
461 VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
462 VK_ACCESS_TRANSFER_WRITE_BIT,
463 VK_ACCESS_TRANSFER_READ_BIT,
464 VK_PIPELINE_STAGE_TRANSFER_BIT,
465 VK_PIPELINE_STAGE_TRANSFER_BIT,
466 i);
467 }
468
469 // Return all mips from TRANSFER_SRC to their default (usually SHADER_READ)
470 HgiVulkanTexture::TransitionImageBarrier(
471 _commandBuffer,
472 vkTex,
473 HgiVulkanTexture::GetDefaultImageLayout(desc.usage),
474 VK_ACCESS_TRANSFER_READ_BIT,
475 HgiVulkanTexture::GetDefaultAccessFlags(desc.usage),
476 VK_PIPELINE_STAGE_TRANSFER_BIT,
477 VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT);
478 }
479
480 void
MemoryBarrier(HgiMemoryBarrier barrier)481 HgiVulkanBlitCmds::MemoryBarrier(HgiMemoryBarrier barrier)
482 {
483 _CreateCommandBuffer();
484 _commandBuffer->MemoryBarrier(barrier);
485 }
486
487 HgiVulkanCommandBuffer*
GetCommandBuffer()488 HgiVulkanBlitCmds::GetCommandBuffer()
489 {
490 return _commandBuffer;
491 }
492
493 bool
_Submit(Hgi * hgi,HgiSubmitWaitType wait)494 HgiVulkanBlitCmds::_Submit(Hgi* hgi, HgiSubmitWaitType wait)
495 {
496 if (!_commandBuffer) {
497 return false;
498 }
499
500 HgiVulkanDevice* device = _commandBuffer->GetDevice();
501 HgiVulkanCommandQueue* queue = device->GetCommandQueue();
502
503 // Submit the GPU work and optionally do CPU - GPU synchronization.
504 queue->SubmitToQueue(_commandBuffer, wait);
505
506 return true;
507 }
508
509 void
_CreateCommandBuffer()510 HgiVulkanBlitCmds::_CreateCommandBuffer()
511 {
512 if (!_commandBuffer) {
513 HgiVulkanDevice* device = _hgi->GetPrimaryDevice();
514 HgiVulkanCommandQueue* queue = device->GetCommandQueue();
515 _commandBuffer = queue->AcquireCommandBuffer();
516 TF_VERIFY(_commandBuffer);
517 }
518 }
519
520 PXR_NAMESPACE_CLOSE_SCOPE
521