blob: da37eac387061cb1cc9f44d16ac42f8c3ac5679e [file] [log] [blame]
// 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 */,
&region);
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