blob: fc62b37c91027b036bef96daf6e5831b411d8bbc [file] [log] [blame]
/* Copyright (c) 2017 Hans-Kristian Arntzen
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
// Based on the following files from the Granite rendering engine:
// - vulkan/command_buffer.cpp
#include "src/ui/lib/escher/third_party/granite/vk/command_buffer.h"
#include <algorithm>
#include "src/ui/lib/escher/escher.h"
#include "src/ui/lib/escher/impl/vulkan_utils.h"
#include "src/ui/lib/escher/util/bit_ops.h"
#include "src/ui/lib/escher/util/hasher.h"
#include "src/ui/lib/escher/util/trace_macros.h"
#include "src/ui/lib/escher/vk/impl/descriptor_set_allocator.h"
#include "src/ui/lib/escher/vk/impl/framebuffer.h"
#include "src/ui/lib/escher/vk/impl/framebuffer_allocator.h"
#include "src/ui/lib/escher/vk/shader_program.h"
#include "src/ui/lib/escher/vk/texture.h"
// TODO(ES-83): We currently wrap an old-style impl::CommandBuffer. This
// facilitates making the same "keep alive" functionality available to users of
// the old and new CommandBuffer classes. Once there are no direct users of the
// old-style CommandBuffers, fold the necessary functionality into this class
// and delete the old one.
#include "src/ui/lib/escher/impl/command_buffer_pool.h"
namespace escher {
CommandBufferPtr CommandBuffer::NewForType(Escher* escher, Type type, bool use_protected_memory) {
switch (type) {
case Type::kGraphics:
return NewForGraphics(escher, use_protected_memory);
case Type::kCompute:
return NewForCompute(escher, use_protected_memory);
case Type::kTransfer:
FXL_DCHECK(!use_protected_memory);
return NewForTransfer(escher);
default:
FXL_LOG(ERROR) << "Unrecognized CommandBuffer type requested. Cannot "
<< "create CommandBuffer.";
return nullptr;
}
}
CommandBufferPtr CommandBuffer::NewForGraphics(Escher* escher, bool use_protected_memory) {
return fxl::AdoptRef(new CommandBuffer(
escher->GetWeakPtr(), Type::kGraphics,
use_protected_memory ? escher->protected_command_buffer_pool()->GetCommandBuffer()
: escher->command_buffer_pool()->GetCommandBuffer()));
}
CommandBufferPtr CommandBuffer::NewForCompute(Escher* escher, bool use_protected_memory) {
return fxl::AdoptRef(new CommandBuffer(
escher->GetWeakPtr(), Type::kCompute,
use_protected_memory ? escher->protected_command_buffer_pool()->GetCommandBuffer()
: escher->command_buffer_pool()->GetCommandBuffer()));
}
CommandBufferPtr CommandBuffer::NewForTransfer(Escher* escher) {
auto pool = escher->transfer_command_buffer_pool() ? escher->transfer_command_buffer_pool()
: escher->command_buffer_pool();
return fxl::AdoptRef(
new CommandBuffer(escher->GetWeakPtr(), Type::kTransfer, pool->GetCommandBuffer()));
}
CommandBuffer::CommandBuffer(EscherWeakPtr escher, Type type, impl::CommandBuffer* impl)
: escher_(std::move(escher)),
type_(type),
impl_(impl),
vk_(impl_->vk()),
vk_device_(escher_->vk_device()) {}
bool CommandBuffer::Submit(CommandBufferFinishedCallback callback) {
vk::Queue queue;
switch (type_) {
case Type::kGraphics:
case Type::kCompute:
queue = escher_->device()->vk_main_queue();
break;
case Type::kTransfer:
queue = escher_->device()->vk_transfer_queue();
break;
default:
FXL_DCHECK(false);
return false;
}
return Submit(queue, std::move(callback));
}
void CommandBuffer::BeginCompute() {
is_compute_ = true;
BeginGraphicsOrComputeContext();
}
void CommandBuffer::BeginGraphics() {
is_compute_ = false;
BeginGraphicsOrComputeContext();
}
void CommandBuffer::BeginGraphicsOrComputeContext() {
dirty_ = ~0u;
dirty_descriptor_sets_ = ~0u;
current_vk_pipeline_ = vk::Pipeline();
current_vk_pipeline_layout_ = vk::PipelineLayout();
current_pipeline_layout_ = nullptr;
current_program_ = nullptr;
for (uint32_t i = 0; i < VulkanLimits::kNumDescriptorSets; ++i) {
DescriptorSetBindings* set = GetDescriptorSetBindings(i);
memset(set->uids, 0, sizeof(set->uids));
memset(set->secondary_uids, 0, sizeof(set->secondary_uids));
}
memset(&index_binding_, 0, sizeof(index_binding_));
pipeline_state_.BeginGraphicsOrComputeContext();
}
void CommandBuffer::BeginRenderPass(const RenderPassInfo& info) {
FXL_DCHECK(!IsInRenderPass());
framebuffer_ = escher_->framebuffer_allocator()->ObtainFramebuffer(info);
auto& render_pass = framebuffer_->render_pass();
pipeline_state_.set_render_pass(render_pass.get());
impl_->KeepAlive(framebuffer_.get());
impl_->KeepAlive(render_pass.get());
vk::Rect2D rect = info.render_area;
impl::ClipToRect(&rect, {{0, 0}, framebuffer_->extent()});
// Provide room for a clear value for each color attachment, plus one extra
// for the depth attachment.
vk::ClearValue clear_values[VulkanLimits::kNumColorAttachments + 1];
uint32_t num_clear_values = 0;
// Gather clear values for each color attachment that is flagged as needing
// clearing.
for (uint32_t i = 0; i < info.num_color_attachments; ++i) {
FXL_DCHECK(info.color_attachments[i]);
if (info.clear_attachments & (1u << i)) {
clear_values[i].color = info.clear_color[i];
num_clear_values = i + 1;
}
}
// Add a clear value for the depth attachment, but only if it exists and
// requires clearing.
if (info.depth_stencil_attachment &&
(info.op_flags & RenderPassInfo::kClearDepthStencilOp) != 0) {
clear_values[info.num_color_attachments].depthStencil = info.clear_depth_stencil;
num_clear_values = info.num_color_attachments + 1;
}
// Set the |layout_| of all color and depth stencil attachments to
// corresponding |finalLayout| values previously stored in render pass.
for (uint32_t i = 0; i < info.num_color_attachments; ++i) {
FXL_DCHECK(info.color_attachments[i]);
info.color_attachments[i]->image()->set_layout(render_pass->GetColorAttachmentFinalLayout(i));
}
if (info.depth_stencil_attachment) {
info.depth_stencil_attachment->image()->set_layout(
render_pass->GetDepthStencilAttachmentFinalLayout());
}
vk::RenderPassBeginInfo begin_info;
begin_info.renderPass = render_pass->vk();
begin_info.framebuffer = framebuffer_->vk();
begin_info.renderArea = rect;
begin_info.clearValueCount = num_clear_values;
begin_info.pClearValues = clear_values;
// Escher's use-cases are not amenable to creating reusable secondary command
// buffers. Therefore, we always encode commands for the render pass inline,
// in the same command buffer.
vk_.beginRenderPass(begin_info, vk::SubpassContents::eInline);
// BeginGraphics() will dirty everything; no need to dirty anything here.
scissor_ = rect;
viewport_ = vk::Viewport(0.0f, 0.0f, framebuffer_->width(), framebuffer_->height(), 0.0f, 1.0f);
BeginGraphics();
}
void CommandBuffer::EndRenderPass() {
FXL_DCHECK(IsInRenderPass());
vk().endRenderPass();
framebuffer_ = nullptr;
pipeline_state_.set_render_pass(nullptr);
BeginCompute();
}
bool CommandBuffer::IsInRenderPass() {
FXL_DCHECK(!framebuffer_ == !pipeline_state_.render_pass());
return static_cast<bool>(pipeline_state_.render_pass());
}
void CommandBuffer::BufferBarrier(const BufferPtr& buffer, vk::PipelineStageFlags src_stages,
vk::AccessFlags src_access, vk::PipelineStageFlags dst_stages,
vk::AccessFlags dst_access) {
impl_->KeepAlive(buffer.get());
vk::BufferMemoryBarrier barrier;
barrier.srcAccessMask = src_access;
barrier.dstAccessMask = dst_access;
barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
barrier.buffer = buffer->vk();
barrier.offset = 0U;
barrier.size = VK_WHOLE_SIZE;
vk().pipelineBarrier(src_stages, dst_stages, {}, {}, {std::move(barrier)}, {});
}
void CommandBuffer::ImageBarrier(const ImagePtr& image, vk::ImageLayout old_layout,
vk::ImageLayout new_layout, vk::PipelineStageFlags src_stages,
vk::AccessFlags src_access, vk::PipelineStageFlags dst_stages,
vk::AccessFlags dst_access) {
// Render passes may also cause image layout transitions. We haven't worked
// through all of the corner cases with respect to our per-image layout
// tracking. Therefore, since we don't currently need image barriers during
// render passes, we disallow them.
FXL_DCHECK(!IsInRenderPass());
FXL_DCHECK(!image->is_transient());
FXL_DCHECK(image->layout() == old_layout || old_layout == vk::ImageLayout::eUndefined ||
old_layout == vk::ImageLayout::ePreinitialized);
impl_->KeepAlive(image.get());
vk::ImageMemoryBarrier barrier;
barrier.srcAccessMask = src_access;
barrier.dstAccessMask = dst_access;
barrier.oldLayout = old_layout;
barrier.newLayout = new_layout;
barrier.image = image->vk();
barrier.subresourceRange.aspectMask =
image_utils::FormatToColorOrDepthStencilAspectFlags(image->format());
barrier.subresourceRange.levelCount = VK_REMAINING_MIP_LEVELS;
barrier.subresourceRange.layerCount = VK_REMAINING_ARRAY_LAYERS;
barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
image->set_layout(new_layout);
vk().pipelineBarrier(src_stages, dst_stages, {}, 0, nullptr, 0, nullptr, 1, &barrier);
}
void CommandBuffer::PushConstants(const void* data, vk::DeviceSize offset, vk::DeviceSize range) {
FXL_DCHECK(offset + range <= VulkanLimits::kPushConstantSize);
memcpy(bindings_.push_constant_data + offset, data, range);
SetDirty(kDirtyPushConstantsBit);
}
void CommandBuffer::BindUniformBuffer(uint32_t set, uint32_t binding, const BufferPtr& buffer) {
BindUniformBuffer(set, binding, buffer.get(), 0, buffer->size());
}
void CommandBuffer::BindUniformBuffer(uint32_t set, uint32_t binding, const BufferPtr& buffer,
vk::DeviceSize offset, vk::DeviceSize range) {
BindUniformBuffer(set, binding, buffer.get(), offset, range);
}
void CommandBuffer::BindUniformBuffer(uint32_t set_index, uint32_t binding, Buffer* buffer,
vk::DeviceSize offset, vk::DeviceSize range) {
auto set = GetDescriptorSetBindings(set_index);
auto b = GetDescriptorBindingInfo(set, binding);
impl_->KeepAlive(buffer);
if (buffer->uid() == set->uids[binding] && b->buffer.offset == offset &&
b->buffer.range == range) {
return;
}
b->buffer = vk::DescriptorBufferInfo{buffer->vk(), offset, range};
set->uids[binding] = buffer->uid();
dirty_descriptor_sets_ |= 1u << set_index;
}
void CommandBuffer::BindTexture(unsigned set_index, unsigned binding, Texture* texture) {
auto set = GetDescriptorSetBindings(set_index);
auto b = GetDescriptorBindingInfo(set, binding);
auto& image = texture->image();
FXL_DCHECK(image->info().usage & vk::ImageUsageFlagBits::eSampled);
impl_->KeepAlive(texture);
vk::ImageLayout vk_layout = image->layout();
if (texture->uid() == set->uids[binding] && b->image.fp.imageLayout == vk_layout &&
// TODO(ES-83): if we reify Samplers as a separate resource type, then use
// the sampler's uid instead of the texture.
texture->uid() == set->secondary_uids[binding]) {
// The image, layout, and sampler are all unchanged, so we do not need to
// update any bindings, nor mark the descriptor set as dirty.
return;
}
b->image.fp.imageLayout = vk_layout;
b->image.fp.imageView = texture->vk_float_view();
b->image.fp.sampler = texture->sampler()->vk();
b->image.integer.imageLayout = vk_layout;
b->image.integer.imageView = texture->vk_integer_view();
b->image.integer.sampler = texture->sampler()->vk();
set->uids[binding] = texture->uid();
// TODO(ES-83): if we reify Samplers as a separate resource type,
// then use the sampler's uid instead of the texture.
set->secondary_uids[binding] = texture->uid();
dirty_descriptor_sets_ |= 1u << set_index;
}
void CommandBuffer::BindVertices(uint32_t binding, vk::Buffer buffer, vk::DeviceSize offset,
vk::DeviceSize stride, vk::VertexInputRate step_rate) {
FXL_DCHECK(IsInRenderPass());
if (pipeline_state_.BindVertices(binding, buffer, offset, stride, step_rate)) {
// Pipeline change is required.
SetDirty(kDirtyStaticVertexBit);
}
}
void CommandBuffer::BindVertices(uint32_t binding, Buffer* buffer, vk::DeviceSize offset,
vk::DeviceSize stride, vk::VertexInputRate step_rate) {
impl_->KeepAlive(buffer);
BindVertices(binding, buffer->vk(), offset, stride, step_rate);
}
void CommandBuffer::BindIndices(vk::Buffer buffer, vk::DeviceSize offset,
vk::IndexType index_type) {
if (index_binding_.buffer == buffer && index_binding_.offset == offset &&
index_binding_.index_type == index_type) {
// Bindings are unchanged.
return;
}
TRACE_DURATION("gfx", "escher::CommandBuffer::BindIndices");
// Index buffer changes never require a new pipeline to be generated, so it is
// OK to make this change immediately.
index_binding_.buffer = buffer;
index_binding_.offset = offset;
index_binding_.index_type = index_type;
vk().bindIndexBuffer(buffer, offset, index_type);
}
void CommandBuffer::BindIndices(const BufferPtr& buffer, vk::DeviceSize offset,
vk::IndexType index_type) {
BindIndices(buffer->vk(), offset, index_type);
impl_->KeepAlive(buffer.get());
}
void CommandBuffer::DrawIndexed(uint32_t index_count, uint32_t instance_count, uint32_t first_index,
int32_t vertex_offset, uint32_t first_instance) {
TRACE_DURATION("gfx", "escher::CommandBuffer::DrawIndexed");
FXL_DCHECK(current_program_);
FXL_DCHECK(!is_compute_);
FXL_DCHECK(index_binding_.buffer);
FlushRenderState();
TRACE_DURATION("gfx", "escher::CommandBuffer::DrawIndexed[vulkan]");
vk().drawIndexed(index_count, instance_count, first_index, vertex_offset, first_instance);
}
void CommandBuffer::FlushRenderState() {
TRACE_DURATION("gfx", "escher::CommandBuffer::FlushRenderState");
FXL_DCHECK(current_pipeline_layout_);
FXL_DCHECK(current_program_);
// We've invalidated pipeline state, update the VkPipeline.
if (GetAndClearDirty(kDirtyStaticStateBit | kDirtyPipelineBit | kDirtyStaticVertexBit)) {
// Update |current_pipeline_|, keeping track of the old one so we can see if
// there was a change.
vk::Pipeline previous_pipeline = current_vk_pipeline_;
FlushGraphicsPipeline();
if (previous_pipeline != current_vk_pipeline_) {
vk().bindPipeline(vk::PipelineBindPoint::eGraphics, current_vk_pipeline_);
// According to the Vulkan spec (Section 9.9 "Dynamic State"), it would
// theoretically be possible to track which state is statically vs.
// dynamically set for a given pipeline, and only set kDirtyDynamicBits
// when the new pipeline leaves some of the previous state in an undefined
// state. However, it is not worth the additional complexity.
SetDirty(kDirtyDynamicBits);
}
}
FlushDescriptorSets();
const PipelineStaticState* static_pipeline_state = pipeline_state_.static_state();
if (GetAndClearDirty(kDirtyPushConstantsBit)) {
TRACE_DURATION("gfx", "escher::CommandBuffer::FlushRenderState[push_constants]");
// The push constants were invalidated (perhaps by being explicitly set, or
// perhaps by a change in the descriptor set layout; it doesn't matter).
uint32_t num_ranges = current_pipeline_layout_->spec().num_push_constant_ranges();
for (unsigned i = 0; i < num_ranges; ++i) {
auto& range = current_pipeline_layout_->spec().push_constant_ranges()[i];
vk().pushConstants(current_vk_pipeline_layout_, range.stageFlags, range.offset, range.size,
bindings_.push_constant_data + range.offset);
}
}
if (GetAndClearDirty(kDirtyViewportBit)) {
TRACE_DURATION("gfx", "escher::CommandBuffer::FlushRenderState[viewport]");
vk().setViewport(0, 1, &viewport_);
}
if (GetAndClearDirty(kDirtyScissorBit)) {
TRACE_DURATION("gfx", "escher::CommandBuffer::FlushRenderState[scissor]");
vk().setScissor(0, 1, &scissor_);
}
if (static_pipeline_state->depth_bias_enable && GetAndClearDirty(kDirtyDepthBiasBit)) {
TRACE_DURATION("gfx", "escher::CommandBuffer::FlushRenderState[depth_bias]");
vk().setDepthBias(dynamic_state_.depth_bias_constant, 0.0f, dynamic_state_.depth_bias_slope);
}
if (static_pipeline_state->stencil_test && GetAndClearDirty(kDirtyStencilMasksAndReferenceBit)) {
TRACE_DURATION("gfx", "escher::CommandBuffer::FlushRenderState[stencil]");
vk().setStencilCompareMask(vk::StencilFaceFlagBits::eFront, dynamic_state_.front_compare_mask);
vk().setStencilReference(vk::StencilFaceFlagBits::eFront, dynamic_state_.front_reference);
vk().setStencilWriteMask(vk::StencilFaceFlagBits::eFront, dynamic_state_.front_write_mask);
vk().setStencilCompareMask(vk::StencilFaceFlagBits::eBack, dynamic_state_.back_compare_mask);
vk().setStencilReference(vk::StencilFaceFlagBits::eBack, dynamic_state_.back_reference);
vk().setStencilWriteMask(vk::StencilFaceFlagBits::eBack, dynamic_state_.back_write_mask);
}
// Bind all vertex buffers that are both active and dirty.
pipeline_state_.FlushVertexBuffers(vk());
}
void CommandBuffer::FlushGraphicsPipeline() {
TRACE_DURATION("gfx", "escher::CommandBuffer::FlushGraphicsPipeline");
current_vk_pipeline_ =
pipeline_state_.FlushGraphicsPipeline(current_pipeline_layout_, current_program_);
}
void CommandBuffer::FlushDescriptorSets() {
TRACE_DURATION("gfx", "escher::CommandBuffer::FlushDescriptorSets");
auto& spec = current_pipeline_layout_->spec();
uint32_t sets_to_flush = spec.descriptor_set_mask() & dirty_descriptor_sets_;
ForEachBitIndex(sets_to_flush, [this](uint32_t set_index) { FlushDescriptorSet(set_index); });
// All descriptor sets that weren't flushed remain dirty.
dirty_descriptor_sets_ &= ~sets_to_flush;
}
void CommandBuffer::FlushDescriptorSet(uint32_t set_index) {
const impl::DescriptorSetLayout& set_layout =
current_pipeline_layout_->spec().descriptor_set_layouts(set_index);
const auto set_bindings = GetDescriptorSetBindings(set_index);
uint32_t num_dynamic_offsets = 0;
uint32_t dynamic_offsets[VulkanLimits::kNumBindings];
Hasher h;
h.u32(set_layout.fp_mask);
// UBOs
ForEachBitIndex(set_layout.uniform_buffer_mask, [&](uint32_t binding) {
const auto b = GetDescriptorBindingInfo(set_bindings, binding);
FXL_DCHECK(b->buffer.buffer) << "No buffer for uniform binding " << set_index << "," << binding;
FXL_DCHECK(set_bindings->uids[binding]);
h.u64(set_bindings->uids[binding]);
h.u32(b->buffer.range);
dynamic_offsets[num_dynamic_offsets++] = b->buffer.offset;
});
// SSBOs
ForEachBitIndex(set_layout.storage_buffer_mask, [&](uint32_t binding) {
const auto b = GetDescriptorBindingInfo(set_bindings, binding);
FXL_DCHECK(b->buffer.buffer);
FXL_DCHECK(set_bindings->uids[binding]);
h.u64(set_bindings->uids[binding]);
h.u32(b->buffer.offset);
h.u32(b->buffer.range);
});
// Sampled buffers
ForEachBitIndex(set_layout.sampled_buffer_mask, [&](uint32_t binding) {
const auto b = GetDescriptorBindingInfo(set_bindings, binding);
FXL_DCHECK(b->buffer_view);
FXL_DCHECK(set_bindings->uids[binding]);
h.u64(set_bindings->uids[binding]);
});
// Sampled images
ForEachBitIndex(set_layout.sampled_image_mask, [&](uint32_t binding) {
const auto b = GetDescriptorBindingInfo(set_bindings, binding);
FXL_DCHECK(b->image.fp.imageView);
FXL_DCHECK(b->image.fp.sampler);
FXL_DCHECK(set_bindings->uids[binding]);
h.u64(set_bindings->uids[binding]);
h.u64(set_bindings->secondary_uids[binding]);
h.u32(EnumCast(b->image.fp.imageLayout));
});
// Storage images
ForEachBitIndex(set_layout.storage_image_mask, [&](uint32_t binding) {
const auto b = GetDescriptorBindingInfo(set_bindings, binding);
FXL_DCHECK(b->image.fp.imageView);
FXL_DCHECK(set_bindings->uids[binding]);
h.u64(set_bindings->uids[binding]);
h.u32(EnumCast(b->image.fp.imageLayout));
});
// Input attachments
ForEachBitIndex(set_layout.input_attachment_mask, [&](uint32_t binding) {
const auto b = GetDescriptorBindingInfo(set_bindings, binding);
FXL_DCHECK(b->image.fp.imageView);
FXL_DCHECK(set_bindings->uids[binding]);
h.u64(set_bindings->uids[binding]);
h.u32(EnumCast(b->image.fp.imageLayout));
});
Hash hash = h.value();
auto pair = current_pipeline_layout_->GetDescriptorSetAllocator(set_index)->Get(hash);
vk::DescriptorSet vk_set = pair.first;
// The descriptor set was not found in the cache; rebuild.
if (!pair.second) {
WriteDescriptors(set_index, vk_set, set_layout);
}
vk().bindDescriptorSets(
IsInRenderPass() ? vk::PipelineBindPoint::eGraphics : vk::PipelineBindPoint::eCompute,
current_vk_pipeline_layout_, set_index, 1, &vk_set, num_dynamic_offsets, dynamic_offsets);
}
void CommandBuffer::WriteDescriptors(uint32_t set_index, vk::DescriptorSet vk_set,
const impl::DescriptorSetLayout& set_layout) {
const auto set_bindings = GetDescriptorSetBindings(set_index);
uint32_t write_count = 0;
uint32_t buffer_info_count = 0;
vk::WriteDescriptorSet writes[VulkanLimits::kNumBindings];
vk::DescriptorBufferInfo buffer_info[VulkanLimits::kNumBindings];
ForEachBitIndex(set_layout.uniform_buffer_mask, [&](uint32_t binding) {
auto& write = writes[write_count++];
write.descriptorCount = 1;
write.descriptorType = vk::DescriptorType::eUniformBufferDynamic;
write.dstArrayElement = 0;
write.dstBinding = binding;
write.dstSet = vk_set;
// Offsets are applied dynamically.
auto& buffer = buffer_info[buffer_info_count++];
const auto b = GetDescriptorBindingInfo(set_bindings, binding);
buffer = b->buffer;
buffer.offset = 0;
write.pBufferInfo = &buffer;
});
ForEachBitIndex(set_layout.storage_buffer_mask, [&](uint32_t binding) {
auto& write = writes[write_count++];
write.descriptorCount = 1;
write.descriptorType = vk::DescriptorType::eStorageBuffer;
write.dstArrayElement = 0;
write.dstBinding = binding;
write.dstSet = vk_set;
const auto b = GetDescriptorBindingInfo(set_bindings, binding);
write.pBufferInfo = &b->buffer;
});
ForEachBitIndex(set_layout.sampled_buffer_mask, [&](uint32_t binding) {
auto& write = writes[write_count++];
write.descriptorCount = 1;
write.descriptorType = vk::DescriptorType::eUniformTexelBuffer;
write.dstArrayElement = 0;
write.dstBinding = binding;
write.dstSet = vk_set;
const auto b = GetDescriptorBindingInfo(set_bindings, binding);
write.pTexelBufferView = &b->buffer_view;
});
vk::DescriptorType image_descriptor_type;
auto write_image_descriptor = [&](uint32_t binding) {
auto& write = writes[write_count++];
write.descriptorCount = 1;
write.descriptorType = image_descriptor_type;
write.dstArrayElement = 0;
write.dstBinding = binding;
write.dstSet = vk_set;
const auto b = GetDescriptorBindingInfo(set_bindings, binding);
if (set_layout.fp_mask & (1u << binding)) {
write.pImageInfo = &b->image.fp;
} else {
write.pImageInfo = &b->image.integer;
}
};
image_descriptor_type = vk::DescriptorType::eCombinedImageSampler;
ForEachBitIndex(set_layout.sampled_image_mask, write_image_descriptor);
image_descriptor_type = vk::DescriptorType::eStorageImage;
ForEachBitIndex(set_layout.storage_image_mask, write_image_descriptor);
image_descriptor_type = vk::DescriptorType::eInputAttachment;
ForEachBitIndex(set_layout.input_attachment_mask, write_image_descriptor);
vk_device().updateDescriptorSets(write_count, writes, 0, nullptr);
}
void CommandBuffer::SetToDefaultState(DefaultState default_state) {
PipelineStaticState* static_state = pipeline_state_.static_state();
memset(static_state, 0, sizeof(PipelineStaticState));
SetDirty(kDirtyStaticStateBit);
// The following state is common to all currently-supported defaults.
static_state->front_face = VK_FRONT_FACE_CLOCKWISE;
static_state->cull_mode = VK_CULL_MODE_BACK_BIT;
static_state->depth_test = true;
static_state->depth_compare = VK_COMPARE_OP_LESS_OR_EQUAL;
static_state->depth_write = true;
static_state->depth_bias_enable = false;
static_state->primitive_restart = false;
static_state->stencil_test = false;
static_state->primitive_topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
static_state->color_write_mask = ~0u;
// These states differ between the various supported defaults.
switch (default_state) {
case DefaultState::kWireframe: {
static_state->wireframe = true;
static_state->blend_enable = false;
} break;
case DefaultState::kOpaque: {
static_state->blend_enable = false;
} break;
case DefaultState::kTranslucent: {
// See definition in header for explanation of these blend factors.
static_state->blend_enable = true;
static_state->src_color_blend = VK_BLEND_FACTOR_SRC_ALPHA;
static_state->dst_color_blend = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
static_state->src_alpha_blend = VK_BLEND_FACTOR_ONE;
static_state->dst_alpha_blend = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
static_state->color_blend_op = VK_BLEND_OP_ADD;
static_state->alpha_blend_op = VK_BLEND_OP_ADD;
} break;
}
}
void CommandBuffer::SaveState(CommandBuffer::SavedStateFlags flags,
CommandBuffer::SavedState* state) const {
TRACE_DURATION("gfx", "escher::CommandBuffer::SaveState");
for (unsigned i = 0; i < VulkanLimits::kNumDescriptorSets; ++i) {
if (flags & (kSavedBindingsBit0 << i)) {
memcpy(&state->bindings.descriptor_sets[i], &bindings_.descriptor_sets[i],
sizeof(bindings_.descriptor_sets[i]));
}
}
if (flags & kSavedViewportBit) {
state->viewport = viewport_;
}
if (flags & kSavedScissorBit) {
state->scissor = scissor_;
}
if (flags & kSavedRenderStateBit) {
memcpy(&state->static_state, pipeline_state_.static_state(), sizeof(PipelineStaticState));
state->potential_static_state = *pipeline_state_.potential_static_state();
state->dynamic_state = dynamic_state_;
}
if (flags & kSavedPushConstantBit) {
memcpy(state->bindings.push_constant_data, bindings_.push_constant_data,
sizeof(bindings_.push_constant_data));
}
state->flags = flags;
}
void CommandBuffer::RestoreState(const CommandBuffer::SavedState& state) {
TRACE_DURATION("gfx", "escher::CommandBuffer::RestoreState");
for (unsigned i = 0; i < VulkanLimits::kNumDescriptorSets; ++i) {
if (state.flags & (kSavedBindingsBit0 << i)) {
if (memcmp(&state.bindings.descriptor_sets[i], &bindings_.descriptor_sets[i],
sizeof(bindings_.descriptor_sets[i]))) {
memcpy(&bindings_.descriptor_sets[i], &state.bindings.descriptor_sets[i],
sizeof(bindings_.descriptor_sets[i]));
dirty_descriptor_sets_ |= 1u << i;
}
}
}
if (state.flags & kSavedPushConstantBit) {
if (memcmp(state.bindings.push_constant_data, bindings_.push_constant_data,
sizeof(bindings_.push_constant_data))) {
memcpy(bindings_.push_constant_data, state.bindings.push_constant_data,
sizeof(bindings_.push_constant_data));
SetDirty(kDirtyPushConstantsBit);
}
}
if ((state.flags & kSavedViewportBit) && memcmp(&state.viewport, &viewport_, sizeof(viewport_))) {
viewport_ = state.viewport;
SetDirty(kDirtyViewportBit);
}
if ((state.flags & kSavedScissorBit) && memcmp(&state.scissor, &scissor_, sizeof(scissor_))) {
scissor_ = state.scissor;
SetDirty(kDirtyScissorBit);
}
if (state.flags & kSavedRenderStateBit) {
if (memcmp(&state.static_state, pipeline_state_.static_state(), sizeof(state.static_state))) {
memcpy(pipeline_state_.static_state(), &state.static_state, sizeof(state.static_state));
SetDirty(kDirtyStaticStateBit);
}
if (memcmp(&state.potential_static_state, pipeline_state_.potential_static_state(),
sizeof(state.potential_static_state))) {
memcpy(pipeline_state_.potential_static_state(), &state.potential_static_state,
sizeof(state.potential_static_state));
SetDirty(kDirtyStaticStateBit);
}
if (memcmp(&state.dynamic_state, &dynamic_state_, sizeof(dynamic_state_))) {
memcpy(&dynamic_state_, &state.dynamic_state, sizeof(dynamic_state_));
SetDirty(kDirtyStencilMasksAndReferenceBit | kDirtyDepthBiasBit);
}
}
}
void CommandBuffer::ClearAttachmentRect(uint32_t attachment, const vk::ClearRect& rect,
const vk::ClearValue& value, vk::ImageAspectFlags aspect) {
FXL_DCHECK(IsInRenderPass());
vk::ClearAttachment att = {};
att.clearValue = value;
att.colorAttachment = attachment;
att.aspectMask = aspect;
vk().clearAttachments(1, &att, 1, &rect);
}
void CommandBuffer::ClearColorAttachmentRect(uint32_t subpass_color_attachment_index,
vk::Offset2D offset, vk::Extent2D extent,
const vk::ClearColorValue& value) {
ClearAttachmentRect(
subpass_color_attachment_index,
vk::ClearRect(vk::Rect2D(offset, extent), 0 /*baseArrayLayer*/, 1 /*layerCount*/), value,
vk::ImageAspectFlagBits::eColor);
}
void CommandBuffer::ClearDepthStencilAttachmentRect(vk::Offset2D offset, vk::Extent2D extent,
const vk::ClearDepthStencilValue& value,
vk::ImageAspectFlags aspect) {
ClearAttachmentRect(
0, vk::ClearRect(vk::Rect2D(offset, extent), 0 /*baseArrayLayer*/, 1 /*layerCount*/), value,
aspect);
}
void CommandBuffer::Blit(const ImagePtr& src_image, vk::Offset2D src_offset,
vk::Extent2D src_extent, const ImagePtr& dst_image,
vk::Offset2D dst_offset, vk::Extent2D dst_extent, vk::Filter filter) {
impl_->KeepAlive(src_image);
impl_->KeepAlive(dst_image);
vk::ImageBlit blit;
blit.srcSubresource.aspectMask = vk::ImageAspectFlagBits::eColor;
blit.srcSubresource.mipLevel = 0;
blit.srcSubresource.baseArrayLayer = 0;
blit.srcSubresource.layerCount = 1;
blit.dstSubresource = blit.srcSubresource;
blit.srcOffsets[0] = vk::Offset3D(src_offset.x, src_offset.y, 0);
blit.srcOffsets[1] =
vk::Offset3D(src_offset.x + src_extent.width, src_offset.y + src_extent.height, 1);
blit.dstOffsets[0] = vk::Offset3D(dst_offset.x, dst_offset.y, 0);
blit.dstOffsets[1] =
vk::Offset3D(dst_offset.x + dst_extent.width, dst_offset.y + dst_extent.height, 1);
vk().blitImage(src_image->vk(), vk::ImageLayout::eTransferSrcOptimal, dst_image->vk(),
vk::ImageLayout::eTransferDstOptimal, 1, &blit, filter);
}
void CommandBuffer::SetShaderProgram(ShaderProgram* program, const SamplerPtr& immutable_sampler) {
// TODO(ES-83): checking the uid() isn't really necessary since we're using
// ref-counted pointers... a pointer comparison would be enough. This is a
// general difference between Escher and the original Granite code; we should
// come up with a general design philosophy and stick to it.
if (current_program_ && current_program_->uid() == program->uid() && current_pipeline_layout_ &&
current_pipeline_layout_->spec().immutable_sampler() == immutable_sampler) {
return;
}
current_vk_pipeline_ = vk::Pipeline();
current_program_ = program;
impl_->KeepAlive(current_program_);
// If we're in a render pass, the program must have a vertex stage. If we're
// not, the program must have a compute stage.
FXL_DCHECK(
pipeline_state_.render_pass() && current_program_->GetModuleForStage(ShaderStage::kVertex) ||
!pipeline_state_.render_pass() && current_program_->GetModuleForStage(ShaderStage::kCompute));
SetDirty(kDirtyPipelineBit | kDirtyDynamicBits);
auto old_pipeline_layout = current_pipeline_layout_;
current_pipeline_layout_ = program->ObtainPipelineLayout(immutable_sampler);
current_vk_pipeline_layout_ = current_pipeline_layout_->vk();
impl_->KeepAlive(current_pipeline_layout_);
if (!old_pipeline_layout) {
dirty_descriptor_sets_ = ~0u;
SetDirty(kDirtyPushConstantsBit);
} else if (old_pipeline_layout != current_pipeline_layout_) {
auto& new_spec = current_pipeline_layout_->spec();
auto& old_spec = old_pipeline_layout->spec();
// If the push constant layout changes, all descriptor sets
// are invalidated.
if (new_spec.push_constant_layout_hash() != old_spec.push_constant_layout_hash()) {
dirty_descriptor_sets_ = ~0u;
SetDirty(kDirtyPushConstantsBit);
} else {
// Find the first set whose descriptor set layout differs. Set dirty bits
// for that set and all subsequent sets.
for (uint32_t set_index = 0; set_index < VulkanLimits::kNumDescriptorSets; ++set_index) {
if (current_pipeline_layout_->GetDescriptorSetAllocator(set_index) !=
old_pipeline_layout->GetDescriptorSetAllocator(set_index)) {
SetBitsAtAndAboveIndex(&dirty_descriptor_sets_, set_index);
break;
}
}
}
}
}
} // namespace escher