blob: ef10caaa9475bdbd31342fe9f9a9a3ea0d561c03 [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/swapchain/display_swapchain.h"
#include <trace/event.h>
#include "garnet/lib/ui/gfx/displays/display.h"
#include "garnet/lib/ui/gfx/displays/display_manager.h"
#include "garnet/lib/ui/gfx/engine/frame_timings.h"
#include "lib/escher/escher.h"
#include "lib/escher/flib/fence.h"
#include "lib/escher/impl/naive_image.h"
#include "lib/escher/util/fuchsia_utils.h"
#include "lib/escher/util/image_utils.h"
#include "lib/escher/vk/gpu_mem.h"
namespace scenic_impl {
namespace gfx {
namespace {
#define VK_CHECK_RESULT(XXX) FXL_CHECK(XXX.result == vk::Result::eSuccess)
// TODO(MZ-400): Don't triple buffer. This is done to avoid "tearing", but it
// wastes memory, and can result in the "permanent" addition of an extra Vsync
// period of latency. An alternative would be to use an acquire fence; this
// saves memory, but can still result in the permanent extra latency. Here's
// how:
//
// First, let's see how tearing occurs in the 2-framebuffer case.
//
// Let's say we have framebuffers A and B in a world that conveniently starts at
// some negative time, such that the first frame rendered into A has a target
// presentation time of 0ms, and the next frame is rendered into B with a target
// presentation time of 16ms.
//
// However, assume that frame being rendered into A takes a bit too long, so
// that instead of being presented at 0ms, it is instead presented at 16ms. The
// frame to render into B has already been scheduled, and starts rendering at
// 8ms to hit the target presentation time of 16ms. Even if it's fast, it
// cannot present at 16ms, because that frame has already been "claimed" by A,
// and so it is instead presented at 32ms.
//
// The tearing occurs when it is time to render A again. We don't know that B
// has been deferred to present at 32ms. So, we wake up at 24ms to render into
// A to hit the 32ms target. Oops!
//
// The problem is that A is still being displayed from 16-32ms, until it is
// replaced by B at 32ms. Thus, tearing.
//
// If you followed that, it should be clear both why triple-buffering fixes the
// tearing, and why it adds the frame of latency.
const uint32_t kSwapchainImageCount = 3;
// Helper functions
// Enumerate the formats supported for the specified surface/device, and pick a
// suitable one.
vk::Format GetDisplayImageFormat(escher::VulkanDeviceQueues* device_queues);
// Determines if the VK_GOOGLE_IMAGE_USAGE_SCANOUT_EXTENSION is supported.
vk::ImageUsageFlags GetFramebufferImageUsage();
} // namespace
DisplaySwapchain::DisplaySwapchain(DisplayManager* display_manager,
Display* display,
EventTimestamper* timestamper,
escher::Escher* escher)
: escher_(escher),
display_manager_(display_manager),
display_(display),
timestamper_(timestamper) {
FXL_DCHECK(display);
FXL_DCHECK(timestamper);
if (escher_) {
device_ = escher_->vk_device();
queue_ = escher_->device()->vk_main_queue();
format_ = GetDisplayImageFormat(escher->device());
display_->Claim();
frames_.resize(kSwapchainImageCount);
if (!InitializeFramebuffers(escher_->resource_recycler())) {
FXL_LOG(ERROR) << "Initializing buffers for display swapchain failed.";
}
} else {
device_ = vk::Device();
queue_ = vk::Queue();
format_ = vk::Format::eUndefined;
display_->Claim();
FXL_VLOG(2) << "Using a NULL escher in DisplaySwapchain; likely in a test.";
}
}
bool DisplaySwapchain::InitializeFramebuffers(
escher::ResourceRecycler* resource_recycler) {
FXL_CHECK(escher_);
vk::ImageUsageFlags image_usage = GetFramebufferImageUsage();
#if !defined(__aarch64__) && !defined(__x86_64__)
FXL_DLOG(ERROR) << "Display swapchain only supported on intel and arm";
return false;
#endif
const uint32_t width_in_px = display_->width_in_px();
const uint32_t height_in_px = display_->height_in_px();
zx_pixel_format_t pixel_format;
#if defined(__aarch64__)
pixel_format = ZX_PIXEL_FORMAT_RGB_x888;
#else
pixel_format = ZX_PIXEL_FORMAT_ARGB_8888;
#endif
display_manager_->SetImageConfig(width_in_px, height_in_px, pixel_format);
for (uint32_t i = 0; i < kSwapchainImageCount; i++) {
// Allocate a framebuffer.
// Start by creating a VkImage.
// TODO(ES-42): Create this using Escher APIs.
vk::ImageCreateInfo create_info;
create_info.imageType = vk::ImageType::e2D, create_info.format = format_;
create_info.extent = vk::Extent3D{width_in_px, height_in_px, 1};
create_info.mipLevels = 1;
create_info.arrayLayers = 1;
create_info.samples = vk::SampleCountFlagBits::e1;
#if defined(__x86_64__)
create_info.tiling = vk::ImageTiling::eOptimal;
#else
create_info.tiling = vk::ImageTiling::eLinear;
// TODO(SCN-79): Use vulkan extension to allocate with correct stride.
create_info.extent.width =
display_manager_->FetchLinearStride(width_in_px, pixel_format);
#endif
create_info.usage = image_usage;
create_info.sharingMode = vk::SharingMode::eExclusive;
create_info.initialLayout = vk::ImageLayout::eUndefined;
auto image_result = device_.createImage(create_info);
if (image_result.result != vk::Result::eSuccess) {
FXL_LOG(ERROR) << "VkCreateImage failed: "
<< vk::to_string(image_result.result);
return false;
}
// Allocate memory to get a VkDeviceMemory.
auto memory_requirements =
device_.getImageMemoryRequirements(image_result.value);
uint32_t memory_type_index = 0;
zx::vmo memory =
display_manager_->AllocateDisplayMemory(memory_requirements.size);
if (!memory) {
FXL_LOG(ERROR) << "allocating vmo failed";
return false;
}
vk::ImportMemoryFuchsiaHandleInfoKHR import_info;
import_info.setHandle(memory.release());
import_info.setHandleType(
vk::ExternalMemoryHandleTypeFlagBits::eFuchsiaVmoKHR);
vk::MemoryAllocateInfo alloc_info;
alloc_info.setPNext(&import_info);
alloc_info.allocationSize = memory_requirements.size;
alloc_info.memoryTypeIndex = memory_type_index;
auto mem_result = device_.allocateMemory(alloc_info);
if (mem_result.result != vk::Result::eSuccess) {
FXL_LOG(ERROR) << "vkAllocMemory failed: "
<< vk::to_string(mem_result.result);
return false;
}
Framebuffer buffer;
buffer.device_memory = escher::GpuMem::AdoptVkMemory(
device_, mem_result.value, memory_requirements.size,
false /* needs_mapped_ptr */);
FXL_CHECK(buffer.device_memory);
// Wrap the image and device memory in a escher::Image.
escher::ImageInfo image_info;
image_info.format = format_;
image_info.width = width_in_px;
image_info.height = height_in_px;
image_info.usage = image_usage;
// escher::NaiveImage::AdoptVkImage() binds the memory to the image.
buffer.escher_image = escher::impl::NaiveImage::AdoptVkImage(
resource_recycler, image_info, image_result.value,
buffer.device_memory);
if (!buffer.escher_image) {
FXL_LOG(ERROR) << "Creating escher::EscherImage failed.";
device_.destroyImage(image_result.value);
return false;
}
// TODO(ES-39): Add stride to escher::ImageInfo so we can use
// getImageSubresourceLayout to look up rowPitch and use it appropriately.
/*vk::ImageSubresource subres;
subres.aspectMask = vk::ImageAspectFlagBits::eColor;
subres.mipLevel = 0;
subres.arrayLayer = 0;
auto layout = device_.getImageSubresourceLayout(image_result.value, subres);
FXL_DCHECK(layout.rowPitch ==
display_->width() *
escher::image_utils::BytesPerPixel(format_));
*/
// Export the vkDeviceMemory to a VMO.
vk::MemoryGetFuchsiaHandleInfoKHR export_memory_info(
buffer.device_memory->base(),
vk::ExternalMemoryHandleTypeFlagBits::eFuchsiaVmoKHR);
auto export_result =
escher_->device()->proc_addrs().getMemoryFuchsiaHandleKHR(
device_, export_memory_info);
if (export_result.result != vk::Result::eSuccess) {
FXL_LOG(ERROR) << "VkGetMemoryFuchsiaHandleKHR failed: "
<< vk::to_string(export_result.result);
return false;
}
buffer.vmo = zx::vmo(export_result.value);
buffer.fb_id = display_manager_->ImportImage(buffer.vmo);
if (buffer.fb_id == fuchsia::hardware::display::invalidId) {
FXL_LOG(ERROR) << "Importing image failed.";
return false;
}
swapchain_buffers_.push_back(std::move(buffer));
}
if (!display_manager_->EnableVsync(
fit::bind_member(this, &DisplaySwapchain::OnVsync))) {
FXL_LOG(ERROR) << "Failed to enable vsync";
return false;
}
return true;
}
DisplaySwapchain::~DisplaySwapchain() {
if (!escher_) {
display_->Unclaim();
return;
}
// Turn off operations.
display_manager_->EnableVsync(nullptr);
// A FrameRecord is now stale and will no longer receive the OnFramePresented
// callback; OnFrameDropped will clean up and make the state consistent.
for (size_t i = 0; i < frames_.size(); ++i) {
const size_t idx = (i + next_frame_index_) % frames_.size();
FrameRecord* record = frames_[idx].get();
if (record && !record->frame_timings->finalized()) {
record->frame_timings->OnFrameDropped(record->swapchain_index);
}
}
display_->Unclaim();
for (auto& buffer : swapchain_buffers_) {
display_manager_->ReleaseImage(buffer.fb_id);
}
}
std::unique_ptr<DisplaySwapchain::FrameRecord> DisplaySwapchain::NewFrameRecord(
const FrameTimingsPtr& frame_timings) {
FXL_DCHECK(frame_timings);
FXL_CHECK(escher_);
auto render_finished_escher_semaphore =
escher::Semaphore::NewExportableSem(device_);
zx::event render_finished_event =
GetEventForSemaphore(escher_->device()->proc_addrs(), device_,
render_finished_escher_semaphore);
uint64_t render_finished_event_id =
display_manager_->ImportEvent(render_finished_event);
if (!render_finished_escher_semaphore ||
render_finished_event_id == fuchsia::hardware::display::invalidId) {
FXL_LOG(ERROR)
<< "DisplaySwapchain::NewFrameRecord() failed to create semaphores";
return std::unique_ptr<FrameRecord>();
}
zx::event retired_event;
zx_status_t status = zx::event::create(0, &retired_event);
if (status != ZX_OK) {
FXL_LOG(ERROR)
<< "DisplaySwapchain::NewFrameRecord() failed to create retired event";
return std::unique_ptr<FrameRecord>();
}
uint64_t retired_event_id = display_manager_->ImportEvent(retired_event);
if (retired_event_id == fuchsia::hardware::display::invalidId) {
FXL_LOG(ERROR)
<< "DisplaySwapchain::NewFrameRecord() failed to import retired event";
return std::unique_ptr<FrameRecord>();
}
auto record = std::make_unique<FrameRecord>();
record->frame_timings = frame_timings;
record->swapchain_index = frame_timings->AddSwapchain(this);
record->render_finished_escher_semaphore =
std::move(render_finished_escher_semaphore);
record->render_finished_event_id = render_finished_event_id;
record->retired_event = std::move(retired_event);
record->retired_event_id = retired_event_id;
record->render_finished_watch = EventTimestamper::Watch(
timestamper_, std::move(render_finished_event), escher::kFenceSignalled,
[this, index = next_frame_index_](zx_time_t timestamp) {
OnFrameRendered(index, timestamp);
});
return record;
}
bool DisplaySwapchain::DrawAndPresentFrame(const FrameTimingsPtr& frame_timings,
const HardwareLayerAssignment& hla,
DrawCallback draw_callback) {
FXL_DCHECK(hla.swapchain == this);
// Find the next framebuffer to render into, and other corresponding data.
auto& buffer = swapchain_buffers_[next_frame_index_];
// Create a record that can be used to notify |frame_timings| (and hence
// ultimately the FrameScheduler) that the frame has been presented.
//
// There must not already exist a pending record. If there is, it indicates
// an error in the FrameScheduler logic (or somewhere similar), which should
// not have scheduled another frame when there are no framebuffers available.
if (frames_[next_frame_index_]) {
FXL_CHECK(frames_[next_frame_index_]->frame_timings->finalized());
if (frames_[next_frame_index_]->retired_event.wait_one(
ZX_EVENT_SIGNALED, zx::time(), nullptr) != ZX_OK) {
FXL_LOG(WARNING) << "DisplaySwapchain::DrawAndPresentFrame rendering "
"into in-use backbuffer";
}
}
auto& frame_record = frames_[next_frame_index_] =
NewFrameRecord(frame_timings);
// TODO(MZ-244): See below. What to do if rendering fails?
frame_record->render_finished_watch.Start();
next_frame_index_ = (next_frame_index_ + 1) % kSwapchainImageCount;
outstanding_frame_count_++;
// Render the scene.
size_t num_hardware_layers = hla.items.size();
// TODO(SCN-1088): handle more hardware layers.
FXL_DCHECK(num_hardware_layers == 1);
// TODO(SCN-1098): we'd like to validate that the layer ID is supported
// by the display/display-controller, but the DisplayManager API doesn't
// currently expose it, and rather than hack in an accessor for |layer_id_|
// we should fix this "properly", whatever that means.
// FXL_DCHECK(hla.items[0].hardware_layer_id is supported by display);
for (size_t i = 0; i < num_hardware_layers; ++i) {
TRACE_DURATION("gfx", "DisplaySwapchain::DrawAndPresent() draw");
// A single semaphore is sufficient to guarantee that all images have been
// rendered, so only provide the semaphore when rendering the image for
// the final layer.
escher::SemaphorePtr render_finished_escher_semaphore =
(i + 1 == num_hardware_layers)
? frame_record->render_finished_escher_semaphore
: escher::SemaphorePtr();
// TODO(SCN-1088): handle more hardware layers: the single image from
// buffer.escher_image is not enough; we need one for each layer.
draw_callback(frame_timings->target_presentation_time(),
buffer.escher_image, hla.items[i], escher::SemaphorePtr(),
render_finished_escher_semaphore);
}
// When the image is completely rendered, present it.
TRACE_DURATION("gfx", "DisplaySwapchain::DrawAndPresent() present");
display_manager_->Flip(display_, buffer.fb_id,
frame_record->render_finished_event_id,
frame_record->retired_event_id);
display_manager_->ReleaseEvent(frame_record->render_finished_event_id);
display_manager_->ReleaseEvent(frame_record->retired_event_id);
return true;
}
void DisplaySwapchain::OnFrameRendered(size_t frame_index,
zx_time_t render_finished_time) {
FXL_DCHECK(frame_index < kSwapchainImageCount);
auto& record = frames_[frame_index];
FXL_DCHECK(record);
record->frame_timings->OnFrameRendered(record->swapchain_index,
render_finished_time);
// See ::OnVsync for comment about finalization.
}
void DisplaySwapchain::OnVsync(zx_time_t timestamp,
const std::vector<uint64_t>& image_ids) {
if (image_ids.empty()) {
return;
}
// Currently, only a single layer is ever used
FXL_CHECK(image_ids.size() == 1);
uint64_t image_id = image_ids[0];
bool match = false;
while (outstanding_frame_count_ && !match) {
auto& buf = swapchain_buffers_[presented_frame_idx_];
auto& record = frames_[presented_frame_idx_];
match = buf.fb_id == image_id;
// Don't double-report a frame as presented if a frame is shown twice
// due to the next frame missing its deadline.
if (!record->presented) {
record->presented = true;
if (match) {
record->frame_timings->OnFramePresented(record->swapchain_index,
timestamp);
} else {
record->frame_timings->OnFrameDropped(record->swapchain_index);
}
}
// Retaining the currently displayed frame allows us to differentiate
// between a frame being dropped and a frame being displayed twice
// without having to look ahead in the queue, so only update the queue
// when we know that the display controller has progressed to the next
// frame.
//
// Since there is no guaranteed order between a frame being retired here
// and OnFrameRendered() for a given frame, and since both must be called
// in order for the FrameTimings to be finalzied, we don't immediately
// destroy the FrameRecord. It will eventually be replaced by
// DrawAndPresentFrame(), when a new frame is rendered into this index.
if (!match) {
presented_frame_idx_ = (presented_frame_idx_ + 1) % kSwapchainImageCount;
outstanding_frame_count_--;
}
}
FXL_DCHECK(match) << "Unhandled vsync";
}
namespace {
vk::ImageUsageFlags GetFramebufferImageUsage() {
const std::string kGoogleImageUsageScanoutExtensionName(
VK_GOOGLE_IMAGE_USAGE_SCANOUT_EXTENSION_NAME);
auto instance_extensions = vk::enumerateInstanceExtensionProperties();
if (instance_extensions.result != vk::Result::eSuccess) {
FXL_DLOG(ERROR) << "vkEnumerateInstanceExtensionProperties failed: "
<< vk::to_string(instance_extensions.result);
return vk::ImageUsageFlagBits::eColorAttachment;
}
for (auto& extension : instance_extensions.value) {
if (extension.extensionName == kGoogleImageUsageScanoutExtensionName) {
return vk::ImageUsageFlagBits::eScanoutGOOGLE |
vk::ImageUsageFlagBits::eColorAttachment;
}
}
FXL_DLOG(ERROR)
<< "Unable to find optimal framebuffer image usage extension ("
<< kGoogleImageUsageScanoutExtensionName << ").";
return vk::ImageUsageFlagBits::eColorAttachment;
}
vk::Format GetDisplayImageFormat(escher::VulkanDeviceQueues* device_queues) {
return vk::Format::eB8G8R8A8Unorm;
}
} // namespace
} // namespace gfx
} // namespace scenic_impl