blob: 7a3842356679de2271d636155254241698732f19 [file] [log] [blame]
// Copyright 2017 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/model_display_list_builder.h"
#include <glm/gtx/transform.hpp>
#include "lib/escher/impl/command_buffer.h"
#include "lib/escher/impl/model_pipeline_cache.h"
#include "lib/escher/impl/model_render_pass.h"
#include "lib/escher/impl/model_renderer.h"
#include "lib/escher/impl/z_sort.h"
#include "lib/escher/renderer/shadow_map.h"
#include "lib/escher/scene/camera.h"
#include "lib/escher/util/align.h"
namespace escher {
namespace impl {
namespace {
// TODO(ES-102): should be queried from device.
constexpr vk::DeviceSize kMinUniformBufferOffsetAlignment = 256;
} // namespace
static mat4 AdjustCameraTransform(const Stage& stage, const Camera& camera,
float scale) {
// Adjust projection matrix to support downsampled render passes.
mat4 scale_adjustment(1.0);
scale_adjustment[0][0] = scale;
scale_adjustment[1][1] = scale;
scale_adjustment[3][0] = scale - 1.f;
scale_adjustment[3][1] = scale - 1.f;
return scale_adjustment * camera.projection();
}
ModelDisplayListBuilder::~ModelDisplayListBuilder() = default;
ModelDisplayListBuilder::ModelDisplayListBuilder(
vk::Device device, const Stage& stage, const Model& model,
const Camera& camera, float scale, const TexturePtr& white_texture,
const TexturePtr& shadow_texture, const mat4& shadow_matrix,
vec3 ambient_light_intensity, vec3 direct_light_intensity,
ModelData* model_data, ModelRenderer* renderer,
ModelRenderPassPtr render_pass, ModelDisplayListFlags flags)
: device_(device),
volume_(stage.viewing_volume()),
view_transform_(camera.transform()),
projection_transform_(AdjustCameraTransform(stage, camera, scale)),
use_material_textures_(render_pass->UseMaterialTextures()),
disable_depth_test_(flags & ModelDisplayListFlag::kDisableDepthTest),
white_texture_(white_texture),
shadow_texture_(shadow_texture ? shadow_texture : white_texture),
shadow_matrix_(shadow_matrix),
renderer_(renderer),
render_pass_(std::move(render_pass)),
pipeline_cache_(render_pass_->pipeline_cache()),
uniform_buffer_pool_(model_data->uniform_buffer_pool()),
per_model_descriptor_set_pool_(
model_data->per_model_descriptor_set_pool()),
per_object_descriptor_set_pool_(
model_data->per_object_descriptor_set_pool()) {
FXL_DCHECK(white_texture_);
// Obtain a uniform buffer and write the PerModel data to it.
PrepareUniformBufferForWriteOfSize(sizeof(ModelData::PerModel),
kMinUniformBufferOffsetAlignment);
auto per_model = reinterpret_cast<ModelData::PerModel*>(
&(uniform_buffer_->host_ptr()[uniform_buffer_write_index_]));
per_model->frag_coord_to_uv_multiplier =
vec2(1.f / volume_.width(), 1.f / volume_.height());
per_model->ambient_light_intensity = ambient_light_intensity;
per_model->direct_light_intensity = direct_light_intensity;
per_model->time = model.time();
if (shadow_texture) {
textures_.push_back(shadow_texture);
per_model->shadow_map_uv_multiplier =
vec2(1.f / shadow_texture->width(), 1.f / shadow_texture->height());
}
// Obtain the single per-Model descriptor set.
DescriptorSetAllocationPtr per_model_descriptor_set_allocation =
per_model_descriptor_set_pool_->Allocate(1, nullptr);
{
// It would be inconvenient to set this in the initializer, so we briefly
// cast it to set its permanent value.
auto& unconst = const_cast<vk::DescriptorSet&>(per_model_descriptor_set_);
unconst = per_model_descriptor_set_allocation->get(0);
}
resources_.push_back(std::move(per_model_descriptor_set_allocation));
// Update each descriptor in the PerModel descriptor set.
vk::WriteDescriptorSet writes[ModelData::PerModel::kDescriptorCount];
auto& buffer_write = writes[0];
buffer_write.dstSet = per_model_descriptor_set_;
buffer_write.dstBinding = ModelData::PerModel::kDescriptorSetUniformBinding;
buffer_write.dstArrayElement = 0;
buffer_write.descriptorCount = 1;
buffer_write.descriptorType = vk::DescriptorType::eUniformBuffer;
vk::DescriptorBufferInfo buffer_info;
buffer_info.buffer = uniform_buffer_->vk();
buffer_info.range = sizeof(ModelData::PerModel);
buffer_info.offset = uniform_buffer_write_index_;
buffer_write.pBufferInfo = &buffer_info;
auto& image_write = writes[1];
image_write.dstSet = per_model_descriptor_set_;
image_write.dstBinding = ModelData::PerModel::kDescriptorSetSamplerBinding;
image_write.dstArrayElement = 0;
image_write.descriptorCount = 1;
image_write.descriptorType = vk::DescriptorType::eCombinedImageSampler;
vk::DescriptorImageInfo image_info;
image_info.imageLayout = vk::ImageLayout::eShaderReadOnlyOptimal;
image_info.imageView = shadow_texture_->vk_image_view();
image_info.sampler = shadow_texture_->vk_sampler();
image_write.pImageInfo = &image_info;
uniform_buffer_write_index_ += sizeof(ModelData::PerModel);
BufferPtr vp_uniform_buffer;
uint32_t vp_uniform_buffer_offset = 0;
if (camera.pose_buffer()) {
// If the camera has a pose buffer bind the output of the late latching
// shader to the VP uniform.
vp_uniform_buffer = camera.latched_pose_buffer();
// Pose buffer latching shader writes the latched pose before the VP matrix
// so we need to offset past it.
vp_uniform_buffer_offset = sizeof(hmd::Pose);
// Keep the latched pose buffer alive for the lifetime of the display list.
resources_.push_back(camera.latched_pose_buffer());
} else {
// If the camera does not have a pose buffer obtain a uniform buffer
// in the usual way and write the ViewProjection data into it directly.
PrepareUniformBufferForWriteOfSize(sizeof(ModelData::ViewProjection),
kMinUniformBufferOffsetAlignment);
auto view_projection = reinterpret_cast<ModelData::ViewProjection*>(
&(uniform_buffer_->host_ptr()[uniform_buffer_write_index_]));
view_projection->vp_matrix = projection_transform_ * view_transform_;
vp_uniform_buffer = uniform_buffer_;
vp_uniform_buffer_offset = uniform_buffer_write_index_;
}
auto& vp_buffer_write = writes[2];
vp_buffer_write.dstSet = per_model_descriptor_set_;
vp_buffer_write.dstBinding =
ModelData::ViewProjection::kDescriptorSetUniformBinding;
vp_buffer_write.dstArrayElement = 0;
vp_buffer_write.descriptorCount = 1;
vp_buffer_write.descriptorType = vk::DescriptorType::eUniformBuffer;
vk::DescriptorBufferInfo vp_buffer_info;
vp_buffer_info.buffer = vp_uniform_buffer->vk();
vp_buffer_info.range = sizeof(ModelData::ViewProjection);
vp_buffer_info.offset = vp_uniform_buffer_offset;
vp_buffer_write.pBufferInfo = &vp_buffer_info;
uniform_buffer_write_index_ += sizeof(ModelData::ViewProjection);
device_.updateDescriptorSets(ModelData::PerModel::kDescriptorCount, writes, 0,
nullptr);
}
void ModelDisplayListBuilder::AddClipperObject(const Object& object) {
if (object.shape().type() == Shape::Type::kNone) {
// The object has no shape to clip against.
return;
}
FXL_DCHECK(object.shape().modifiers() == ShapeModifiers());
PrepareUniformBufferForWriteOfSize(sizeof(ModelData::PerObject),
kMinUniformBufferOffsetAlignment);
vk::DescriptorSet descriptor_set = ObtainPerObjectDescriptorSet();
UpdateDescriptorSetForObject(object, descriptor_set);
ModelDisplayList::Item item;
item.descriptor_set = descriptor_set;
item.mesh = renderer_->GetMeshForShape(object.shape());
pipeline_spec_.mesh_spec = item.mesh->spec();
pipeline_spec_.shape_modifiers = object.shape().modifiers();
pipeline_spec_.is_clippee = clip_depth_ > 0;
pipeline_spec_.clipper_state =
ModelPipelineSpec::ClipperState::kBeginClipChildren;
if (object.material()) {
pipeline_spec_.has_material = true;
pipeline_spec_.is_opaque = object.material()->opaque();
} else {
pipeline_spec_.has_material = false;
pipeline_spec_.is_opaque = false;
}
item.pipeline = pipeline_cache_->GetPipeline(pipeline_spec_);
item.stencil_reference = clip_depth_;
items_.push_back(item);
}
void ModelDisplayListBuilder::AddClipperAndClippeeObjects(
const Object& object) {
const bool is_clippee = clip_depth_ > 0;
// Remember the beginning and end of clipper-items, so that we can later
// undo their effects upon the stencil buffer.
size_t clipper_start_index = items_.size();
// Drawing clippers will increment the values in the stencil buffer. Update
// |clip_depth_| so that children can test against the correct value.
AddClipperObject(object);
for (auto& clipper : object.clippers()) {
FXL_DCHECK(clipper.clippers().empty());
FXL_DCHECK(clipper.clippees().empty());
AddClipperObject(clipper);
}
// Remember the beginning and end of clipper-items, so that we can later
// undo their effects upon the stencil buffer.
size_t clipper_end_index = items_.size();
++clip_depth_;
// Recursively draw clipped children, z-sorting the semitransparent ones.
// TODO(rosswang): See TODOs in |ModelRenderer|.
const std::vector<Object>& clippees = object.clippees();
std::vector<uint32_t> alpha_children;
for (size_t i = 0; i < clippees.size(); i++) {
const Object& o = clippees[i];
if (!o.material() || o.material()->opaque()) {
AddObject(o);
} else {
alpha_children.push_back(i);
}
}
ZSort(&alpha_children, clippees, view_transform_ * projection_transform_);
for (uint32_t i : alpha_children) {
AddObject(clippees[i]);
}
// Revert the stencil buffer to the previous state.
// TODO: if we knew that no subsequent children were to be clipped, we
// could avoid this.
for (size_t index = clipper_start_index; index < clipper_end_index; ++index) {
ModelDisplayList::Item item = items_[index];
pipeline_spec_.mesh_spec = item.mesh->spec();
pipeline_spec_.shape_modifiers = ShapeModifiers();
pipeline_spec_.is_clippee = is_clippee;
pipeline_spec_.clipper_state =
ModelPipelineSpec::ClipperState::kEndClipChildren;
// Even if the object has a material, we already drew it the first time;
// now we just need to clear the stencil buffer.
pipeline_spec_.has_material = false;
pipeline_spec_.is_opaque = false;
pipeline_spec_.disable_depth_test = disable_depth_test_;
item.pipeline = pipeline_cache_->GetPipeline(pipeline_spec_);
item.stencil_reference = clip_depth_;
items_.push_back(std::move(item));
}
--clip_depth_;
}
void ModelDisplayListBuilder::AddNonClipperObject(const Object& object) {
FXL_DCHECK(object.clippees().empty());
if (object.material()) {
// Simply push the item.
PrepareUniformBufferForWriteOfSize(sizeof(ModelData::PerObject),
kMinUniformBufferOffsetAlignment);
vk::DescriptorSet descriptor_set = ObtainPerObjectDescriptorSet();
UpdateDescriptorSetForObject(object, descriptor_set);
ModelDisplayList::Item item;
item.descriptor_set = descriptor_set;
item.mesh = renderer_->GetMeshForShape(object.shape());
pipeline_spec_.mesh_spec = item.mesh->spec();
pipeline_spec_.shape_modifiers = object.shape().modifiers();
pipeline_spec_.is_clippee = clip_depth_ > 0;
pipeline_spec_.clipper_state =
ModelPipelineSpec::ClipperState::kNoClipChildren;
pipeline_spec_.has_material = true;
pipeline_spec_.is_opaque = object.material()->opaque();
pipeline_spec_.disable_depth_test = disable_depth_test_;
item.pipeline = pipeline_cache_->GetPipeline(pipeline_spec_);
item.stencil_reference = clip_depth_;
items_.push_back(std::move(item));
}
}
void ModelDisplayListBuilder::AddObject(const Object& object) {
const bool has_clippees = !object.clippees().empty();
if (has_clippees) {
AddClipperAndClippeeObjects(object);
} else {
// Some of these may need to be drawn (i.e. if they have both shape and
// material), even though there are no clippees to clip. In this case,
// draw them without updating the stencil buffer.
AddNonClipperObject(object);
for (auto& clipper : object.clippers()) {
AddNonClipperObject(clipper);
}
}
}
void ModelDisplayListBuilder::UpdateDescriptorSetForObject(
const Object& object, vk::DescriptorSet descriptor_set) {
auto per_object = reinterpret_cast<ModelData::PerObject*>(
&(uniform_buffer_->host_ptr()[uniform_buffer_write_index_]));
*per_object = ModelData::PerObject(); // initialize with default values
auto& mat = object.material();
// Push uniforms for scale/translation and color.
per_object->model_transform = object.transform();
per_object->shadow_transform = shadow_matrix_ * object.transform();
per_object->color =
mat ? mat->color() : vec4(1, 1, 1, 1); // opaque by default
// Find the texture to use, either the object's material's texture, or
// the default texture if the material doesn't have one.
vk::ImageView image_view;
vk::Sampler sampler;
if (auto& texture = mat ? mat->texture() : nullptr) {
if (!use_material_textures_) {
// The object's material has a texture, but we choose not to use it.
image_view = white_texture_->vk_image_view();
sampler = white_texture_->vk_sampler();
} else {
image_view = object.material()->vk_image_view();
sampler = object.material()->vk_sampler();
textures_.push_back(texture);
}
} else {
// No texture available. Use white texture, so that object's color shows.
image_view = white_texture_->vk_image_view();
sampler = white_texture_->vk_sampler();
}
// TODO: Remove when WobbleModifierAbsorber is stable.
if (object.shape().modifiers() & ShapeModifier::kWobble) {
auto wobble = object.shape_modifier_data<ModifierWobble>();
per_object->wobble = wobble ? *wobble : ModifierWobble();
}
// Update each descriptor in the PerObject descriptor set.
{
// A pair of writes; order doesn't matter.
vk::WriteDescriptorSet writes[ModelData::PerObject::kDescriptorCount];
auto& buffer_write = writes[0];
buffer_write.dstSet = descriptor_set;
buffer_write.dstBinding =
ModelData::PerObject::kDescriptorSetUniformBinding;
buffer_write.dstArrayElement = 0;
buffer_write.descriptorCount = 1;
buffer_write.descriptorType = vk::DescriptorType::eUniformBuffer;
vk::DescriptorBufferInfo buffer_info;
buffer_info.buffer = uniform_buffer_->vk();
buffer_info.range = sizeof(ModelData::PerObject);
buffer_info.offset = uniform_buffer_write_index_;
buffer_write.pBufferInfo = &buffer_info;
auto& image_write = writes[1];
image_write.dstSet = descriptor_set;
image_write.dstBinding = ModelData::PerObject::kDescriptorSetSamplerBinding;
image_write.dstArrayElement = 0;
image_write.descriptorCount = 1;
image_write.descriptorType = vk::DescriptorType::eCombinedImageSampler;
vk::DescriptorImageInfo image_info;
image_info.imageLayout = vk::ImageLayout::eShaderReadOnlyOptimal;
image_info.imageView = image_view;
image_info.sampler = sampler;
image_write.pImageInfo = &image_info;
device_.updateDescriptorSets(2, writes, 0, nullptr);
}
uniform_buffer_write_index_ += sizeof(ModelData::PerObject);
}
ModelDisplayListPtr ModelDisplayListBuilder::Build(
CommandBuffer* command_buffer) {
for (auto& uniform_buffer : uniform_buffers_) {
vk::BufferMemoryBarrier barrier;
barrier.srcAccessMask = vk::AccessFlagBits::eHostWrite;
barrier.dstAccessMask = vk::AccessFlagBits::eUniformRead;
barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
barrier.buffer = uniform_buffer->vk();
barrier.offset = 0;
barrier.size = uniform_buffer->size();
command_buffer->vk().pipelineBarrier(
vk::PipelineStageFlagBits::eHost,
vk::PipelineStageFlagBits::eVertexShader |
vk::PipelineStageFlagBits::eFragmentShader,
vk::DependencyFlags(), 0, nullptr, 1, &barrier, 0, nullptr);
// The display list still needs to retain the buffer, and since it no
// longer needs special treatment, add it in with the other resources.
//
// NOTE: see comment in ModelData constructor... at some point there will
// probably be an Escher-wide UniformBufferPool that will use a per-Frame
// resource management strategy. ModelDisplayListBuilder will not be around
// for long enough to make updating it worthwhile.
resources_.push_back(std::move(uniform_buffer));
}
uniform_buffers_.clear();
auto display_list = fxl::MakeRefCounted<ModelDisplayList>(
renderer_->resource_recycler(), per_model_descriptor_set_,
std::move(items_), std::move(textures_), std::move(resources_));
command_buffer->KeepAlive(display_list);
return display_list;
}
vk::DescriptorSet ModelDisplayListBuilder::ObtainPerObjectDescriptorSet() {
if (!per_object_descriptor_set_allocation_ ||
per_object_descriptor_set_index_ >=
per_object_descriptor_set_allocation_->size()) {
per_object_descriptor_set_index_ = 0;
constexpr uint32_t kReasonableDescriptorSetAllocationCount = 100;
per_object_descriptor_set_allocation_ =
per_object_descriptor_set_pool_->Allocate(
kReasonableDescriptorSetAllocationCount, nullptr);
resources_.push_back(per_object_descriptor_set_allocation_);
}
vk::DescriptorSet ds = per_object_descriptor_set_allocation_->get(
per_object_descriptor_set_index_);
++per_object_descriptor_set_index_;
return ds;
}
void ModelDisplayListBuilder::PrepareUniformBufferForWriteOfSize(
size_t size, size_t alignment) {
uniform_buffer_write_index_ =
AlignedToNext(uniform_buffer_write_index_, alignment);
if (!uniform_buffer_ ||
uniform_buffer_write_index_ + size > uniform_buffer_->size()) {
uniform_buffer_ = uniform_buffer_pool_->Allocate();
uniform_buffer_write_index_ = 0;
uniform_buffers_.push_back(uniform_buffer_);
}
}
} // namespace impl
} // namespace escher