blob: 15551b9f0bc3c3e5abf9795e08837ae4ded683ac [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 "src/ui/scenic/lib/gfx/engine/engine_renderer.h"
#include <lib/fostr/fidl/fuchsia/ui/gfx/formatting.h>
#include <trace/event.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/vk/image.h"
#include "src/ui/lib/escher/vk/image_layout_updater.h"
#include "src/ui/scenic/lib/gfx/engine/engine_renderer_visitor.h"
#include "src/ui/scenic/lib/gfx/resources/camera.h"
#include "src/ui/scenic/lib/gfx/resources/compositor/layer.h"
#include "src/ui/scenic/lib/gfx/resources/dump_visitor.h"
#include "src/ui/scenic/lib/gfx/resources/lights/ambient_light.h"
#include "src/ui/scenic/lib/gfx/resources/lights/directional_light.h"
#include "src/ui/scenic/lib/gfx/resources/lights/point_light.h"
#include "src/ui/scenic/lib/gfx/resources/renderers/renderer.h"
#include "src/ui/scenic/lib/gfx/resources/stereo_camera.h"
#include "src/ui/scenic/lib/scheduling/frame_timings.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, vk::Format depth_stencil_format)
: 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_)),
depth_stencil_format_(depth_stencil_format) {}
EngineRenderer::~EngineRenderer() = default;
void EngineRenderer::RenderLayers(const escher::FramePtr& frame, zx::time target_presentation_time,
const RenderTarget& render_target,
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");
// Check that we are working with a protected framebuffer.
FXL_DCHECK(render_target.output_image->use_protected_memory() == frame->use_protected_memory());
// 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);
for (size_t i = 1; i < layers.size(); ++i) {
auto layer = layers[i];
auto texture = escher::Texture::New(
escher_->resource_recycler(),
GetLayerFramebufferImage(layer->width(), layer->height(), frame->use_protected_memory()),
// 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, {.output_image = texture->image()}, {});
// TODO(SCN-1093): it would be preferable to insert barriers instead of
// using semaphores.
if (i == layers.size() - 1) {
// After rendering the "final" (non-bottom) layer, we wait for them all to complete before
// doing any more work.
auto overlay_semaphore = escher::Semaphore::New(escher_->vk_device());
frame->SubmitPartialFrame(overlay_semaphore);
frame->cmds()->AddWaitSemaphore(overlay_semaphore,
vk::PipelineStageFlagBits::eFragmentShader);
} else {
frame->SubmitPartialFrame(escher::SemaphorePtr());
}
auto material = escher::Material::New(layer->color(), std::move(texture));
material->set_type(layer->opaque() ? escher::Material::Type::kOpaque
: escher::Material::Type::kTranslucent);
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], render_target,
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 target_presentation_time,
Layer* layer, const RenderTarget& render_target,
const escher::Model& overlay_model) {
FXL_DCHECK(layer->IsDrawable());
float stage_width = static_cast<float>(render_target.output_image->width());
float stage_height = static_cast<float>(render_target.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.
FXL_LOG(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
<< "... not drawing.";
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, render_target,
overlay_model);
}
std::vector<escher::Camera> EngineRenderer::GenerateEscherCamerasForPaperRenderer(
const escher::FramePtr& frame, Camera* camera, escher::ViewingVolume viewing_volume,
zx::time 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.get());
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.get());
escher_camera.SetLatchedPoseBuffer(latched_pose_buffer, escher::CameraEye::kLeft);
}
return {escher_camera};
}
}
void EngineRenderer::DrawLayerWithPaperRenderer(const escher::FramePtr& frame,
zx::time target_presentation_time, Layer* layer,
const escher::PaperRendererShadowType shadow_type,
const RenderTarget& render_target,
const escher::Model& overlay_model) {
TRACE_DURATION("gfx", "EngineRenderer::DrawLayerWithPaperRenderer");
frame->cmds()->AddWaitSemaphore(render_target.output_image_acquire_semaphore,
vk::PipelineStageFlagBits::eColorAttachmentOutput);
frame->cmds()->impl()->TransitionImageLayout(render_target.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(),
#if SCENIC_DISPLAY_FRAME_NUMBER
.debug_frame_number = true,
#endif
.depth_stencil_format = depth_stencil_format_,
});
// 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(),
});
}
auto gpu_uploader = std::make_shared<escher::BatchGpuUploader>(escher_, frame->frame_number());
auto layout_updater = std::make_unique<escher::ImageLayoutUpdater>(escher_);
paper_renderer_->BeginFrame(
frame, gpu_uploader, paper_scene,
GenerateEscherCamerasForPaperRenderer(frame, camera, layer->GetViewingVolume(),
target_presentation_time),
render_target.output_image);
// TODO(SCN-1256): scene-visitation should generate cameras, collect
// lights, etc.
// Using resources allocated with protected memory on non-protected CommandBuffers is not allowed.
// In order to avoid breaking access rules, we should replace them with non-protected materials
// when using a non-protected |frame|.
const bool hide_protected_memory = !frame->use_protected_memory();
EngineRendererVisitor visitor(
paper_renderer_.get(), gpu_uploader.get(), layout_updater.get(), hide_protected_memory,
hide_protected_memory ? GetReplacementMaterial(gpu_uploader.get()) : nullptr);
visitor.Visit(camera->scene().get());
// TODO(SCN-1270): support for multiple layers.
FXL_DCHECK(overlay_model.objects().empty());
paper_renderer_->FinalizeFrame();
escher::SemaphorePtr escher_image_updater_semaphore = escher::SemaphorePtr();
if (gpu_uploader->NeedsCommandBuffer() || layout_updater->NeedsCommandBuffer()) {
auto updater_frame =
escher_->NewFrame("EngineRenderer uploads and image layout updates", frame->frame_number(),
/* enable_gpu_logging */ false, escher::CommandBuffer::Type::kTransfer,
/* use_protected_memory */ false);
escher_image_updater_semaphore = escher::Semaphore::New(escher_->vk_device());
// Note that only host images (except for directly-mapped images) will be
// uploaded to GPU by BatchGpuUploader; and only device images (and
// directly-mapped host images) will be initialized by ImageLayoutUpdater;
// so we can submit all the commands into one single command buffer without
// causing any problem.
gpu_uploader->GenerateCommands(updater_frame->cmds());
layout_updater->GenerateCommands(updater_frame->cmds());
updater_frame->EndFrame(escher_image_updater_semaphore, []() {});
}
paper_renderer_->EndFrame(std::move(escher_image_updater_semaphore));
}
escher::ImagePtr EngineRenderer::GetLayerFramebufferImage(uint32_t width, uint32_t height,
bool use_protected_memory) {
escher::ImageInfo info;
info.format = vk::Format::eB8G8R8A8Srgb;
info.width = width;
info.height = height;
info.usage = vk::ImageUsageFlagBits::eColorAttachment | vk::ImageUsageFlagBits::eSampled;
if (use_protected_memory) {
info.memory_flags = vk::MemoryPropertyFlagBits::eProtected;
}
return escher_->image_cache()->NewImage(info);
}
escher::MaterialPtr EngineRenderer::GetReplacementMaterial(escher::BatchGpuUploader* gpu_uploader) {
if (!replacement_material_) {
FXL_DCHECK(escher_);
// Fuchsia color.
uint8_t channels[4];
channels[0] = channels[2] = channels[3] = 255;
channels[1] = 0;
glm::vec4 color;
color.x = color.z = color.a = 255;
color.y = 0;
auto image = escher_->NewRgbaImage(gpu_uploader, 1, 1, channels);
replacement_material_ =
escher::Material::New(color, escher_->NewTexture(std::move(image), vk::Filter::eNearest));
}
return replacement_material_;
}
} // namespace gfx
} // namespace scenic_impl