| // Copyright 2019 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "src/ui/lib/escher/renderer/batch_gpu_downloader.h" |
| |
| #include <lib/fit/function.h> |
| #include <lib/syslog/cpp/macros.h> |
| |
| #include <variant> |
| |
| #include "src/ui/lib/escher/forward_declarations.h" |
| #include "src/ui/lib/escher/impl/vulkan_utils.h" |
| #include "src/ui/lib/escher/renderer/batch_gpu_uploader.h" |
| #include "src/ui/lib/escher/util/align.h" |
| #include "src/ui/lib/escher/util/trace_macros.h" |
| |
| namespace escher { |
| |
| namespace { |
| |
| // Vulkan specs requires that bufferOffset in VkBufferImageCopy be multiple of |
| // 4, so we enforce that all buffer offsets (including buffers and images) align |
| // to multiples of 4 by aligning up the addresses. |
| constexpr vk::DeviceSize kByteAlignment = 4; |
| |
| } // namespace |
| |
| std::unique_ptr<BatchGpuDownloader> BatchGpuDownloader::New(EscherWeakPtr weak_escher, |
| CommandBuffer::Type command_buffer_type, |
| uint64_t frame_trace_number) { |
| if (!weak_escher) { |
| // This class is not functional without a valid escher. |
| FX_LOGS(WARNING) << "Error, creating a BatchGpuDownloader without an escher."; |
| return nullptr; |
| } |
| return std::make_unique<BatchGpuDownloader>(std::move(weak_escher), command_buffer_type, |
| frame_trace_number); |
| } |
| |
| BatchGpuDownloader::BatchGpuDownloader(EscherWeakPtr weak_escher, |
| CommandBuffer::Type command_buffer_type, |
| uint64_t frame_trace_number) |
| : escher_(std::move(weak_escher)), |
| command_buffer_type_(command_buffer_type), |
| frame_trace_number_(frame_trace_number), |
| buffer_cache_(escher_->buffer_cache()->GetWeakPtr()) { |
| FX_DCHECK(escher_); |
| FX_DCHECK(buffer_cache_); |
| } |
| |
| BatchGpuDownloader::~BatchGpuDownloader() { |
| FX_CHECK(resources_.empty() && copy_info_records_.empty() && wait_semaphores_.empty() && |
| signal_semaphores_.empty() && current_offset_ == 0U); |
| } |
| |
| void BatchGpuDownloader::ScheduleReadBuffer(const BufferPtr& source, |
| BatchGpuDownloader::Callback callback, |
| vk::DeviceSize source_offset, |
| vk::DeviceSize copy_size) { |
| TRACE_DURATION("gfx", "escher::BatchGpuDownloader::ScheduleReadBuffer"); |
| vk::DeviceSize dst_offset = AlignedToNext(current_offset_, kByteAlignment); |
| copy_size = copy_size == 0U ? source->size() : copy_size; |
| auto region = vk::BufferCopy(source_offset, dst_offset, copy_size); |
| |
| copy_info_records_.push_back( |
| CopyInfo{.type = CopyType::COPY_BUFFER, |
| .offset = dst_offset, |
| .size = region.size, |
| .callback = std::move(callback), |
| .copy_info = BufferCopyInfo{.source = source, .region = region}}); |
| current_offset_ = dst_offset + copy_info_records_.back().size; |
| resources_.push_back(source); |
| } |
| |
| void BatchGpuDownloader::ScheduleReadImage(const ImagePtr& source, |
| BatchGpuDownloader::Callback callback, |
| vk::BufferImageCopy region) { |
| if (region == vk::BufferImageCopy()) { |
| region = impl::GetDefaultBufferImageCopy(source->width(), source->height()); |
| } |
| FX_DCHECK(region.bufferOffset == 0U); |
| |
| // For now we expect that we only accept full image to be downloadable. |
| FX_DCHECK(region.imageOffset == vk::Offset3D(0, 0, 0) && |
| region.imageExtent == vk::Extent3D(source->width(), source->height(), 1U)); |
| |
| TRACE_DURATION("gfx", "escher::BatchGpuDownloader::ScheduleReadImage"); |
| vk::DeviceSize dst_offset = AlignedToNext(current_offset_, kByteAlignment); |
| auto final_region = region; |
| final_region.setBufferOffset(dst_offset); |
| |
| copy_info_records_.push_back( |
| CopyInfo{.type = CopyType::COPY_IMAGE, |
| .offset = dst_offset, |
| // TODO(add a bug) Use the size calculated from region. |
| .size = source->size(), |
| .callback = std::move(callback), |
| .copy_info = ImageCopyInfo{.source = source, .region = final_region}}); |
| current_offset_ = dst_offset + copy_info_records_.back().size; |
| resources_.push_back(source); |
| } |
| |
| CommandBufferFinishedCallback BatchGpuDownloader::GenerateCommands(CommandBuffer* cmds) { |
| if (!NeedsCommandBuffer()) { |
| return []() {}; |
| } |
| |
| TRACE_DURATION("gfx", "BatchGpuDownloader::GenerateCommands"); |
| // Check existence of command buffer and buffer cache. |
| FX_DCHECK(cmds); |
| |
| // We only create the target_buffer if we need to download something. |
| // If we only need to create an empty command buffer (to signal / wait on |
| // semaphores), we won't need the buffer. |
| BufferPtr target_buffer = BufferPtr(); |
| if (HasContentToDownload()) { |
| // Create a large buffer to store all images / buffers to be downloaded. |
| vk::DeviceSize buffer_size = copy_info_records_.back().offset + copy_info_records_.back().size; |
| target_buffer = buffer_cache_->NewHostBuffer(buffer_size); |
| FX_DCHECK(target_buffer) << "Error allocating buffer"; |
| cmds->KeepAlive(target_buffer); |
| } |
| |
| // Set up pipeline flags and access flags for synchronization. See class |
| // comments for details. |
| constexpr auto kPipelineFlag = vk::PipelineStageFlagBits::eTransfer; |
| const auto kAccessFlagOutside = |
| vk::AccessFlagBits::eTransferRead | vk::AccessFlagBits::eTransferWrite; |
| const auto kAccessFlagInside = vk::AccessFlagBits::eTransferRead; |
| |
| for (const auto& copy_info_record : copy_info_records_) { |
| switch (copy_info_record.type) { |
| case CopyType::COPY_IMAGE: { |
| const auto* image_copy_info = std::get_if<ImageCopyInfo>(©_info_record.copy_info); |
| FX_DCHECK(image_copy_info); |
| |
| ImagePtr source = image_copy_info->source; |
| auto target_layout = source->is_layout_initialized() |
| ? source->layout() |
| : vk::ImageLayout::eShaderReadOnlyOptimal; |
| |
| cmds->ImageBarrier(source, source->layout(), vk::ImageLayout::eTransferSrcOptimal, |
| kPipelineFlag, kAccessFlagOutside, kPipelineFlag, kAccessFlagInside); |
| cmds->vk().copyImageToBuffer(source->vk(), vk::ImageLayout::eTransferSrcOptimal, |
| target_buffer->vk(), 1, &image_copy_info->region); |
| cmds->ImageBarrier(source, vk::ImageLayout::eTransferSrcOptimal, target_layout, |
| kPipelineFlag, kAccessFlagInside, kPipelineFlag, kAccessFlagOutside); |
| cmds->KeepAlive(source); |
| break; |
| } |
| case CopyType::COPY_BUFFER: { |
| const auto* buffer_copy_info = std::get_if<BufferCopyInfo>(©_info_record.copy_info); |
| FX_DCHECK(buffer_copy_info); |
| |
| cmds->BufferBarrier(buffer_copy_info->source, kPipelineFlag, kAccessFlagOutside, |
| kPipelineFlag, kAccessFlagInside); |
| cmds->vk().copyBuffer(buffer_copy_info->source->vk(), target_buffer->vk(), 1, |
| &buffer_copy_info->region); |
| cmds->BufferBarrier(buffer_copy_info->source, kPipelineFlag, kAccessFlagInside, |
| kPipelineFlag, kAccessFlagOutside); |
| cmds->KeepAlive(buffer_copy_info->source); |
| break; |
| } |
| } |
| } |
| |
| // Add semaphores for the submitted command buffer to wait on / signal. |
| for (auto& pair : wait_semaphores_) { |
| cmds->AddWaitSemaphore(std::move(pair.first), pair.second); |
| } |
| wait_semaphores_.clear(); |
| |
| for (auto& sem : signal_semaphores_) { |
| cmds->AddSignalSemaphore(std::move(sem)); |
| } |
| signal_semaphores_.clear(); |
| |
| // Since we keep the target alive using CommandBuffer, we can remove these |
| // RefPtrs from |resources_| right now. |
| resources_.clear(); |
| current_offset_ = 0U; |
| |
| // The target buffer will be moved to the callback function so it will be |
| // still alive until this function gets called. |
| // |copy_info_records_| is guaranteed to be cleared after being moved to |
| // |readers_info|. |
| return |
| [target_buffer = std::move(target_buffer), readers_info = std::move(copy_info_records_)]() { |
| for (const auto& reader_info : readers_info) { |
| reader_info.callback(target_buffer->host_ptr() + reader_info.offset, reader_info.size); |
| } |
| }; |
| } |
| |
| void BatchGpuDownloader::Submit(CommandBufferFinishedCallback client_callback) { |
| if (!NeedsCommandBuffer()) { |
| // This downloader was never used, nothing to submit. |
| if (client_callback) { |
| client_callback(); |
| } |
| return; |
| } |
| |
| TRACE_DURATION("gfx", "BatchGpuDownloader::Submit"); |
| |
| // Create new command buffer. |
| FramePtr frame = escher_->NewFrame("Gpu Downloader", frame_trace_number_, |
| /*enable_gpu_logging=*/false, command_buffer_type_, |
| /*use_protected_memory=*/false); |
| |
| // Add commands to |frame|'s command buffer. |
| CommandBufferFinishedCallback reader_callback = GenerateCommands(frame->cmds()); |
| |
| // Submit the command buffer. |
| frame->EndFrame(SemaphorePtr(), [client_callback = std::move(client_callback), |
| reader_callback = std::move(reader_callback)]() { |
| if (reader_callback) { |
| reader_callback(); |
| } |
| if (client_callback) { |
| client_callback(); |
| } |
| }); |
| |
| // Verify that everything is reset so that the downloader can be reused as |
| // though new. |
| FX_CHECK(resources_.empty() && copy_info_records_.empty() && wait_semaphores_.empty() && |
| signal_semaphores_.empty() && current_offset_ == 0U); |
| } |
| |
| } // namespace escher |