blob: 98481faa88f75994ff0e8f8266da0b0cd5a63126 [file] [log] [blame]
// Copyright 2018 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 "garnet/lib/ui/gfx/engine/engine_renderer.h"
#include <trace/event.h>
#include "garnet/lib/ui/gfx/engine/frame_timings.h"
#include "garnet/lib/ui/gfx/resources/camera.h"
#include "garnet/lib/ui/gfx/resources/compositor/layer.h"
#include "garnet/lib/ui/gfx/resources/dump_visitor.h"
#include "garnet/lib/ui/gfx/resources/lights/ambient_light.h"
#include "garnet/lib/ui/gfx/resources/lights/directional_light.h"
#include "garnet/lib/ui/gfx/resources/lights/point_light.h"
#include "garnet/lib/ui/gfx/resources/renderers/renderer.h"
#include "garnet/lib/ui/gfx/resources/stereo_camera.h"
#include "lib/escher/hmd/pose_buffer_latching_shader.h"
#include "lib/escher/impl/image_cache.h"
#include "lib/escher/renderer/batch_gpu_uploader.h"
#include "lib/escher/renderer/paper_renderer.h"
#include "lib/escher/renderer/shadow_map.h"
#include "lib/escher/renderer/shadow_map_renderer.h"
#include "lib/escher/scene/model.h"
#include "lib/escher/scene/stage.h"
#include "lib/escher/vk/image.h"
// TODO(SCN-1113): Move this someplace. PoseBufferLatchingShader assumes this,
// but we can't put it there because it lives in a Zircon-ignorant part of
// Escher.
#include <type_traits>
static_assert(
std::is_same<zx_time_t, int64_t>::value,
"PoseBufferLatchingShader incorrectly assumes that zx_time_t is int64_t");
namespace scenic_impl {
namespace gfx {
EngineRenderer::EngineRenderer(escher::EscherWeakPtr weak_escher)
: escher_(std::move(weak_escher)),
paper_renderer_(escher::PaperRenderer::New(escher_)),
shadow_renderer_(
escher::ShadowMapRenderer::New(escher_, paper_renderer_->model_data(),
paper_renderer_->model_renderer())),
pose_buffer_latching_shader_(
std::make_unique<escher::hmd::PoseBufferLatchingShader>(escher_)) {
paper_renderer_->set_sort_by_pipeline(false);
}
EngineRenderer::~EngineRenderer() = default;
void EngineRenderer::RenderLayers(const escher::FramePtr& frame,
zx_time_t target_presentation_time,
const escher::ImagePtr& output_image,
const std::vector<Layer*>& layers) {
// TODO(SCN-1119): change this to say "EngineRenderer::RenderLayers".
TRACE_DURATION("gfx", "Compositor::DrawFrame");
FXL_CHECK(!layers.empty());
auto overlay_model =
DrawOverlaysToModel(layers, frame, target_presentation_time);
const auto& bottom_layer = layers[0];
DrawLayer(frame, target_presentation_time, bottom_layer, output_image,
overlay_model.get());
}
std::unique_ptr<escher::Model> EngineRenderer::DrawOverlaysToModel(
const std::vector<Layer*>& layers, const escher::FramePtr& frame,
zx_time_t target_presentation_time) {
TRACE_DURATION("gfx", "EngineRenderer::DrawOverlaysToModel");
if (layers.size() <= 1) {
return nullptr;
}
std::vector<escher::Object> layer_objects;
layer_objects.reserve(layers.size() - 1);
// Render each layer, except the bottom one. Create an escher::Object for
// each layer, which will be composited as part of rendering the final
// layer.
auto recycler = escher_->resource_recycler();
for (size_t i = 1; i < layers.size(); ++i) {
auto layer = layers[i];
auto texture = escher::Texture::New(
recycler, GetLayerFramebufferImage(layer->width(), layer->height()),
vk::Filter::eLinear);
DrawLayer(frame, target_presentation_time, layers[i], texture->image(),
nullptr);
// TODO(SCN-1093): it would be preferable to insert barriers instead of
// using semaphores.
auto semaphore = escher::Semaphore::New(escher_->vk_device());
frame->SubmitPartialFrame(semaphore);
texture->image()->SetWaitSemaphore(std::move(semaphore));
auto material = escher::Material::New(layer->color(), std::move(texture));
material->set_opaque(layer->opaque());
layer_objects.push_back(escher::Object::NewRect(
escher::Transform(layer->translation()), std::move(material)));
}
return std::make_unique<escher::Model>(std::move(layer_objects));
}
// Helper function for DrawLayer().
static void InitEscherStage(
escher::Stage* stage, const escher::ViewingVolume& viewing_volume,
const std::vector<AmbientLightPtr>& ambient_lights,
const std::vector<DirectionalLightPtr>& directional_lights) {
stage->set_viewing_volume(viewing_volume);
if (ambient_lights.empty()) {
constexpr float kIntensity = 0.3f;
FXL_LOG(WARNING) << "scenic::gfx::Compositor::InitEscherStage(): no "
"ambient light was provided. Using one with "
"intensity: "
<< kIntensity << ".";
stage->set_fill_light(escher::AmbientLight(kIntensity));
} else {
if (ambient_lights.size() > 1) {
FXL_LOG(WARNING)
<< "scenic::gfx::Compositor::InitEscherStage(): only a single "
"ambient light is supported, but "
<< ambient_lights.size() << " were provided. Using the first one.";
}
stage->set_fill_light(escher::AmbientLight(ambient_lights[0]->color()));
}
if (directional_lights.empty()) {
constexpr float kHeading = 1.5f * M_PI;
constexpr float kElevation = 1.5f * M_PI;
constexpr float kIntensity = 0.3f;
constexpr float kDispersion = 0.15f * M_PI;
FXL_LOG(WARNING) << "scenic::gfx::Compositor::InitEscherStage(): no "
"directional light was provided (heading: "
<< kHeading << ", elevation: " << kElevation
<< ", intensity: " << kIntensity << ").";
stage->set_key_light(
escher::DirectionalLight(escher::vec2(kHeading, kElevation),
kDispersion, escher::vec3(kIntensity)));
} else {
if (directional_lights.size() > 1) {
FXL_LOG(WARNING)
<< "scenic::gfx::Compositor::InitEscherStage(): only a single "
"directional light is supported, but "
<< directional_lights.size()
<< " were provided. Using the first one.";
}
auto& light = directional_lights[0];
stage->set_key_light(escher::DirectionalLight(
light->direction(), 0.15f * M_PI, light->color()));
}
}
void EngineRenderer::DrawLayer(const escher::FramePtr& frame,
zx_time_t target_presentation_time, Layer* layer,
const escher::ImagePtr& output_image,
const escher::Model* overlay_model) {
TRACE_DURATION("gfx", "EngineRenderer::DrawLayer");
FXL_DCHECK(layer->IsDrawable());
float stage_width = static_cast<float>(output_image->width());
float stage_height = static_cast<float>(output_image->height());
if (layer->size().x != stage_width || layer->size().y != stage_height) {
// TODO(SCN-248): Should be able to render into a viewport of the
// output image, but we're not that fancy yet.
layer->error_reporter()->ERROR()
<< "TODO(MZ-248): scenic::gfx::EngineRenderer::DrawLayer()"
": layer size of "
<< layer->size().x << "x" << layer->size().y
<< " does not match output image size of " << stage_width << "x"
<< stage_height;
return;
}
auto& renderer = layer->renderer();
auto& scene = renderer->camera()->scene();
escher::Stage stage;
InitEscherStage(&stage, layer->GetViewingVolume(), scene->ambient_lights(),
scene->directional_lights());
// The BatchGpuUploader is here to update the textures for Materials that are
// used in the scene, if and only if the backing image is dirty.
// TODO(SCN-1219) See if these updates can be consolidated with the batched
// uploads when Sessions are updated.
escher::BatchGpuUploader gpu_uploader(escher_, frame->frame_number());
escher::Model model(renderer->CreateDisplayList(
renderer->camera()->scene(), escher::vec2(layer->size()), &gpu_uploader));
gpu_uploader.Submit();
// Set the renderer's shadow mode, and generate a shadow map if necessary.
escher::ShadowMapPtr shadow_map;
switch (renderer->shadow_technique()) {
case ::fuchsia::ui::gfx::ShadowTechnique::UNSHADOWED:
paper_renderer_->set_shadow_type(escher::PaperRendererShadowType::kNone);
break;
case ::fuchsia::ui::gfx::ShadowTechnique::SCREEN_SPACE:
paper_renderer_->set_shadow_type(escher::PaperRendererShadowType::kSsdo);
break;
case ::fuchsia::ui::gfx::ShadowTechnique::MOMENT_SHADOW_MAP:
FXL_DLOG(WARNING) << "Moment shadow maps not implemented";
// Fallthrough to regular shadow maps.
case ::fuchsia::ui::gfx::ShadowTechnique::SHADOW_MAP:
paper_renderer_->set_shadow_type(
escher::PaperRendererShadowType::kShadowMap);
shadow_map = shadow_renderer_->GenerateDirectionalShadowMap(
frame, stage, model, stage.key_light().direction(),
stage.key_light().color());
break;
case ::fuchsia::ui::gfx::ShadowTechnique::STENCIL_SHADOW_VOLUME:
FXL_DLOG(WARNING) << "Stencil shadow volumes not implemented";
paper_renderer_->set_shadow_type(escher::PaperRendererShadowType::kSsdo);
break;
}
auto draw_frame_lambda = [this, paper_renderer{paper_renderer_.get()}, frame,
target_presentation_time, &stage, &model,
&output_image, &shadow_map,
&overlay_model](escher::Camera camera) {
if (camera.pose_buffer()) {
camera.SetLatchedPoseBuffer(pose_buffer_latching_shader_->LatchPose(
frame, camera, camera.pose_buffer(), target_presentation_time));
}
paper_renderer->DrawFrame(frame, stage, model, camera, output_image,
shadow_map, overlay_model);
};
if (renderer->camera()->IsKindOf<StereoCamera>()) {
auto stereo_camera = renderer->camera()->As<StereoCamera>();
for (const auto eye : {StereoCamera::Eye::LEFT, StereoCamera::Eye::RIGHT}) {
escher::Camera camera = stereo_camera->GetEscherCamera(eye);
draw_frame_lambda(camera);
}
} else {
escher::Camera camera =
renderer->camera()->GetEscherCamera(stage.viewing_volume());
draw_frame_lambda(camera);
}
}
escher::ImagePtr EngineRenderer::GetLayerFramebufferImage(uint32_t width,
uint32_t height) {
escher::ImageInfo info;
info.format = vk::Format::eB8G8R8A8Srgb;
info.width = width;
info.height = height;
info.usage = vk::ImageUsageFlagBits::eColorAttachment |
vk::ImageUsageFlagBits::eSampled;
return escher_->image_cache()->NewImage(info);
}
} // namespace gfx
} // namespace scenic_impl