| // 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/lib/escher/renderer/frame.h" |
| |
| #ifdef OS_FUCHSIA |
| #include <zircon/syscalls.h> |
| #endif |
| |
| #include "src/ui/lib/escher/escher.h" |
| #include "src/ui/lib/escher/impl/frame_manager.h" |
| #include "src/ui/lib/escher/util/trace_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, bool use_protected_memory) |
| : 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), |
| use_protected_memory_(use_protected_memory), |
| queue_(escher()->device()->vk_main_queue()), |
| command_buffer_type_(requested_type), |
| block_allocator_(std::move(allocator)), |
| uniform_block_allocator_(std::move(uniform_buffer_pool)) { |
| FX_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. |
| FX_DCHECK(state_ == State::kReadyToBegin) |
| << "EndFrame() was not called - state_: " << static_cast<int>(state_); |
| } |
| |
| vk::CommandBuffer Frame::vk_command_buffer() const { |
| FX_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_); |
| FX_DCHECK(state_ == State::kReadyToBegin); |
| IssueCommandBuffer(); |
| } |
| |
| void Frame::IssueCommandBuffer() { |
| FX_DCHECK(!command_buffer_); |
| state_ = State::kInProgress; |
| |
| command_buffer_ = |
| CommandBuffer::NewForType(escher(), command_buffer_type_, use_protected_memory_); |
| command_buffer_sequence_number_ = command_buffer_->sequence_number(); |
| |
| if (disable_lazy_pipeline_creation_) { |
| command_buffer_->DisableLazyPipelineCreation(); |
| } |
| } |
| |
| void Frame::SubmitPartialFrame(const SemaphorePtr& frame_done) { |
| FX_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_); |
| FX_DCHECK(state_ == State::kInProgress); |
| |
| command_buffer_->AddSignalSemaphore(frame_done); |
| command_buffer_->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) { |
| std::vector semaphores = {frame_done}; |
| EndFrame(semaphores, std::move(frame_retired_callback)); |
| } |
| |
| void Frame::EndFrame(const std::vector<SemaphorePtr>& semaphores, |
| FrameRetiredCallback frame_retired_callback) { |
| FX_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_); |
| FX_DCHECK(state_ == State::kInProgress); |
| state_ = State::kFinishing; |
| |
| for (const auto& semaphore : semaphores) { |
| command_buffer_->AddSignalSemaphore(semaphore); |
| } |
| |
| // 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_->Submit( |
| queue_, [client_callback{std::move(frame_retired_callback)}, this_frame = FramePtr(this)]() { |
| // Run the client-specified callback. |
| if (client_callback) { |
| client_callback(); |
| } |
| |
| // |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(https://fxbug.dev/42151346): reconsider this keep-alive scheme. |
| // TODO(https://fxbug.dev/42151379): 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::KeepAlive(ResourcePtr resource) { keep_alive_.push_back(std::move(resource)); } |
| |
| CommandBufferPtr Frame::TakeCommandBuffer() { return std::move(command_buffer_); } |
| |
| void Frame::PutCommandBuffer(CommandBufferPtr command_buffer) { |
| FX_DCHECK(!command_buffer_ && command_buffer); |
| FX_DCHECK(command_buffer_sequence_number_ == command_buffer->sequence_number()); |
| |
| command_buffer_ = std::move(command_buffer); |
| } |
| |
| GpuAllocator* Frame::gpu_allocator() { return escher()->gpu_allocator(); } |
| |
| void Frame::DisableLazyPipelineCreation() { |
| disable_lazy_pipeline_creation_ = true; |
| command_buffer_->DisableLazyPipelineCreation(); |
| } |
| |
| } // namespace escher |