| // 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 |