blob: 4605b726cb8d24be3e7550f4a79466f64f380e34 [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 <lib/fostr/fidl/fuchsia/ui/gfx/formatting.h>
#include <trace/event.h>
#include "garnet/lib/ui/gfx/engine/engine_renderer_visitor.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 "src/ui/lib/escher/hmd/pose_buffer_latching_shader.h"
#include "src/ui/lib/escher/impl/image_cache.h"
#include "src/ui/lib/escher/paper/paper_scene.h"
#include "src/ui/lib/escher/renderer/batch_gpu_uploader.h"
#include "src/ui/lib/escher/scene/model.h"
#include "src/ui/lib/escher/scene/stage.h"
#include "src/ui/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)),
// We use two depth buffers so that we can render multiple Layers without
// introducing a GPU stall.
paper_renderer_(escher::PaperRenderer::New(
escher_, {.shadow_type = escher::PaperRendererShadowType::kNone,
.num_depth_buffers = 2})),
pose_buffer_latching_shader_(
std::make_unique<escher::hmd::PoseBufferLatchingShader>(escher_)) {
}
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) {
// NOTE: this name is important for benchmarking. Do not remove or modify it
// without also updating the "process_gfx_trace.go" script.
TRACE_DURATION("gfx", "EngineRenderer::RenderLayers");
// 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.
// TODO(SCN-1254): the efficiency of this GPU compositing could be
// improved on tile-based GPUs by generating each layer in a subpass and
// compositing it into |output_image| in another subpass.
std::vector<escher::Object> overlay_objects;
if (layers.size() > 1) {
overlay_objects.reserve(layers.size() - 1);
auto it = layers.begin();
while (++it != layers.end()) {
auto layer = *it;
auto texture = escher::Texture::New(
escher_->resource_recycler(),
GetLayerFramebufferImage(layer->width(), layer->height()),
// TODO(SCN-1270): shouldn't need linear filter, since this is
// 1-1 pixel mapping. Verify when re-enabling multi-layer support.
vk::Filter::eLinear);
DrawLayer(frame, target_presentation_time, layer, texture->image(), {});
// 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());
overlay_objects.push_back(escher::Object::NewRect(
escher::Transform(layer->translation()), std::move(material)));
}
}
// TODO(SCN-1270): add support for multiple layers.
if (layers.size() > 1) {
FXL_LOG(ERROR)
<< "EngineRenderer::RenderLayers(): only a single Layer is supported.";
overlay_objects.clear();
}
// Draw the bottom layer with all of the overlay layers above it.
DrawLayer(frame, target_presentation_time, layers[0], output_image,
escher::Model(std::move(overlay_objects)));
}
// Helper function for DrawLayer
static escher::PaperRendererShadowType GetPaperRendererShadowType(
fuchsia::ui::gfx::ShadowTechnique technique) {
using escher::PaperRendererShadowType;
using fuchsia::ui::gfx::ShadowTechnique;
switch (technique) {
case ShadowTechnique::UNSHADOWED:
return PaperRendererShadowType::kNone;
case ShadowTechnique::SCREEN_SPACE:
return PaperRendererShadowType::kSsdo;
case ShadowTechnique::SHADOW_MAP:
return PaperRendererShadowType::kShadowMap;
case ShadowTechnique::MOMENT_SHADOW_MAP:
return PaperRendererShadowType::kMomentShadowMap;
case ShadowTechnique::STENCIL_SHADOW_VOLUME:
return PaperRendererShadowType::kShadowVolume;
}
}
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) {
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(SCN-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;
}
// TODO(SCN-1273): add pixel tests for various shadow modes (particularly
// those implemented by PaperRenderer).
escher::PaperRendererShadowType shadow_type =
GetPaperRendererShadowType(layer->renderer()->shadow_technique());
switch (shadow_type) {
case escher::PaperRendererShadowType::kNone:
case escher::PaperRendererShadowType::kShadowVolume:
break;
default:
FXL_LOG(WARNING) << "EngineRenderer does not support "
<< layer->renderer()->shadow_technique()
<< "; using UNSHADOWED.";
shadow_type = escher::PaperRendererShadowType::kNone;
}
DrawLayerWithPaperRenderer(frame, target_presentation_time, layer,
shadow_type, output_image, overlay_model);
}
std::vector<escher::Camera>
EngineRenderer::GenerateEscherCamerasForPaperRenderer(
const escher::FramePtr& frame, Camera* camera,
escher::ViewingVolume viewing_volume, zx_time_t target_presentation_time) {
if (camera->IsKindOf<StereoCamera>()) {
auto stereo_camera = camera->As<StereoCamera>();
escher::Camera left_camera =
stereo_camera->GetEscherCamera(StereoCamera::Eye::LEFT);
escher::Camera right_camera =
stereo_camera->GetEscherCamera(StereoCamera::Eye::RIGHT);
escher::BufferPtr latched_pose_buffer;
if (escher::hmd::PoseBuffer pose_buffer = camera->GetEscherPoseBuffer()) {
latched_pose_buffer = pose_buffer_latching_shader_->LatchStereoPose(
frame, left_camera, right_camera, pose_buffer,
target_presentation_time);
left_camera.SetLatchedPoseBuffer(latched_pose_buffer,
escher::CameraEye::kLeft);
right_camera.SetLatchedPoseBuffer(latched_pose_buffer,
escher::CameraEye::kRight);
}
return {left_camera, right_camera};
} else {
escher::Camera escher_camera = camera->GetEscherCamera(viewing_volume);
escher::BufferPtr latched_pose_buffer;
if (escher::hmd::PoseBuffer pose_buffer = camera->GetEscherPoseBuffer()) {
latched_pose_buffer = pose_buffer_latching_shader_->LatchPose(
frame, escher_camera, pose_buffer, target_presentation_time);
escher_camera.SetLatchedPoseBuffer(latched_pose_buffer,
escher::CameraEye::kLeft);
}
return {escher_camera};
}
}
void EngineRenderer::DrawLayerWithPaperRenderer(
const escher::FramePtr& frame, zx_time_t target_presentation_time,
Layer* layer, const escher::PaperRendererShadowType shadow_type,
const escher::ImagePtr& output_image, const escher::Model& overlay_model) {
TRACE_DURATION("gfx", "EngineRenderer::DrawLayerWithPaperRenderer");
frame->command_buffer()->TransitionImageLayout(
output_image, vk::ImageLayout::eUndefined,
vk::ImageLayout::eColorAttachmentOptimal);
auto& renderer = layer->renderer();
auto camera = renderer->camera();
auto& scene = camera->scene();
paper_renderer_->SetConfig(escher::PaperRendererConfig{
.shadow_type = shadow_type,
.debug = renderer->enable_debugging(),
});
// Set up PaperScene from Scenic Scene resource.
auto paper_scene = fxl::MakeRefCounted<escher::PaperScene>();
paper_scene->bounding_box = layer->GetViewingVolume().bounding_box();
// Set up ambient light.
if (scene->ambient_lights().empty()) {
FXL_LOG(WARNING)
<< "scenic_impl::gfx::EngineRenderer: scene has no ambient light.";
paper_scene->ambient_light.color = escher::vec3(0, 0, 0);
} else {
paper_scene->ambient_light.color = scene->ambient_lights()[0]->color();
}
// Set up point lights.
paper_scene->point_lights.reserve(scene->point_lights().size());
for (auto& light : scene->point_lights()) {
paper_scene->point_lights.push_back(escher::PaperPointLight{
.position = light->position(),
.color = light->color(),
.falloff = light->falloff(),
});
}
paper_renderer_->BeginFrame(
frame, paper_scene,
GenerateEscherCamerasForPaperRenderer(
frame, camera, layer->GetViewingVolume(), target_presentation_time),
output_image);
// TODO(SCN-1256): scene-visitation should generate cameras, collect
// lights, etc.
escher::BatchGpuUploader gpu_uploader(escher_, frame->frame_number());
EngineRendererVisitor visitor(paper_renderer_.get(), &gpu_uploader);
visitor.Visit(camera->scene().get());
gpu_uploader.Submit();
// TODO(SCN-1270): support for multiple layers.
FXL_DCHECK(overlay_model.objects().empty());
paper_renderer_->EndFrame();
}
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