| // Copyright 2016 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 "lib/escher/impl/command_buffer.h" |
| #include "lib/escher/impl/descriptor_set_pool.h" |
| #include "lib/escher/impl/mesh_shader_binding.h" |
| #include "lib/escher/shape/mesh.h" |
| #include "lib/escher/util/trace_macros.h" |
| #include "lib/escher/vk/buffer.h" |
| #include "lib/escher/vk/framebuffer.h" |
| #include "lib/escher/vk/image.h" |
| #include "lib/escher/vk/render_pass.h" |
| #include "lib/fxl/macros.h" |
| |
| namespace escher { |
| namespace impl { |
| |
| CommandBuffer::CommandBuffer(vk::Device device, |
| vk::CommandBuffer command_buffer, vk::Fence fence, |
| vk::PipelineStageFlags pipeline_stage_mask) |
| : device_(device), |
| command_buffer_(command_buffer), |
| fence_(fence), |
| pipeline_stage_mask_(pipeline_stage_mask) {} |
| |
| CommandBuffer::~CommandBuffer() { |
| FXL_DCHECK(!is_active_ && !is_submitted_); |
| // Owner is responsible for destroying command buffer and fence. |
| } |
| |
| void CommandBuffer::Begin(uint64_t sequence_number) { |
| FXL_DCHECK(!is_active_ && !is_submitted_); |
| FXL_DCHECK(sequence_number > sequence_number_); |
| is_active_ = true; |
| sequence_number_ = sequence_number; |
| auto result = command_buffer_.begin(vk::CommandBufferBeginInfo()); |
| FXL_DCHECK(result == vk::Result::eSuccess); |
| } |
| |
| bool CommandBuffer::Submit(vk::Queue queue, |
| CommandBufferFinishedCallback callback) { |
| TRACE_DURATION("gfx", "escher::CommandBuffer::Submit"); |
| |
| FXL_DCHECK(is_active_ && !is_submitted_); |
| is_submitted_ = true; |
| callback_ = std::move(callback); |
| |
| auto end_command_buffer_result = command_buffer_.end(); |
| FXL_DCHECK(end_command_buffer_result == vk::Result::eSuccess); |
| |
| vk::SubmitInfo submit_info; |
| submit_info.commandBufferCount = 1; |
| submit_info.pCommandBuffers = &command_buffer_; |
| submit_info.waitSemaphoreCount = wait_semaphores_for_submit_.size(); |
| submit_info.pWaitSemaphores = wait_semaphores_for_submit_.data(); |
| submit_info.pWaitDstStageMask = wait_semaphore_stages_.data(); |
| submit_info.signalSemaphoreCount = signal_semaphores_for_submit_.size(); |
| submit_info.pSignalSemaphores = signal_semaphores_for_submit_.data(); |
| |
| auto submit_result = queue.submit(1, &submit_info, fence_); |
| if (submit_result != vk::Result::eSuccess) { |
| FXL_LOG(WARNING) << "failed queue submission: " << to_string(submit_result); |
| // Clearing these flags allows Retire() to make progress. |
| is_active_ = is_submitted_ = false; |
| return false; |
| } |
| return true; |
| } |
| |
| vk::Result CommandBuffer::Wait(uint64_t nanoseconds) { |
| if (!is_active_) { |
| // The command buffer is already finished. |
| return vk::Result::eSuccess; |
| } |
| FXL_DCHECK(is_submitted_); |
| return device_.waitForFences(1, &fence_, true, nanoseconds); |
| } |
| |
| void CommandBuffer::AddWaitSemaphore(SemaphorePtr semaphore, |
| vk::PipelineStageFlags stage) { |
| FXL_DCHECK(is_active_); |
| if (semaphore) { |
| // Build up list that will be used when frame is submitted. |
| wait_semaphores_for_submit_.push_back(semaphore->vk_semaphore()); |
| wait_semaphore_stages_.push_back(stage); |
| // Retain semaphore to ensure that it doesn't prematurely die. |
| wait_semaphores_.push_back(std::move(semaphore)); |
| } |
| } |
| |
| void CommandBuffer::AddSignalSemaphore(SemaphorePtr semaphore) { |
| FXL_DCHECK(is_active_); |
| if (semaphore) { |
| // Build up list that will be used when frame is submitted. |
| signal_semaphores_for_submit_.push_back(semaphore->vk_semaphore()); |
| // Retain semaphore to ensure that it doesn't prematurely die. |
| signal_semaphores_.push_back(std::move(semaphore)); |
| } |
| } |
| |
| void CommandBuffer::KeepAlive(Resource* resource) { |
| FXL_DCHECK(is_active_); |
| if (sequence_number_ == resource->sequence_number()) { |
| // The resource is already being kept alive by this CommandBuffer. |
| return; |
| } |
| |
| resource->KeepAlive(sequence_number_); |
| } |
| |
| void CommandBuffer::DrawMesh(const MeshPtr& mesh) { |
| KeepAlive(mesh); |
| |
| AddWaitSemaphore(mesh->TakeWaitSemaphore(), |
| vk::PipelineStageFlagBits::eVertexInput); |
| |
| const uint32_t vbo_binding = |
| MeshShaderBinding::kTheOnlyCurrentlySupportedBinding; |
| auto& attribute_buffer = mesh->attribute_buffer(vbo_binding); |
| vk::Buffer vbo = attribute_buffer.vk_buffer; |
| vk::DeviceSize vbo_offset = attribute_buffer.offset; |
| command_buffer_.bindVertexBuffers(vbo_binding, 1, &vbo, &vbo_offset); |
| command_buffer_.bindIndexBuffer(mesh->vk_index_buffer(), |
| mesh->index_buffer_offset(), |
| vk::IndexType::eUint32); |
| command_buffer_.drawIndexed(mesh->num_indices(), 1, 0, 0, 0); |
| } |
| |
| void CommandBuffer::CopyImage(const ImagePtr& src_image, |
| const ImagePtr& dst_image, |
| vk::ImageLayout src_layout, |
| vk::ImageLayout dst_layout, |
| vk::ImageCopy* region) { |
| command_buffer_.copyImage(src_image->vk(), src_layout, dst_image->vk(), |
| dst_layout, 1, region); |
| KeepAlive(src_image); |
| KeepAlive(dst_image); |
| } |
| |
| void CommandBuffer::CopyBuffer(const BufferPtr& src, const BufferPtr& dst, |
| vk::BufferCopy region) { |
| command_buffer_.copyBuffer(src->vk(), dst->vk(), 1 /* region_count */, |
| ®ion); |
| KeepAlive(src); |
| KeepAlive(dst); |
| } |
| |
| void CommandBuffer::CopyBufferAfterBarrier( |
| const BufferPtr& src, const BufferPtr& dst, vk::BufferCopy region, |
| vk::AccessFlags src_access_mask, vk::PipelineStageFlags src_stage_mask) { |
| vk::BufferMemoryBarrier barrier; |
| barrier.srcAccessMask = src_access_mask; |
| barrier.dstAccessMask = vk::AccessFlagBits::eTransferRead; |
| barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; |
| barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; |
| barrier.buffer = dst->vk(); |
| barrier.offset = 0; |
| barrier.size = dst->size(); |
| command_buffer_.pipelineBarrier( |
| src_stage_mask, vk::PipelineStageFlagBits::eTransfer, |
| vk::DependencyFlags(), 0, nullptr, 1, &barrier, 0, nullptr); |
| CopyBuffer(src, dst, region); |
| } |
| |
| void CommandBuffer::TransitionImageLayout(const ImagePtr& image, |
| vk::ImageLayout old_layout, |
| vk::ImageLayout new_layout) { |
| KeepAlive(image); |
| |
| vk::PipelineStageFlags src_stage_mask; |
| vk::PipelineStageFlags dst_stage_mask; |
| |
| vk::ImageMemoryBarrier barrier; |
| barrier.oldLayout = old_layout; |
| barrier.newLayout = new_layout; |
| barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; |
| barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; |
| barrier.image = image->vk(); |
| |
| if (image->has_depth() || image->has_stencil()) { |
| if (image->has_depth()) { |
| barrier.subresourceRange.aspectMask = vk::ImageAspectFlagBits::eDepth; |
| } |
| if (image->has_stencil()) { |
| barrier.subresourceRange.aspectMask |= vk::ImageAspectFlagBits::eStencil; |
| } |
| } else { |
| barrier.subresourceRange.aspectMask = vk::ImageAspectFlagBits::eColor; |
| } |
| |
| // TODO: assert that image only has one level. |
| barrier.subresourceRange.baseMipLevel = 0; |
| barrier.subresourceRange.levelCount = 1; |
| barrier.subresourceRange.baseArrayLayer = 0; |
| barrier.subresourceRange.layerCount = 1; |
| |
| switch (old_layout) { |
| case vk::ImageLayout::eColorAttachmentOptimal: |
| barrier.srcAccessMask = vk::AccessFlagBits::eColorAttachmentRead | |
| vk::AccessFlagBits::eColorAttachmentWrite; |
| src_stage_mask = vk::PipelineStageFlagBits::eColorAttachmentOutput; |
| break; |
| case vk::ImageLayout::eDepthStencilAttachmentOptimal: |
| barrier.srcAccessMask = vk::AccessFlagBits::eDepthStencilAttachmentRead | |
| vk::AccessFlagBits::eDepthStencilAttachmentWrite; |
| src_stage_mask = vk::PipelineStageFlagBits::eLateFragmentTests; |
| break; |
| case vk::ImageLayout::eGeneral: |
| barrier.srcAccessMask = |
| vk::AccessFlagBits::eShaderRead | vk::AccessFlagBits::eShaderWrite; |
| src_stage_mask = vk::PipelineStageFlagBits::eComputeShader; |
| break; |
| case vk::ImageLayout::ePreinitialized: |
| barrier.srcAccessMask = vk::AccessFlagBits::eHostWrite; |
| src_stage_mask = vk::PipelineStageFlagBits::eHost; |
| break; |
| case vk::ImageLayout::eShaderReadOnlyOptimal: |
| barrier.srcAccessMask = vk::AccessFlagBits::eShaderRead; |
| // TODO: investigate whether there are performance benefits to providing |
| // a less-conservative mask. |
| src_stage_mask = |
| vk::PipelineStageFlagBits::eVertexShader | |
| vk::PipelineStageFlagBits::eTessellationControlShader | |
| vk::PipelineStageFlagBits::eTessellationEvaluationShader | |
| vk::PipelineStageFlagBits::eGeometryShader | |
| vk::PipelineStageFlagBits::eFragmentShader; |
| break; |
| case vk::ImageLayout::eTransferDstOptimal: |
| barrier.srcAccessMask = vk::AccessFlagBits::eTransferWrite; |
| src_stage_mask = vk::PipelineStageFlagBits::eTransfer; |
| break; |
| case vk::ImageLayout::eUndefined: |
| // If layout was eUndefined, we don't need a srcAccessMask. |
| src_stage_mask = vk::PipelineStageFlagBits::eAllCommands; |
| break; |
| default: |
| FXL_LOG(ERROR) |
| << "CommandBuffer does not know how to transition from layout: " |
| << vk::to_string(old_layout); |
| FXL_DCHECK(false); |
| } |
| |
| switch (new_layout) { |
| case vk::ImageLayout::eColorAttachmentOptimal: |
| barrier.dstAccessMask = vk::AccessFlagBits::eColorAttachmentRead | |
| vk::AccessFlagBits::eColorAttachmentWrite; |
| dst_stage_mask = vk::PipelineStageFlagBits::eColorAttachmentOutput; |
| break; |
| case vk::ImageLayout::eDepthStencilAttachmentOptimal: |
| barrier.dstAccessMask = vk::AccessFlagBits::eDepthStencilAttachmentRead | |
| vk::AccessFlagBits::eDepthStencilAttachmentWrite; |
| dst_stage_mask = vk::PipelineStageFlagBits::eEarlyFragmentTests; |
| break; |
| case vk::ImageLayout::eGeneral: |
| barrier.dstAccessMask = |
| vk::AccessFlagBits::eShaderRead | vk::AccessFlagBits::eShaderWrite; |
| dst_stage_mask = vk::PipelineStageFlagBits::eComputeShader; |
| break; |
| case vk::ImageLayout::ePresentSrcKHR: |
| barrier.dstAccessMask = vk::AccessFlagBits::eMemoryRead; |
| dst_stage_mask = vk::PipelineStageFlagBits::eAllGraphics; |
| break; |
| case vk::ImageLayout::eShaderReadOnlyOptimal: |
| barrier.dstAccessMask = vk::AccessFlagBits::eShaderRead; |
| // TODO: investigate whether there are performance benefits to providing |
| // a less-conservative mask. |
| dst_stage_mask = |
| (pipeline_stage_mask_ & vk::PipelineStageFlagBits::eAllCommands) |
| ? vk::PipelineStageFlagBits::eAllCommands |
| : vk::PipelineStageFlagBits::eAllGraphics; |
| break; |
| case vk::ImageLayout::eTransferDstOptimal: |
| barrier.dstAccessMask = vk::AccessFlagBits::eTransferWrite; |
| dst_stage_mask = vk::PipelineStageFlagBits::eTransfer; |
| break; |
| case vk::ImageLayout::eTransferSrcOptimal: |
| barrier.dstAccessMask = vk::AccessFlagBits::eTransferRead; |
| dst_stage_mask = vk::PipelineStageFlagBits::eTransfer; |
| break; |
| default: |
| FXL_LOG(ERROR) |
| << "CommandBuffer does not know how to transition to layout: " |
| << vk::to_string(new_layout); |
| FXL_DCHECK(false); |
| } |
| |
| src_stage_mask = src_stage_mask & pipeline_stage_mask_; |
| dst_stage_mask = dst_stage_mask & pipeline_stage_mask_; |
| |
| command_buffer_.pipelineBarrier(src_stage_mask, dst_stage_mask, |
| vk::DependencyFlagBits::eByRegion, 0, nullptr, |
| 0, nullptr, 1, &barrier); |
| } |
| |
| void CommandBuffer::BeginRenderPass( |
| const escher::RenderPassPtr& render_pass, |
| const escher::FramebufferPtr& framebuffer, |
| const std::vector<vk::ClearValue>& clear_values, |
| const vk::Rect2D viewport) { |
| KeepAlive(render_pass); |
| BeginRenderPass(render_pass->vk(), framebuffer, clear_values.data(), |
| clear_values.size(), viewport); |
| } |
| |
| void CommandBuffer::BeginRenderPass( |
| vk::RenderPass render_pass, const escher::FramebufferPtr& framebuffer, |
| const std::vector<vk::ClearValue>& clear_values, |
| const vk::Rect2D viewport) { |
| BeginRenderPass(render_pass, framebuffer, clear_values.data(), |
| clear_values.size(), viewport); |
| } |
| |
| void CommandBuffer::BeginRenderPass(vk::RenderPass render_pass, |
| const escher::FramebufferPtr& framebuffer, |
| const vk::ClearValue* clear_values, |
| size_t clear_value_count, |
| vk::Rect2D viewport) { |
| FXL_DCHECK(is_active_); |
| KeepAlive(framebuffer); |
| |
| vk::RenderPassBeginInfo info; |
| info.renderPass = render_pass; |
| info.renderArea = viewport; |
| info.clearValueCount = static_cast<uint32_t>(clear_value_count); |
| info.pClearValues = clear_values; |
| info.framebuffer = framebuffer->vk(); |
| |
| command_buffer_.beginRenderPass(&info, vk::SubpassContents::eInline); |
| |
| vk::Viewport vk_viewport; |
| vk_viewport.x = static_cast<float>(viewport.offset.x); |
| vk_viewport.y = static_cast<float>(viewport.offset.y); |
| vk_viewport.width = static_cast<float>(viewport.extent.width); |
| vk_viewport.height = static_cast<float>(viewport.extent.height); |
| vk_viewport.minDepth = static_cast<float>(0.0f); |
| vk_viewport.maxDepth = static_cast<float>(1.0f); |
| command_buffer_.setViewport(0, 1, &vk_viewport); |
| |
| // TODO: probably unnecessary? |
| vk::Rect2D scissor; |
| scissor.extent.width = viewport.extent.width; |
| scissor.extent.height = viewport.extent.height; |
| scissor.offset.x = viewport.offset.x; |
| scissor.offset.y = viewport.offset.y; |
| command_buffer_.setScissor(0, 1, &scissor); |
| } |
| |
| void CommandBuffer::EndRenderPass() { command_buffer_.endRenderPass(); } |
| |
| bool CommandBuffer::Retire() { |
| if (!is_active_) { |
| // Submission failed, so proceed with cleanup. |
| FXL_DLOG(INFO) |
| << "CommandBuffer submission failed, proceeding with retirement"; |
| } else if (!is_submitted_) { |
| return false; |
| } else { |
| FXL_DCHECK(is_active_); |
| // Check if fence has been reached. |
| auto fence_status = device_.getFenceStatus(fence_); |
| if (fence_status == vk::Result::eNotReady) { |
| // Fence has not been reached; try again later. |
| return false; |
| } |
| } |
| is_active_ = is_submitted_ = false; |
| device_.resetFences(1, &fence_); |
| |
| if (callback_) { |
| TRACE_DURATION("gfx", "escher::CommandBuffer::Retire::callback"); |
| callback_(); |
| callback_ = nullptr; |
| } |
| |
| // TODO: move semaphores to pool for reuse? |
| wait_semaphores_.clear(); |
| wait_semaphores_for_submit_.clear(); |
| wait_semaphore_stages_.clear(); |
| signal_semaphores_.clear(); |
| signal_semaphores_for_submit_.clear(); |
| |
| auto result = command_buffer_.reset(vk::CommandBufferResetFlags()); |
| FXL_DCHECK(result == vk::Result::eSuccess); |
| |
| return true; |
| } |
| |
| } // namespace impl |
| } // namespace escher |