blob: dbadd1d34fe2f996a697219afa81fdc6baa4215f [file] [log] [blame]
// Copyright 2017 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.h"
#include <fbl/string.h>
#include <fs/pseudo-dir.h>
#include <set>
#include <lib/async/cpp/task.h>
#include <lib/async/default.h>
#include <trace/event.h>
#include <zx/time.h>
#include "garnet/lib/ui/gfx/engine/engine_renderer.h"
#include "garnet/lib/ui/gfx/engine/frame_scheduler.h"
#include "garnet/lib/ui/gfx/engine/frame_timings.h"
#include "garnet/lib/ui/gfx/engine/hardware_layer_assignment.h"
#include "garnet/lib/ui/gfx/engine/session.h"
#include "garnet/lib/ui/gfx/engine/session_handler.h"
#include "garnet/lib/ui/gfx/resources/compositor/compositor.h"
#include "garnet/lib/ui/gfx/resources/dump_visitor.h"
#include "garnet/lib/ui/gfx/resources/nodes/traversal.h"
#include "garnet/lib/ui/gfx/swapchain/display_swapchain.h"
#include "garnet/lib/ui/gfx/swapchain/vulkan_display_swapchain.h"
#include "garnet/lib/ui/scenic/session.h"
#include "lib/escher/impl/vulkan_utils.h"
#include "lib/escher/renderer/batch_gpu_uploader.h"
namespace scenic_impl {
namespace gfx {
// Determine a plausible memory type index for importing memory from VMOs.
static uint32_t GetImportedMemoryTypeIndex(vk::PhysicalDevice physical_device,
vk::Device device) {
// Is there a better way to get the memory type bits than creating and
// immediately destroying a buffer?
// TODO(SCN-79): Use sysmem for this when it's available.
constexpr vk::DeviceSize kUnimportantBufferSize = 30000;
vk::BufferCreateInfo buffer_create_info;
buffer_create_info.size = kUnimportantBufferSize;
buffer_create_info.usage = vk::BufferUsageFlagBits::eTransferSrc |
vk::BufferUsageFlagBits::eTransferDst |
vk::BufferUsageFlagBits::eStorageTexelBuffer |
vk::BufferUsageFlagBits::eStorageBuffer |
vk::BufferUsageFlagBits::eIndexBuffer |
vk::BufferUsageFlagBits::eVertexBuffer;
buffer_create_info.sharingMode = vk::SharingMode::eExclusive;
auto vk_buffer =
escher::ESCHER_CHECKED_VK_RESULT(device.createBuffer(buffer_create_info));
vk::MemoryRequirements reqs = device.getBufferMemoryRequirements(vk_buffer);
device.destroyBuffer(vk_buffer);
// TODO(SCN-998): Decide how to determine if we're on an UMA platform
// or not.
return escher::impl::GetMemoryTypeIndex(
physical_device, reqs.memoryTypeBits,
vk::MemoryPropertyFlagBits::eDeviceLocal |
vk::MemoryPropertyFlagBits::eHostVisible);
}
CommandContext::CommandContext(escher::BatchGpuUploaderPtr uploader)
: batch_gpu_uploader_(std::move(uploader)) {}
void CommandContext::Flush() {
if (batch_gpu_uploader_) {
// Submit regardless of whether or not there are updates to release the
// underlying CommandBuffer so the pool and sequencer don't stall out.
// TODO(ES-115) to remove this restriction.
batch_gpu_uploader_->Submit(escher::SemaphorePtr());
}
}
Engine::Engine(DisplayManager* display_manager,
escher::EscherWeakPtr weak_escher)
: display_manager_(display_manager),
escher_(std::move(weak_escher)),
engine_renderer_(std::make_unique<EngineRenderer>(escher_)),
image_factory_(std::make_unique<escher::SimpleImageFactory>(
escher()->resource_recycler(), escher()->gpu_allocator())),
rounded_rect_factory_(
std::make_unique<escher::RoundedRectFactory>(escher_)),
release_fence_signaller_(std::make_unique<escher::ReleaseFenceSignaller>(
escher()->command_buffer_sequencer())),
session_manager_(std::make_unique<SessionManager>()),
imported_memory_type_index_(GetImportedMemoryTypeIndex(
escher()->vk_physical_device(), escher()->vk_device())),
has_vulkan_(escher_ && escher_->vk_device()),
weak_factory_(this) {
FXL_DCHECK(display_manager_);
FXL_DCHECK(escher_);
InitializeFrameScheduler();
}
Engine::Engine(
DisplayManager* display_manager,
std::unique_ptr<escher::ReleaseFenceSignaller> release_fence_signaller,
std::unique_ptr<SessionManager> session_manager,
escher::EscherWeakPtr weak_escher)
: display_manager_(display_manager),
escher_(std::move(weak_escher)),
release_fence_signaller_(std::move(release_fence_signaller)),
session_manager_(std::move(session_manager)),
imported_memory_type_index_(
escher_ ? GetImportedMemoryTypeIndex(escher_->vk_physical_device(),
escher_->vk_device())
: 0),
has_vulkan_(escher_ && escher_->vk_device()),
weak_factory_(this) {
FXL_DCHECK(display_manager_);
InitializeFrameScheduler();
}
Engine::~Engine() = default;
void Engine::InitializeFrameScheduler() {
// TODO(SCN-1092): make |frame_scheduler_| non-nullable. For testing, this
// might entail plugging in a dummy Display. Relates to SCN-452.
if (display_manager_->default_display()) {
frame_scheduler_ =
std::make_unique<FrameScheduler>(display_manager_->default_display());
frame_scheduler_->set_delegate(this);
}
}
void Engine::ScheduleUpdate(uint64_t presentation_time) {
// TODO(SCN-1092): make |frame_scheduler_| non-nullable. This is feasible now
// that we can use TestLoopFixture::RunLoopFor() to cause the scheduler to
// render.
if (frame_scheduler_) {
frame_scheduler_->RequestFrame(presentation_time);
} else {
// Apply update immediately. This is done for tests.
FXL_LOG(WARNING)
<< "No FrameScheduler available; applying update immediately";
RenderFrame(FrameTimingsPtr(), presentation_time, 0, false);
}
}
std::unique_ptr<Swapchain> Engine::CreateDisplaySwapchain(Display* display) {
FXL_DCHECK(!display->is_claimed());
#if SCENIC_VULKAN_SWAPCHAIN
return std::make_unique<VulkanDisplaySwapchain>(display, event_timestamper(),
escher());
#else
return std::make_unique<DisplaySwapchain>(display_manager_, display,
event_timestamper(), escher());
#endif
}
void Engine::InitializeCommandContext(uint64_t frame_number_for_tracing) {
FXL_DCHECK(!command_context_) << "CommandContext already exists.";
command_context_ = std::make_unique<CommandContext>(
has_vulkan()
? escher::BatchGpuUploader::New(escher_, frame_number_for_tracing)
: escher::BatchGpuUploaderPtr());
}
void Engine::FlushAndInvalidateCommandContext() {
FXL_DCHECK(command_context_) << "CommandContext does not exist.";
command_context_->Flush();
command_context_ = nullptr;
}
bool Engine::UpdateSessions(uint64_t presentation_time,
uint64_t presentation_interval,
uint64_t frame_number_for_tracing) {
InitializeCommandContext(frame_number_for_tracing);
bool any_updates_were_applied =
session_manager_->ApplyScheduledSessionUpdates(presentation_time,
presentation_interval);
FlushAndInvalidateCommandContext();
return any_updates_were_applied;
}
// Helper for RenderFrame(). Generate a mapping between a Compositor's Layer
// resources and the hardware layers they should be displayed on.
// TODO(SCN-1088): there should be a separate mechanism that is responsible
// for inspecting the compositor's resource tree and optimizing the assignment
// of rendered content to hardware display layers.
std::optional<HardwareLayerAssignment> GetHardwareLayerAssignment(
const Compositor& compositor) {
// TODO(SCN-1098): this is a placeholder; currently only a single hardware
// layer is supported, and we don't know its ID (it is hidden within the
// DisplayManager implementation), so we just say 0.
std::vector<Layer*> layers = compositor.GetDrawableLayers();
if (layers.empty() || !compositor.swapchain()) {
return {};
}
return {
{.items = {{
.hardware_layer_id = 0,
.layers = std::move(layers),
}},
.swapchain = compositor.swapchain()},
};
}
bool Engine::RenderFrame(const FrameTimingsPtr& timings,
uint64_t presentation_time,
uint64_t presentation_interval, bool force_render) {
TRACE_DURATION("gfx", "RenderFrame", "frame_number", timings->frame_number(),
"time", presentation_time, "interval", presentation_interval);
// TODO(SCN-1092): make |timings| non-nullable, and unconditionally use
// timings->frame_number() below. When this is done, uncomment the following
// line:
// FXL_DCHECK(timings);
// TODO(SCN-1108): consider applying updates as each fence is signalled.
if (!UpdateSessions(presentation_time, presentation_interval,
timings ? timings->frame_number() : 0) &&
!force_render) {
return false;
}
UpdateAndDeliverMetrics(presentation_time);
// Some updates were applied; we interpret this to mean that the scene may
// have changed, and therefore needs to be rendered.
// TODO(SCN-1091): this is a very conservative approach that may result in
// excessive rendering.
// TODO(SCN-1089): the FrameTimings are passed to the Compositor's swapchain
// to notify when the frame is finished rendering, presented, dropped, etc.
// This doesn't make any sense if there are multiple compositors.
FXL_DCHECK(compositors_.size() <= 1);
std::vector<HardwareLayerAssignment> hlas;
for (auto& compositor : compositors_) {
if (auto hla = GetHardwareLayerAssignment(*compositor)) {
hlas.push_back(std::move(hla.value()));
// Verbose logging of the entire Compositor resource tree.
if (FXL_VLOG_IS_ON(3)) {
std::ostringstream output;
DumpVisitor visitor(output);
compositor->Accept(&visitor);
FXL_VLOG(3) << "Compositor dump\n" << output.str();
}
} else {
// Nothing to be drawn; either the Compositor has no layers to draw or
// it has no valid Swapchain. The latter will be true if Escher/Vulkan
// is unavailable for whatever reason.
}
}
if (hlas.empty()) {
// No compositor has any renderable content.
return false;
}
escher::FramePtr frame =
escher()->NewFrame("Scenic Compositor", timings->frame_number());
bool success = true;
for (size_t i = 0; i < hlas.size(); ++i) {
const bool is_last_hla = (i == hlas.size() - 1);
HardwareLayerAssignment& hla = hlas[i];
success &= hla.swapchain->DrawAndPresentFrame(
timings, hla,
[is_last_hla, &frame, engine_renderer{engine_renderer_.get()}](
zx_time_t target_presentation_time,
const escher::ImagePtr& output_image,
const HardwareLayerAssignment::Item hla_item,
const escher::SemaphorePtr& acquire_semaphore,
const escher::SemaphorePtr& frame_done_semaphore) {
output_image->SetWaitSemaphore(acquire_semaphore);
engine_renderer->RenderLayers(frame, target_presentation_time,
output_image, hla_item.layers);
if (!is_last_hla) {
frame->SubmitPartialFrame(frame_done_semaphore);
} else {
frame->EndFrame(frame_done_semaphore, nullptr);
}
});
}
if (!success) {
// TODO(SCN-1089): what is the proper behavior when some swapchains
// are displayed and others aren't? This isn't currently an issue because
// there is only one Compositor; see above.
FXL_DCHECK(hlas.size() == 1);
return false;
}
CleanupEscher();
return true;
}
void Engine::AddCompositor(Compositor* compositor) {
FXL_DCHECK(compositor);
FXL_DCHECK(compositor->session()->engine() == this);
bool success = compositors_.insert(compositor).second;
FXL_DCHECK(success);
}
void Engine::RemoveCompositor(Compositor* compositor) {
FXL_DCHECK(compositor);
FXL_DCHECK(compositor->session()->engine() == this);
size_t count = compositors_.erase(compositor);
FXL_DCHECK(count == 1);
}
Compositor* Engine::GetFirstCompositor() const {
FXL_DCHECK(!compositors_.empty());
return compositors_.empty() ? nullptr : *compositors_.begin();
}
Compositor* Engine::GetCompositor(scenic_impl::GlobalId compositor_id) const {
for (Compositor* compositor : compositors_) {
if (compositor->global_id() == compositor_id) {
return compositor;
}
}
return nullptr;
}
void Engine::UpdateAndDeliverMetrics(uint64_t presentation_time) {
TRACE_DURATION("gfx", "UpdateAndDeliverMetrics", "time", presentation_time);
// Gather all of the scene which might need to be updated.
std::set<Scene*> scenes;
for (auto compositor : compositors_) {
compositor->CollectScenes(&scenes);
}
if (scenes.empty())
return;
// TODO(MZ-216): Traversing the whole graph just to compute this is pretty
// inefficient. We should optimize this.
::fuchsia::ui::gfx::Metrics metrics;
metrics.scale_x = 1.f;
metrics.scale_y = 1.f;
metrics.scale_z = 1.f;
std::vector<Node*> updated_nodes;
for (auto scene : scenes) {
UpdateMetrics(scene, metrics, &updated_nodes);
}
// TODO(MZ-216): Deliver events to sessions in batches.
// We probably want delivery to happen somewhere else which can also
// handle delivery of other kinds of events. We should probably also
// have some kind of backpointer from a session to its handler.
for (auto node : updated_nodes) {
if (node->session()) {
auto event = ::fuchsia::ui::gfx::Event();
event.set_metrics(::fuchsia::ui::gfx::MetricsEvent());
event.metrics().node_id = node->id();
event.metrics().metrics = node->reported_metrics();
node->session()->EnqueueEvent(std::move(event));
}
}
}
// TODO(mikejurka): move this to appropriate util file
bool MetricsEquals(const ::fuchsia::ui::gfx::Metrics& a,
const ::fuchsia::ui::gfx::Metrics& b) {
return a.scale_x == b.scale_x && a.scale_y == b.scale_y &&
a.scale_z == b.scale_z;
}
void Engine::UpdateMetrics(Node* node,
const ::fuchsia::ui::gfx::Metrics& parent_metrics,
std::vector<Node*>* updated_nodes) {
::fuchsia::ui::gfx::Metrics local_metrics;
local_metrics.scale_x = parent_metrics.scale_x * node->scale().x;
local_metrics.scale_y = parent_metrics.scale_y * node->scale().y;
local_metrics.scale_z = parent_metrics.scale_z * node->scale().z;
if ((node->event_mask() & ::fuchsia::ui::gfx::kMetricsEventMask) &&
!MetricsEquals(node->reported_metrics(), local_metrics)) {
node->set_reported_metrics(local_metrics);
updated_nodes->push_back(node);
}
ForEachDirectDescendantFrontToBack(
*node, [this, &local_metrics, updated_nodes](Node* node) {
UpdateMetrics(node, local_metrics, updated_nodes);
});
}
void Engine::CleanupEscher() {
// Either there is already a cleanup scheduled (meaning that this was already
// called recently), or there is no Escher because we're running tests.
if (!escher_ || escher_cleanup_scheduled_) {
return;
}
// Only trace when there is the possibility of doing work.
TRACE_DURATION("gfx", "Engine::CleanupEscher");
if (!escher_->Cleanup()) {
// Wait long enough to give GPU work a chance to finish.
const zx::duration kCleanupDelay = zx::msec(1);
escher_cleanup_scheduled_ = true;
async::PostDelayedTask(
async_get_default_dispatcher(),
[weak = weak_factory_.GetWeakPtr()] {
if (weak) {
// Recursively reschedule if cleanup is
// incomplete.
weak->escher_cleanup_scheduled_ = false;
weak->CleanupEscher();
}
},
kCleanupDelay);
}
}
std::string Engine::DumpScenes() const {
std::ostringstream output;
DumpVisitor visitor(output);
bool first = true;
for (auto compositor : compositors_) {
if (first)
first = false;
else
output << std::endl << "===" << std::endl << std::endl;
compositor->Accept(&visitor);
}
return output.str();
}
} // namespace gfx
} // namespace scenic_impl