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         &region);
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         &copyRegion);
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             &copyRegion);
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         &copyRegion);
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