blob: a405934a371f230ca5a695a94ec61a4271230a54 [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/model_renderer.h"
#include <glm/gtx/transform.hpp>
#include "lib/escher/geometry/tessellation.h"
#include "lib/escher/impl/command_buffer.h"
#include "lib/escher/impl/image_cache.h"
#include "lib/escher/impl/mesh_manager.h"
#include "lib/escher/impl/model_data.h"
#include "lib/escher/impl/model_display_list.h"
#include "lib/escher/impl/model_display_list_builder.h"
#include "lib/escher/impl/model_pipeline.h"
#include "lib/escher/impl/model_render_pass.h"
#include "lib/escher/impl/vulkan_utils.h"
#include "lib/escher/impl/z_sort.h"
#include "lib/escher/scene/camera.h"
#include "lib/escher/scene/model.h"
#include "lib/escher/scene/shape.h"
#include "lib/escher/scene/stage.h"
#include "lib/escher/util/hash_map.h"
#include "lib/escher/util/image_utils.h"
#include "lib/escher/util/trace_macros.h"
#include "lib/escher/vk/image.h"
namespace escher {
namespace impl {
ModelRendererPtr ModelRenderer::New(EscherWeakPtr weak_escher,
ModelDataPtr model_data) {
return fxl::AdoptRef(
new ModelRenderer(std::move(weak_escher), std::move(model_data)));
}
ModelRenderer::ModelRenderer(EscherWeakPtr weak_escher, ModelDataPtr model_data)
: escher_(std::move(weak_escher)),
device_(escher_->vk_device()),
resource_recycler_(escher_->resource_recycler()),
model_data_(std::move(model_data)) {
rectangle_ = CreateRectangle();
circle_ = CreateCircle();
white_texture_ = CreateWhiteTexture(escher_.get());
}
ModelRenderer::~ModelRenderer() {}
ModelDisplayListPtr ModelRenderer::CreateDisplayList(
const Stage& stage, const Model& model, const Camera& camera,
const ModelRenderPassPtr& render_pass, ModelDisplayListFlags flags,
float scale, const TexturePtr& shadow_texture, const mat4& shadow_matrix,
vec3 ambient_light_color, vec3 direct_light_color,
CommandBuffer* command_buffer) {
TRACE_DURATION("gfx", "escher::ModelRenderer::CreateDisplayList",
"object_count", model.objects().size());
const std::vector<Object>& objects = model.objects();
// TODO(ES-29): not low-hanging fruit, but maybe someday...
FXL_DCHECK(
!(flags & ModelDisplayListFlag::kShareDescriptorSetsBetweenObjects))
<< "unimplemented (ES-29).";
opaque_objects_.clear();
// Beware that this function only handles top-level objects. Clippees are
// handled by |ModelDisplayListBuilder|.
alpha_objects_.clear();
// TODO: We should sort according to more different metrics, and look for
// performance differences between them. At the same time, we should
// experiment with strategies for updating/binding descriptor-sets.
const bool sort_by_pipeline(flags & ModelDisplayListFlag::kSortByPipeline);
if (!sort_by_pipeline) {
// Simply render objects in the order that they appear in the model.
for (uint32_t i = 0; i < objects.size(); ++i) {
const escher::MaterialPtr& material = objects[i].material();
if (!material || material->opaque()) {
opaque_objects_.push_back(i);
} else {
alpha_objects_.push_back(i);
}
}
} else {
TRACE_DURATION("gfx", "escher::ModelRenderer::CreateDisplayList[sort]");
// Sort all objects into bins. Then, iterate over each bin in arbitrary
// order, without additional sorting within the bin.
HashMap<ModelPipelineSpec, std::vector<size_t>> pipeline_bins;
for (size_t i = 0; i < objects.size(); ++i) {
auto& obj = objects[i];
if (obj.shape().type() == Shape::Type::kNone) {
// The Object is a clip-group; immediately add this to list of opaque
// objects without binning.
opaque_objects_.push_back(i);
} else if (obj.material() && !obj.material()->opaque()) {
alpha_objects_.push_back(i);
} else {
ModelPipelineSpec spec;
spec.mesh_spec = GetMeshForShape(obj.shape())->spec();
spec.shape_modifiers = obj.shape().modifiers();
pipeline_bins[spec].push_back(i);
}
}
for (auto& pair : pipeline_bins) {
for (uint32_t object_index : pair.second) {
opaque_objects_.push_back(object_index);
}
}
}
FXL_DCHECK(opaque_objects_.size() + alpha_objects_.size() == objects.size());
ZSort(&alpha_objects_, objects, camera);
TRACE_DURATION("gfx", "escher::ModelRenderer::CreateDisplayList[build]");
ModelDisplayListBuilder builder(device_, stage, model, camera, scale,
white_texture_, shadow_texture, shadow_matrix,
ambient_light_color, direct_light_color,
model_data_.get(), this, render_pass, flags);
for (uint32_t object_index : opaque_objects_) {
builder.AddObject(objects[object_index]);
}
for (uint32_t object_index : alpha_objects_) {
builder.AddObject(objects[object_index]);
}
return builder.Build(command_buffer);
}
// TODO: stage shouldn't be necessary.
void ModelRenderer::Draw(const Stage& stage,
const ModelDisplayListPtr& display_list,
CommandBuffer* command_buffer,
const Camera::Viewport& viewport) {
TRACE_DURATION("gfx", "escher::ModelRenderer::Draw");
vk::CommandBuffer vk_command_buffer = command_buffer->vk();
for (const TexturePtr& texture : display_list->textures()) {
// TODO(ES-104): it would be nice if Resource::TakeWaitSemaphore() were
// virtual so that we could say texture->TakeWaitSemaphore(), instead of
// needing to know that the image is really the thing that we might need to
// wait for. Another approach would be for the Texture constructor to say
// SetWaitSemaphore(image->TakeWaitSemaphore()), but this isn't a
// bulletproof solution... what if someone else made a Texture with the
// same image, and used that one first. Of course, in general we want
// lighter-weight synchronization such as events or barriers... need to
// revisit this whole topic.
command_buffer->AddWaitSemaphore(
texture->image()->TakeWaitSemaphore(),
vk::PipelineStageFlagBits::eFragmentShader);
}
vk::Viewport vk_viewport;
vk_viewport.x = stage.viewing_volume().width() * viewport.x;
vk_viewport.y = stage.viewing_volume().height() * viewport.y;
vk_viewport.width = stage.viewing_volume().width() * viewport.width;
vk_viewport.height = stage.viewing_volume().height() * viewport.height;
// We normalize all depths to the range [0,1]. If we didn't, then Vulkan
// would clip them anyway. NOTE: this is only true because we are using an
// orthonormal projection; otherwise the depth computed by the vertex shader
// could be outside [0,1] as long as the perspective division brought it back.
// In this case, it might make sense to use different values for viewport
// min/max depth.
vk_viewport.minDepth = 0.f;
vk_viewport.maxDepth = 1.f;
vk_command_buffer.setViewport(0, 1, &vk_viewport);
// Retain all display-list resources until the frame is finished rendering.
command_buffer->KeepAlive(display_list);
vk::Pipeline current_pipeline;
vk::PipelineLayout current_pipeline_layout;
uint32_t current_stencil_reference = 0;
for (const ModelDisplayList::Item& item : display_list->items()) {
// Bind new pipeline and PerModel descriptor set, if necessary.
if (current_pipeline != item.pipeline->pipeline()) {
current_pipeline = item.pipeline->pipeline();
vk_command_buffer.bindPipeline(vk::PipelineBindPoint::eGraphics,
current_pipeline);
// According to my reading of the Vulkan spec, the "valid usage"
// requirements for vkCmdSetStencilReference() imply that it must be
// called after binding a new pipeline:
// "The currently bound graphics pipeline MUST have been created with
// the VK_DYNAMIC_STATE_STENCIL_REFERENCE dynamic state enabled".
// ... this implies that it will not simply be ignored if the pipeline
// doesn't have dynamic state (i.e. it can have bad effects, which we
// verified by experiment), which implies that the reference state is
// stored into memory associated with the pipeline, which implies that
// we must set it when binding a new pipeline.
if (item.pipeline->HasDynamicStencilState()) {
current_stencil_reference = item.stencil_reference;
vk_command_buffer.setStencilReference(vk::StencilFaceFlagBits::eFront,
current_stencil_reference);
}
// Whenever the pipeline changes, it is possible that the pipeline layout
// must also change.
if (current_pipeline_layout != item.pipeline->pipeline_layout()) {
current_pipeline_layout = item.pipeline->pipeline_layout();
vk::DescriptorSet ds = display_list->stage_data();
vk_command_buffer.bindDescriptorSets(
vk::PipelineBindPoint::eGraphics, current_pipeline_layout,
ModelData::PerModel::kDescriptorSetIndex, 1, &ds, 0, nullptr);
}
}
if (item.pipeline->HasDynamicStencilState() &&
current_stencil_reference != item.stencil_reference) {
current_stencil_reference = item.stencil_reference;
vk_command_buffer.setStencilReference(vk::StencilFaceFlagBits::eFront,
current_stencil_reference);
}
vk::DescriptorSet ds = item.descriptor_set;
vk_command_buffer.bindDescriptorSets(
vk::PipelineBindPoint::eGraphics, current_pipeline_layout,
ModelData::PerObject::kDescriptorSetIndex, 1, &ds, 0, nullptr);
command_buffer->DrawMesh(item.mesh);
}
}
const MeshPtr& ModelRenderer::GetMeshForShape(const Shape& shape) const {
switch (shape.type()) {
case Shape::Type::kRect:
return rectangle_;
case Shape::Type::kCircle:
return circle_;
case Shape::Type::kMesh:
return shape.mesh();
case Shape::Type::kNone: {
FXL_DCHECK(false);
static const MeshPtr kNone;
return kNone;
}
}
}
MeshPtr ModelRenderer::CreateRectangle() {
return NewSimpleRectangleMesh(escher_->mesh_manager());
}
MeshPtr ModelRenderer::CreateCircle() {
MeshSpec spec{MeshAttribute::kPosition2D | MeshAttribute::kUV};
return NewCircleMesh(escher_->mesh_manager(), spec, 4, vec2(0, 0), 1);
}
TexturePtr ModelRenderer::CreateWhiteTexture(Escher* escher) {
uint8_t channels[4];
channels[0] = channels[1] = channels[2] = channels[3] = 255;
auto image = escher->NewRgbaImage(1, 1, channels);
return fxl::MakeRefCounted<Texture>(escher->resource_recycler(),
std::move(image), vk::Filter::eNearest);
}
} // namespace impl
} // namespace escher