diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter index a6e3a6f30a1a3..7185fc4718bdf 100644 --- a/ci/licenses_golden/licenses_flutter +++ b/ci/licenses_golden/licenses_flutter @@ -2185,6 +2185,8 @@ ORIGIN: ../../../flutter/impeller/renderer/backend/vulkan/fence_waiter_vk.cc + . ORIGIN: ../../../flutter/impeller/renderer/backend/vulkan/fence_waiter_vk.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/impeller/renderer/backend/vulkan/formats_vk.cc + ../../../flutter/LICENSE ORIGIN: ../../../flutter/impeller/renderer/backend/vulkan/formats_vk.h + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/impeller/renderer/backend/vulkan/gpu_tracer_vk.cc + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/impeller/renderer/backend/vulkan/gpu_tracer_vk.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/impeller/renderer/backend/vulkan/pass_bindings_cache.cc + ../../../flutter/LICENSE ORIGIN: ../../../flutter/impeller/renderer/backend/vulkan/pass_bindings_cache.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/impeller/renderer/backend/vulkan/pipeline_cache_vk.cc + ../../../flutter/LICENSE @@ -4946,6 +4948,8 @@ FILE: ../../../flutter/impeller/renderer/backend/vulkan/fence_waiter_vk.cc FILE: ../../../flutter/impeller/renderer/backend/vulkan/fence_waiter_vk.h FILE: ../../../flutter/impeller/renderer/backend/vulkan/formats_vk.cc FILE: ../../../flutter/impeller/renderer/backend/vulkan/formats_vk.h +FILE: ../../../flutter/impeller/renderer/backend/vulkan/gpu_tracer_vk.cc +FILE: ../../../flutter/impeller/renderer/backend/vulkan/gpu_tracer_vk.h FILE: ../../../flutter/impeller/renderer/backend/vulkan/limits_vk.h FILE: ../../../flutter/impeller/renderer/backend/vulkan/pass_bindings_cache.cc FILE: ../../../flutter/impeller/renderer/backend/vulkan/pass_bindings_cache.h diff --git a/impeller/renderer/backend/vulkan/BUILD.gn b/impeller/renderer/backend/vulkan/BUILD.gn index 6b49311ed35a2..0a7702f36a566 100644 --- a/impeller/renderer/backend/vulkan/BUILD.gn +++ b/impeller/renderer/backend/vulkan/BUILD.gn @@ -61,6 +61,8 @@ impeller_component("vulkan") { "fence_waiter_vk.h", "formats_vk.cc", "formats_vk.h", + "gpu_tracer_vk.cc", + "gpu_tracer_vk.h", "limits_vk.h", "pass_bindings_cache.cc", "pass_bindings_cache.h", diff --git a/impeller/renderer/backend/vulkan/command_buffer_vk.cc b/impeller/renderer/backend/vulkan/command_buffer_vk.cc index 0059fe41b26f2..78eb547847082 100644 --- a/impeller/renderer/backend/vulkan/command_buffer_vk.cc +++ b/impeller/renderer/backend/vulkan/command_buffer_vk.cc @@ -52,6 +52,9 @@ const std::shared_ptr& CommandBufferVK::GetEncoder() { } bool CommandBufferVK::OnSubmitCommands(CompletionCallback callback) { + if (!encoder_) { + encoder_ = encoder_factory_->Create(); + } if (!callback) { return encoder_->Submit(); } diff --git a/impeller/renderer/backend/vulkan/context_vk.cc b/impeller/renderer/backend/vulkan/context_vk.cc index 5a8b0e98c85b2..028b92ffb1be4 100644 --- a/impeller/renderer/backend/vulkan/context_vk.cc +++ b/impeller/renderer/backend/vulkan/context_vk.cc @@ -26,6 +26,7 @@ #include "impeller/renderer/backend/vulkan/command_pool_vk.h" #include "impeller/renderer/backend/vulkan/debug_report_vk.h" #include "impeller/renderer/backend/vulkan/fence_waiter_vk.h" +#include "impeller/renderer/backend/vulkan/gpu_tracer_vk.h" #include "impeller/renderer/backend/vulkan/resource_manager_vk.h" #include "impeller/renderer/backend/vulkan/surface_context_vk.h" #include "impeller/renderer/capabilities.h" @@ -430,6 +431,10 @@ void ContextVK::Setup(Settings settings) { device_name_ = std::string(physical_device_properties.deviceName); is_valid_ = true; + // Create the GPU Tracer later because it depends on state from + // the ContextVK. + gpu_tracer_ = std::make_shared(weak_from_this()); + //---------------------------------------------------------------------------- /// Label all the relevant objects. This happens after setup so that the /// debug messengers have had a chance to be set up. @@ -532,4 +537,8 @@ ContextVK::CreateGraphicsCommandEncoderFactory() const { return std::make_unique(weak_from_this()); } +std::shared_ptr ContextVK::GetGPUTracer() const { + return gpu_tracer_; +} + } // namespace impeller diff --git a/impeller/renderer/backend/vulkan/context_vk.h b/impeller/renderer/backend/vulkan/context_vk.h index 144de15b26913..0febc8b03da15 100644 --- a/impeller/renderer/backend/vulkan/context_vk.h +++ b/impeller/renderer/backend/vulkan/context_vk.h @@ -32,6 +32,7 @@ class DebugReportVK; class FenceWaiterVK; class ResourceManagerVK; class SurfaceContextVK; +class GPUTracerVK; class ContextVK final : public Context, public BackendCast, @@ -144,6 +145,10 @@ class ContextVK final : public Context, std::shared_ptr GetCommandPoolRecycler() const; + std::shared_ptr GetGPUTracer() const; + + void RecordFrameEndTime() const; + private: struct DeviceHolderImpl : public DeviceHolder { // |DeviceHolder| @@ -171,6 +176,8 @@ class ContextVK final : public Context, std::shared_ptr command_pool_recycler_; std::string device_name_; std::shared_ptr raster_message_loop_; + std::shared_ptr gpu_tracer_; + bool sync_presentation_ = false; const uint64_t hash_; diff --git a/impeller/renderer/backend/vulkan/gpu_tracer_vk.cc b/impeller/renderer/backend/vulkan/gpu_tracer_vk.cc new file mode 100644 index 0000000000000..41614ab708f54 --- /dev/null +++ b/impeller/renderer/backend/vulkan/gpu_tracer_vk.cc @@ -0,0 +1,134 @@ +// Copyright 2013 The Flutter 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 "impeller/renderer/backend/vulkan/gpu_tracer_vk.h" + +#include +#include "fml/trace_event.h" +#include "impeller/base/validation.h" +#include "impeller/renderer/backend/vulkan/command_buffer_vk.h" +#include "impeller/renderer/backend/vulkan/command_encoder_vk.h" +#include "impeller/renderer/backend/vulkan/context_vk.h" +#include "impeller/renderer/command_buffer.h" + +namespace impeller { + +static constexpr uint32_t kPoolSize = 128u; + +GPUTracerVK::GPUTracerVK(const std::weak_ptr& context) + : context_(context) { + auto strong_context = context_.lock(); + if (!strong_context) { + return; + } + timestamp_period_ = strong_context->GetPhysicalDevice() + .getProperties() + .limits.timestampPeriod; + if (timestamp_period_ <= 0) { + // The device does not support timestamp queries. + return; + } + vk::QueryPoolCreateInfo info; + info.queryCount = kPoolSize; + info.queryType = vk::QueryType::eTimestamp; + + auto [status, pool] = strong_context->GetDevice().createQueryPoolUnique(info); + if (status != vk::Result::eSuccess) { + VALIDATION_LOG << "Failed to create query pool."; + return; + } + query_pool_ = std::move(pool); + // Disable tracing in release mode. +#ifdef IMPELLER_DEBUG + valid_ = true; +#endif +} + +void GPUTracerVK::RecordStartFrameTime() { + if (!valid_) { + return; + } + auto strong_context = context_.lock(); + if (!strong_context) { + return; + } + auto buffer = strong_context->CreateCommandBuffer(); + auto vk_trace_cmd_buffer = + CommandBufferVK::Cast(*buffer).GetEncoder()->GetCommandBuffer(); + // The two commands below are executed in order, such that writeTimeStamp is + // guaranteed to occur after resetQueryPool has finished. The validation + // layer seem particularly strict, and efforts to reset the entire pool + // were met with validation errors (though seemingly correct measurements). + // To work around this, the tracer only resets the query that will be + // used next. + vk_trace_cmd_buffer.resetQueryPool(query_pool_.get(), current_index_, 1); + vk_trace_cmd_buffer.writeTimestamp(vk::PipelineStageFlagBits::eBottomOfPipe, + query_pool_.get(), current_index_); + + if (!buffer->SubmitCommands()) { + VALIDATION_LOG << "GPUTracerVK: Failed to record start time."; + } + + // The logic in RecordEndFrameTime requires us to have recorded a pair of + // tracing events. If this method failed for any reason we need to be sure we + // don't attempt to record and read back a second value, or we will get values + // that span multiple frames. + started_frame_ = true; +} + +void GPUTracerVK::RecordEndFrameTime() { + if (!valid_ || !started_frame_) { + return; + } + auto strong_context = context_.lock(); + if (!strong_context) { + return; + } + + started_frame_ = false; + auto last_query = current_index_; + current_index_ += 1; + + auto buffer = strong_context->CreateCommandBuffer(); + auto vk_trace_cmd_buffer = + CommandBufferVK::Cast(*buffer).GetEncoder()->GetCommandBuffer(); + vk_trace_cmd_buffer.resetQueryPool(query_pool_.get(), current_index_, 1); + vk_trace_cmd_buffer.writeTimestamp(vk::PipelineStageFlagBits::eBottomOfPipe, + query_pool_.get(), current_index_); + + // On completion of the second time stamp recording, we read back this value + // and the previous value. The difference is approximately the frame time. + const auto device_holder = strong_context->GetDeviceHolder(); + if (!buffer->SubmitCommands([&, last_query, + device_holder](CommandBuffer::Status status) { + auto strong_context = context_.lock(); + if (!strong_context) { + return; + } + uint64_t bits[2] = {0, 0}; + auto result = device_holder->GetDevice().getQueryPoolResults( + query_pool_.get(), last_query, 2, sizeof(bits), &bits, + sizeof(int64_t), vk::QueryResultFlagBits::e64); + + if (result == vk::Result::eSuccess) { + // This value should probably be available in some form besides a + // timeline event but that is a job for a future Jonah. + auto gpu_ms = (((bits[1] - bits[0]) * timestamp_period_) / 1000000); + FML_TRACE_COUNTER( + "flutter", "GPUTracer", + reinterpret_cast(this), // Trace Counter ID + "FrameTimeMS", gpu_ms); + } + })) { + if (!buffer->SubmitCommands()) { + VALIDATION_LOG << "GPUTracerVK failed to record frame end time."; + } + } + + if (current_index_ == kPoolSize - 1) { + current_index_ = 0u; + } +} + +} // namespace impeller diff --git a/impeller/renderer/backend/vulkan/gpu_tracer_vk.h b/impeller/renderer/backend/vulkan/gpu_tracer_vk.h new file mode 100644 index 0000000000000..8913bf3ffdb08 --- /dev/null +++ b/impeller/renderer/backend/vulkan/gpu_tracer_vk.h @@ -0,0 +1,40 @@ +// Copyright 2013 The Flutter 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 + +#include "impeller/renderer/backend/vulkan/context_vk.h" + +namespace impeller { + +/// @brief A class that uses timestamp queries to record the approximate GPU +/// execution time. +class GPUTracerVK { + public: + explicit GPUTracerVK(const std::weak_ptr& context); + + ~GPUTracerVK() = default; + + /// @brief Record the approximate start time of the GPU workload for the + /// current frame. + void RecordStartFrameTime(); + + /// @brief Record the approximate end time of the GPU workload for the current + /// frame. + void RecordEndFrameTime(); + + private: + void ResetQueryPool(size_t pool); + + const std::weak_ptr context_; + vk::UniqueQueryPool query_pool_ = {}; + + size_t current_index_ = 0u; + // The number of nanoseconds for each timestamp unit. + float timestamp_period_ = 1; + bool started_frame_ = false; + bool valid_ = false; +}; + +} // namespace impeller diff --git a/impeller/renderer/backend/vulkan/swapchain_impl_vk.cc b/impeller/renderer/backend/vulkan/swapchain_impl_vk.cc index dcd5d1c50b593..351ac2a1c9352 100644 --- a/impeller/renderer/backend/vulkan/swapchain_impl_vk.cc +++ b/impeller/renderer/backend/vulkan/swapchain_impl_vk.cc @@ -4,12 +4,15 @@ #include "impeller/renderer/backend/vulkan/swapchain_impl_vk.h" +#include "impeller/base/validation.h" #include "impeller/renderer/backend/vulkan/command_buffer_vk.h" #include "impeller/renderer/backend/vulkan/command_encoder_vk.h" #include "impeller/renderer/backend/vulkan/context_vk.h" #include "impeller/renderer/backend/vulkan/formats_vk.h" +#include "impeller/renderer/backend/vulkan/gpu_tracer_vk.h" #include "impeller/renderer/backend/vulkan/surface_vk.h" #include "impeller/renderer/backend/vulkan/swapchain_image_vk.h" +#include "impeller/renderer/context.h" #include "vulkan/vulkan_structs.hpp" namespace impeller { @@ -376,6 +379,9 @@ SwapchainImplVK::AcquireResult SwapchainImplVK::AcquireNextDrawable() { nullptr // fence ); + /// Record the approximate start of the GPU workload. + context.GetGPUTracer()->RecordStartFrameTime(); + switch (acq_result) { case vk::Result::eSuccess: // Keep going. @@ -451,6 +457,9 @@ bool SwapchainImplVK::Present(const std::shared_ptr& image, } } + /// Record the approximate end of the GPU workload. + context.GetGPUTracer()->RecordEndFrameTime(); + //---------------------------------------------------------------------------- /// Signal that the presentation semaphore is ready. ///