blob: d07b2874ab79aae8ccea987a779b2844c2f4f352 [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 "src/ui/scenic/lib/gfx/swapchain/display_swapchain.h"
#include <fuchsia/sysmem/cpp/fidl.h>
#include <lib/async/default.h>
#include <lib/trace/event.h>
#include <fbl/auto_call.h>
#include "src/ui/lib/escher/escher.h"
#include "src/ui/lib/escher/flib/fence.h"
#include "src/ui/lib/escher/impl/naive_image.h"
#include "src/ui/lib/escher/util/bit_ops.h"
#include "src/ui/lib/escher/util/fuchsia_utils.h"
#include "src/ui/lib/escher/util/image_utils.h"
#include "src/ui/lib/escher/vk/gpu_mem.h"
#define VK_CHECK_RESULT(XXX) FX_CHECK(XXX.result == vk::Result::eSuccess)
namespace scenic_impl {
namespace gfx {
// TODO(SCN-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.
static const uint32_t kSwapchainImageCount = 3;
DisplaySwapchain::DisplaySwapchain(
Sysmem* sysmem,
std::shared_ptr<fuchsia::hardware::display::ControllerSyncPtr> display_controller,
std::shared_ptr<display::DisplayControllerListener> display_controller_listener,
display::Display* display, escher::Escher* escher)
: escher_(escher),
sysmem_(sysmem),
display_(display),
display_controller_(display_controller),
display_controller_listener_(display_controller_listener),
swapchain_buffers_(/*count=*/0, /*environment=*/nullptr, /*use_protected_memory=*/false),
protected_swapchain_buffers_(/*count=*/0, /*environment=*/nullptr,
/*use_protected_memory=*/true) {
FX_DCHECK(display);
FX_DCHECK(sysmem);
if (escher_) {
device_ = escher_->vk_device();
queue_ = escher_->device()->vk_main_queue();
display_->Claim();
frames_.resize(kSwapchainImageCount);
if (!InitializeDisplayLayer()) {
FX_LOGS(FATAL) << "Initializing display layer failed";
}
if (!InitializeFramebuffers(escher_->resource_recycler(), /*use_protected_memory=*/false)) {
FX_LOGS(FATAL) << "Initializing buffers for display swapchain failed - check "
"whether fuchsia.sysmem.Allocator is available in this sandbox";
}
display_controller_listener_->SetOnVsyncCallback(
fit::bind_member(this, &DisplaySwapchain::OnVsync));
if ((*display_controller_)->EnableVsync(true) != ZX_OK) {
FX_LOGS(ERROR) << "Failed to enable vsync";
}
} else {
device_ = vk::Device();
queue_ = vk::Queue();
display_->Claim();
FX_VLOGS(2) << "Using a NULL escher in DisplaySwapchain; likely in a test.";
}
}
bool DisplaySwapchain::InitializeFramebuffers(escher::ResourceRecycler* resource_recycler,
bool use_protected_memory) {
FX_CHECK(escher_);
BufferPool::Environment environment = {
.display_controller = display_controller_,
.display = display_,
.escher = escher_,
.sysmem = sysmem_,
.recycler = resource_recycler,
.vk_device = device_,
};
BufferPool pool(kSwapchainImageCount, &environment, use_protected_memory);
if ((*display_controller_)->SetLayerPrimaryConfig(primary_layer_id_, pool.image_config()) !=
ZX_OK) {
FX_LOGS(ERROR) << "Failed to set layer primary config";
}
if (use_protected_memory) {
protected_swapchain_buffers_ = std::move(pool);
} else {
swapchain_buffers_ = std::move(pool);
}
return true;
}
DisplaySwapchain::~DisplaySwapchain() {
if (!escher_) {
display_->Unclaim();
return;
}
// Turn off operations.
if ((*display_controller_)->EnableVsync(false) != ZX_OK) {
FX_LOGS(ERROR) << "Failed to disable vsync";
}
display_controller_listener_->SetOnVsyncCallback(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 && !record->frame_timings->finalized()) {
if (record->render_finished_wait->is_pending()) {
// There has not been an OnFrameRendered signal. The wait will be
// destroyed when this function returns, and will never trigger the
// OnFrameRendered callback. Trigger it here to make the state consistent
// in FrameTimings. Record infinite time to signal unknown render time.
record->frame_timings->OnFrameRendered(record->swapchain_index,
scheduling::FrameTimings::kTimeDropped);
}
record->frame_timings->OnFrameDropped(record->swapchain_index);
}
}
display_->Unclaim();
if ((*display_controller_)->SetDisplayLayers(display_->display_id(), {}) != ZX_OK) {
FX_LOGS(ERROR) << "Failed to configure display layers";
} else {
if ((*display_controller_)->DestroyLayer(primary_layer_id_) != ZX_OK) {
FX_DLOGS(ERROR) << "Failed to destroy layer";
}
}
swapchain_buffers_.Clear(display_controller_);
protected_swapchain_buffers_.Clear(display_controller_);
}
std::unique_ptr<DisplaySwapchain::FrameRecord> DisplaySwapchain::NewFrameRecord(
fxl::WeakPtr<scheduling::FrameTimings> frame_timings, size_t swapchain_index) {
FX_DCHECK(frame_timings);
FX_CHECK(escher_);
auto render_finished_escher_semaphore = escher::Semaphore::NewExportableSem(device_);
zx::event render_finished_event =
GetEventForSemaphore(escher_->device(), render_finished_escher_semaphore);
uint64_t render_finished_event_id = ImportEvent(render_finished_event);
if (!render_finished_escher_semaphore ||
render_finished_event_id == fuchsia::hardware::display::INVALID_DISP_ID) {
FX_LOGS(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) {
FX_LOGS(ERROR) << "DisplaySwapchain::NewFrameRecord() failed to create retired event";
return std::unique_ptr<FrameRecord>();
}
uint64_t retired_event_id = ImportEvent(retired_event);
if (retired_event_id == fuchsia::hardware::display::INVALID_DISP_ID) {
FX_LOGS(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 = swapchain_index;
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_event = std::move(render_finished_event);
record->render_finished_wait = std::make_unique<async::Wait>(
record->render_finished_event.get(), escher::kFenceSignalled, ZX_WAIT_ASYNC_TIMESTAMP,
[this, index = next_frame_index_](async_dispatcher_t* dispatcher, async::Wait* wait,
zx_status_t status, const zx_packet_signal_t* signal) {
OnFrameRendered(index, zx::time(signal->timestamp));
});
// TODO(SCN-244): What to do if rendering fails?
record->render_finished_wait->Begin(async_get_default_dispatcher());
return record;
}
bool DisplaySwapchain::DrawAndPresentFrame(fxl::WeakPtr<scheduling::FrameTimings> frame_timings,
size_t swapchain_index,
const HardwareLayerAssignment& hla,
DrawCallback draw_callback) {
FX_DCHECK(hla.swapchain == this);
FX_DCHECK(frame_timings);
// 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.
auto& old_frame = frames_[next_frame_index_];
if (old_frame) {
if (auto timings = old_frame->frame_timings) {
FX_CHECK(timings->finalized());
}
if (old_frame->retired_event.wait_one(ZX_EVENT_SIGNALED, zx::time(), nullptr) != ZX_OK) {
FX_LOGS(WARNING) << "DisplaySwapchain::DrawAndPresentFrame rendering "
"into in-use backbuffer";
}
if (old_frame->buffer) {
if (old_frame->use_protected_memory) {
protected_swapchain_buffers_.Put(old_frame->buffer);
} else {
swapchain_buffers_.Put(old_frame->buffer);
}
old_frame->buffer = nullptr;
}
}
auto& frame_record = frames_[next_frame_index_] = NewFrameRecord(frame_timings, swapchain_index);
// Find the next framebuffer to render into, and other corresponding data.
frame_record->buffer = use_protected_memory_ ? protected_swapchain_buffers_.GetUnused()
: swapchain_buffers_.GetUnused();
frame_record->use_protected_memory = use_protected_memory_;
FX_CHECK(frame_record->buffer != nullptr);
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.
FX_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.
// FX_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(), frame_record->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");
Flip(display_->display_id(), frame_record->buffer->id, frame_record->render_finished_event_id,
frame_record->retired_event_id);
if ((*display_controller_)->ReleaseEvent(frame_record->render_finished_event_id) != ZX_OK) {
FX_LOGS(ERROR) << "Failed to release display controller event.";
}
if ((*display_controller_)->ReleaseEvent(frame_record->retired_event_id) != ZX_OK) {
FX_LOGS(ERROR) << "Failed to release display controller event.";
}
return true;
}
void DisplaySwapchain::SetDisplayColorConversion(
uint64_t display_id, fuchsia::hardware::display::ControllerSyncPtr& display_controller,
const ColorTransform& transform) {
// Attempt to apply color conversion.
zx_status_t status = display_controller->SetDisplayColorConversion(
display_id, transform.preoffsets, transform.matrix, transform.postoffsets);
if (status != ZX_OK) {
FX_LOGS(WARNING)
<< "DisplaySwapchain:SetDisplayColorConversion failed, controller returned status: "
<< status;
return;
}
// Now check the config.
fuchsia::hardware::display::ConfigResult result;
std::vector<fuchsia::hardware::display::ClientCompositionOp> ops;
display_controller->CheckConfig(/*discard=*/false, &result, &ops);
bool client_color_conversion_required = false;
if (result != fuchsia::hardware::display::ConfigResult::OK) {
client_color_conversion_required = true;
}
for (const auto& op : ops) {
if (op.opcode == fuchsia::hardware::display::ClientCompositionOpcode::CLIENT_COLOR_CONVERSION) {
client_color_conversion_required = true;
break;
}
}
if (client_color_conversion_required) {
// Clear config by calling |CheckConfig| once more with "discard" set to true.
display_controller->CheckConfig(/*discard=*/true, &result, &ops);
// TODO (24591): Implement scenic software fallback for color correction.
FX_LOGS(ERROR) << "Software fallback for color conversion not implemented.";
}
}
void DisplaySwapchain::SetDisplayColorConversion(const ColorTransform& transform) {
FX_CHECK(display_);
uint64_t display_id = display_->display_id();
SetDisplayColorConversion(display_id, *display_controller_, transform);
}
void DisplaySwapchain::SetUseProtectedMemory(bool use_protected_memory) {
if (use_protected_memory == use_protected_memory_)
return;
// Allocate protected memory buffers lazily and once only.
// TODO(35785): Free this memory chunk when we no longer expect protected memory.
if (use_protected_memory && protected_swapchain_buffers_.empty()) {
InitializeFramebuffers(escher_->resource_recycler(), use_protected_memory);
}
use_protected_memory_ = use_protected_memory;
}
bool DisplaySwapchain::InitializeDisplayLayer() {
zx_status_t create_layer_status;
zx_status_t transport_status =
(*display_controller_)->CreateLayer(&create_layer_status, &primary_layer_id_);
if (create_layer_status != ZX_OK || transport_status != ZX_OK) {
FX_LOGS(ERROR) << "Failed to create layer, " << create_layer_status;
return false;
}
zx_status_t status =
(*display_controller_)->SetDisplayLayers(display_->display_id(), {primary_layer_id_});
if (status != ZX_OK) {
FX_LOGS(ERROR) << "Failed to configure display layers";
return false;
}
return true;
}
void DisplaySwapchain::OnFrameRendered(size_t frame_index, zx::time render_finished_time) {
FX_DCHECK(frame_index < kSwapchainImageCount);
auto& record = frames_[frame_index];
uint64_t frame_number = record->frame_timings ? record->frame_timings->frame_number() : 0u;
TRACE_DURATION("gfx", "DisplaySwapchain::OnFrameRendered", "frame count", frame_number,
"frame index", frame_index);
TRACE_FLOW_END("gfx", "scenic_frame", frame_number);
// It is effectively 1-indexed in the display.
TRACE_FLOW_BEGIN("gfx", "present_image", frame_index + 1);
FX_DCHECK(record);
if (record->frame_timings) {
record->frame_timings->OnFrameRendered(record->swapchain_index, render_finished_time);
// See ::OnVsync for comment about finalization.
}
}
void DisplaySwapchain::OnVsync(uint64_t display_id, uint64_t timestamp,
std::vector<uint64_t> image_ids, uint64_t cookie) {
if (on_vsync_) {
on_vsync_(zx::time(timestamp));
}
// Respond acknowledgement message to display controller.
if (cookie) {
(*display_controller_)->AcknowledgeVsync(cookie);
}
if (image_ids.empty()) {
return;
}
// Currently, only a single layer is ever used
FX_CHECK(image_ids.size() == 1);
uint64_t image_id = image_ids[0];
bool match = false;
while (outstanding_frame_count_ && !match) {
auto& record = frames_[presented_frame_idx_];
match = record->buffer->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) {
record->frame_timings->OnFramePresented(record->swapchain_index, zx::time(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
// for the FrameTimings to be finalized, 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_--;
}
}
FX_DCHECK(match) << "Unhandled vsync image_id=" << image_id;
}
uint64_t DisplaySwapchain::ImportEvent(const zx::event& event) {
zx::event dup;
uint64_t event_id = next_event_id_++;
if (event.duplicate(ZX_RIGHT_SAME_RIGHTS, &dup) != ZX_OK) {
FX_LOGS(ERROR) << "Failed to duplicate display controller event.";
return fuchsia::hardware::display::INVALID_DISP_ID;
}
auto before = zx::clock::get_monotonic();
if ((*display_controller_)->ImportEvent(std::move(dup), event_id) != ZX_OK) {
auto after = zx::clock::get_monotonic();
FX_LOGS(ERROR) << "Failed to import display controller event. Waited "
<< (after - before).to_msecs() << "msecs";
return fuchsia::hardware::display::INVALID_DISP_ID;
}
return event_id;
}
void DisplaySwapchain::Flip(uint64_t layer_id, uint64_t buffer, uint64_t render_finished_event_id,
uint64_t signal_event_id) {
zx_status_t status =
(*display_controller_)
->SetLayerImage(layer_id, buffer, render_finished_event_id, signal_event_id);
// TODO(SCN-244): handle this more robustly.
FX_CHECK(status == ZX_OK) << "DisplaySwapchain::Flip failed";
auto before = zx::clock::get_monotonic();
status = (*display_controller_)->ApplyConfig();
// TODO(SCN-244): handle this more robustly.
FX_CHECK(status == ZX_OK) << "DisplaySwapchain::Flip failed. Waited "
<< (zx::clock::get_monotonic() - before).to_msecs() << "msecs";
}
} // namespace gfx
} // namespace scenic_impl