blob: 51844ccd7360b6083480176f72b7d74f65c0f519 [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 "lib/escher/renderer/frame.h"
#ifdef OS_FUCHSIA
#include <zircon/syscalls.h>
#endif
#include "lib/escher/escher.h"
#include "lib/escher/impl/frame_manager.h"
#include "lib/escher/util/trace_macros.h"
#include "lib/escher/vk/command_buffer.h"
#include "lib/fxl/macros.h"
namespace escher {
namespace {
// Generates a unique frame count for each created frame.
static uint64_t NextFrameNumber() {
static std::atomic<uint64_t> counter(0);
return ++counter;
}
} // anonymous namespace
const ResourceTypeInfo Frame::kTypeInfo("Frame", ResourceType::kResource,
ResourceType::kFrame);
Frame::Frame(impl::FrameManager* manager,
escher::CommandBuffer::Type requested_type,
BlockAllocator allocator,
impl::UniformBufferPoolWeakPtr uniform_buffer_pool,
uint64_t frame_number, const char* trace_literal,
const char* gpu_vthread_literal, uint64_t gpu_vthread_id,
bool enable_gpu_logging)
: Resource(manager),
frame_number_(frame_number),
escher_frame_number_(NextFrameNumber()),
trace_literal_(trace_literal),
gpu_vthread_literal_(gpu_vthread_literal),
gpu_vthread_id_(gpu_vthread_id),
enable_gpu_logging_(enable_gpu_logging),
queue_(escher()->device()->vk_main_queue()),
command_buffer_type_(requested_type),
block_allocator_(std::move(allocator)),
uniform_block_allocator_(std::move(uniform_buffer_pool)),
profiler_(escher()->supports_timer_queries()
? fxl::MakeRefCounted<TimestampProfiler>(
escher()->vk_device(), escher()->timestamp_period())
: TimestampProfilerPtr()) {
FXL_DCHECK(queue_);
}
Frame::~Frame() {
// Why can we confidently state that if this DCHECK fires, it is because
// EndFrame() was not called? Because when EndFrame() submits the command
// buffer, it registers a closure that will only be called once the frame has
// finished rendering, and because this closure both:
// - refs the Frame, keeping it alive until the closure completes
// - sets the state to kReadyToBegin.
FXL_DCHECK(state_ == State::kReadyToBegin)
<< "EndFrame() was not called - state_: " << static_cast<int>(state_);
}
impl::CommandBuffer* Frame::command_buffer() const {
FXL_DCHECK(command_buffer_) << "Cannot access command buffer.";
return command_buffer_->impl();
}
vk::CommandBuffer Frame::vk_command_buffer() const {
FXL_DCHECK(command_buffer_) << "Cannot access command buffer.";
return command_buffer_->vk();
}
void Frame::BeginFrame() {
TRACE_DURATION("gfx", "escher::Frame::BeginFrame", "frame_number",
frame_number_, "escher_frame_number", escher_frame_number_);
FXL_DCHECK(state_ == State::kReadyToBegin);
static_cast<impl::FrameManager*>(owner())->IncrementNumOutstandingFrames();
IssueCommandBuffer();
AddTimestamp("start of frame");
}
void Frame::IssueCommandBuffer() {
FXL_DCHECK(!command_buffer_);
state_ = State::kInProgress;
command_buffer_ = CommandBuffer::NewForType(escher(), command_buffer_type_);
command_buffer_sequence_number_ = command_buffer_->impl()->sequence_number();
}
void Frame::SubmitPartialFrame(const SemaphorePtr& frame_done) {
FXL_DCHECK(command_buffer_);
++submission_count_;
TRACE_DURATION("gfx", "escher::Frame::SubmitPartialFrame", "frame_number",
frame_number_, "escher_frame_number", escher_frame_number_,
"submission_index", submission_count_);
FXL_DCHECK(state_ == State::kInProgress);
command_buffer_->impl()->AddSignalSemaphore(frame_done);
command_buffer_->impl()->Submit(queue_, nullptr);
// Command buffer has submitted, clear the current command buffer data to
// recycle it.
command_buffer_ = nullptr;
command_buffer_sequence_number_ = 0;
// Issue a new command buffer this frame can be used for more submits.
IssueCommandBuffer();
}
void Frame::EndFrame(const SemaphorePtr& frame_done,
FrameRetiredCallback frame_retired_callback) {
FXL_DCHECK(command_buffer_);
++submission_count_;
TRACE_DURATION("gfx", "escher::Frame::EndFrame", "frame_number",
frame_number_, "escher_frame_number", escher_frame_number_,
"submission_index", submission_count_);
FXL_DCHECK(state_ == State::kInProgress);
state_ = State::kFinishing;
AddTimestamp("end of frame");
command_buffer_->impl()->AddSignalSemaphore(frame_done);
// Submit the final command buffer and register a callback to perform a
// variety of bookkeeping and cleanup tasks.
//
// NOTE: this closure refs this Frame via a FramePtr, guaranteeing that it
// will not be destroyed until the frame is finished rendering.
command_buffer_->impl()->Submit(
queue_, [client_callback{std::move(frame_retired_callback)},
profiler{std::move(profiler_)}, frame_number = frame_number_,
escher_frame_number = escher_frame_number_,
trace_literal = trace_literal_,
gpu_vthread_literal = gpu_vthread_literal_,
gpu_vthread_id = gpu_vthread_id_,
enable_gpu_logging = enable_gpu_logging_,
this_frame = FramePtr(this)]() {
// Run the client-specified callback.
if (client_callback) {
client_callback();
}
// If GPU profiling was enabled, read/interpret the query results and:
// - add them to the system trace (if active).
// - if specified, log a summary.
if (profiler) {
auto timestamps = profiler->GetQueryResults();
auto trace_events = profiler->ProcessTraceEvents(timestamps);
profiler->TraceGpuQueryResults(trace_events, frame_number,
escher_frame_number, trace_literal,
gpu_vthread_literal, gpu_vthread_id);
if (enable_gpu_logging) {
profiler->LogGpuQueryResults(escher_frame_number, timestamps);
}
}
// Notify the FrameManager that this frame is completely finished
// rendering.
static_cast<impl::FrameManager*>(this_frame->owner())
->DecrementNumOutstandingFrames();
// |this_frame| refs the frame until rendering is finished, and
// therefore keeps alive everything in |keep_alive_|.
this_frame->keep_alive_.clear();
// The frame is now ready for reuse or destruction.
this_frame->state_ = State::kReadyToBegin;
});
command_buffer_ = nullptr;
command_buffer_sequence_number_ = 0;
// Keep per-frame uniform buffers alive until frame is finished rendering.
for (auto& buf : uniform_block_allocator_.TakeBuffers()) {
// TODO(ES-103): reconsider this keep-alive scheme.
// TODO(ES-106): test that blocks make it back to the pool but only after
// the frame is finished rendering.
KeepAlive(std::move(buf));
}
// Immediately release per-frame CPU memory; it is no longer needed now that
// all work has been submitted to the GPU.
block_allocator_.Reset();
escher()->Cleanup();
}
void Frame::AddTimestamp(const char* name, vk::PipelineStageFlagBits stages) {
if (profiler_)
profiler_->AddTimestamp(command_buffer_->impl(), stages, name);
}
void Frame::KeepAlive(ResourcePtr resource) {
keep_alive_.push_back(std::move(resource));
}
CommandBufferPtr Frame::TakeCommandBuffer() {
return std::move(command_buffer_);
}
void Frame::PutCommandBuffer(CommandBufferPtr command_buffer) {
FXL_DCHECK(!command_buffer_ && command_buffer);
FXL_DCHECK(command_buffer_sequence_number_ ==
command_buffer->impl()->sequence_number());
command_buffer_ = std::move(command_buffer);
}
GpuAllocator* Frame::gpu_allocator() { return escher()->gpu_allocator(); }
} // namespace escher