| // 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/lib/fxl/macros.h" |
| #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" |
| #include "src/ui/lib/escher/vk/command_buffer.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)), |
| // vkCmdBeginQuery, vkCmdEndQuery that is used in querying gpu cannot be executed on a |
| // protected command buffer. |
| // https://www.khronos.org/registry/vulkan/specs/1.1-extensions/man/html/vkCmdBeginQuery.html |
| profiler_((escher()->supports_timer_queries() && enable_gpu_logging && !use_protected_memory) |
| ? 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_); |
| } |
| |
| 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); |
| IssueCommandBuffer(); |
| AddTimestamp("start of frame"); |
| } |
| |
| void Frame::IssueCommandBuffer() { |
| FXL_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(); |
| } |
| |
| 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_->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) { |
| 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_->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_->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); |
| } |
| } |
| |
| // |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_, 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->sequence_number()); |
| |
| command_buffer_ = std::move(command_buffer); |
| } |
| |
| GpuAllocator* Frame::gpu_allocator() { return escher()->gpu_allocator(); } |
| |
| } // namespace escher |