| // Copyright 2019 The Chromium Authors | |
| // Use of this source code is governed by a BSD-style license that can be | |
| // found in the LICENSE file. | |
| #include "gpu/command_buffer/service/dawn_platform.h" | |
| #include <mutex> | |
| #include <string_view> | |
| #include "base/compiler_specific.h" | |
| #include "base/containers/fixed_flat_map.h" | |
| #include "base/metrics/histogram.h" | |
| #include "base/metrics/histogram_functions.h" | |
| #include "base/rand_util.h" | |
| #include "base/strings/strcat.h" | |
| #include "base/strings/string_util.h" | |
| #include "base/synchronization/waitable_event.h" | |
| #include "base/task/post_job.h" | |
| #include "base/task/thread_pool.h" | |
| #include "base/time/time.h" | |
| #include "base/trace_event/trace_event.h" | |
| #include "gpu/config/gpu_finch_features.h" | |
| #include "gpu/vulkan/buildflags.h" | |
| #include "ui/gl/progress_reporter.h" | |
| #if BUILDFLAG(ENABLE_VULKAN) | |
| #include "gpu/vulkan/vulkan_util.h" | |
| #endif | |
| namespace gpu::webgpu { | |
| namespace { | |
| class AsyncWaitableEventImpl | |
| : public base::RefCountedThreadSafe<AsyncWaitableEventImpl> { | |
| public: | |
| AsyncWaitableEventImpl() | |
| : waitable_event_(base::WaitableEvent::ResetPolicy::MANUAL, | |
| base::WaitableEvent::InitialState::NOT_SIGNALED) {} | |
| void Wait() { waitable_event_.Wait(); } | |
| bool IsComplete() { return waitable_event_.IsSignaled(); } | |
| void MarkAsComplete() { waitable_event_.Signal(); } | |
| private: | |
| friend class base::RefCountedThreadSafe<AsyncWaitableEventImpl>; | |
| ~AsyncWaitableEventImpl() = default; | |
| base::WaitableEvent waitable_event_; | |
| }; | |
| class AsyncWaitableEvent : public dawn::platform::WaitableEvent { | |
| public: | |
| AsyncWaitableEvent() | |
| : waitable_event_impl_(base::MakeRefCounted<AsyncWaitableEventImpl>()) {} | |
| ~AsyncWaitableEvent() override = default; | |
| void Wait() override { waitable_event_impl_->Wait(); } | |
| bool IsComplete() override { return waitable_event_impl_->IsComplete(); } | |
| scoped_refptr<AsyncWaitableEventImpl> GetWaitableEventImpl() const { | |
| return waitable_event_impl_; | |
| } | |
| private: | |
| scoped_refptr<AsyncWaitableEventImpl> waitable_event_impl_; | |
| }; | |
| class AsyncJobHandle : public dawn::platform::JobHandle { | |
| public: | |
| explicit AsyncJobHandle(base::JobHandle&& job_handle) | |
| : job_handle_(std::move(job_handle)) {} | |
| ~AsyncJobHandle() override = default; | |
| // Chromium's JobHandle can either be cancelled or joined, but not both (it | |
| // crashes if you try to |Join| after |Cancel|). The Dawn platform's JobHandle | |
| // allows both to be called without error, so we wrap it here and avoid | |
| // calling them more than once. | |
| void Cancel() override { | |
| std::call_once(once_flag_, [&]() { job_handle_.Cancel(); }); | |
| } | |
| void Join() override { | |
| std::call_once(once_flag_, [&]() { job_handle_.Join(); }); | |
| } | |
| private: | |
| std::once_flag once_flag_; | |
| base::JobHandle job_handle_; | |
| }; | |
| class AsyncWorkerTaskPool : public dawn::platform::WorkerTaskPool { | |
| public: | |
| explicit AsyncWorkerTaskPool(gl::ProgressReporter* progress_reporter) | |
| : progress_reporter_(progress_reporter) {} | |
| ~AsyncWorkerTaskPool() override = default; | |
| std::unique_ptr<dawn::platform::WaitableEvent> PostWorkerTask( | |
| dawn::platform::PostWorkerTaskCallback callback, | |
| void* user_data) override { | |
| std::unique_ptr<AsyncWaitableEvent> waitable_event = | |
| std::make_unique<AsyncWaitableEvent>(); | |
| base::ThreadPool::PostTask( | |
| FROM_HERE, {base::MayBlock(), base::TaskPriority::USER_BLOCKING}, | |
| base::BindOnce(&RunWorkerTask, callback, user_data, progress_reporter_, | |
| waitable_event->GetWaitableEventImpl())); | |
| return waitable_event; | |
| } | |
| std::unique_ptr<dawn::platform::JobHandle> PostWorkerJob( | |
| dawn::platform::PostWorkerJobCallback callback, | |
| void* user_data) override { | |
| std::unique_ptr<AsyncJobHandle> job_handle = | |
| std::make_unique<AsyncJobHandle>(base::PostJob( | |
| FROM_HERE, {base::TaskPriority::USER_VISIBLE}, | |
| base::BindRepeating(&RunWorkerJob, callback, user_data), | |
| base::BindRepeating([](size_t) -> size_t { return 1; }))); | |
| return job_handle; | |
| } | |
| private: | |
| static void RunWorkerTask( | |
| dawn::platform::PostWorkerTaskCallback callback, | |
| void* user_data, | |
| gl::ProgressReporter* progress_reporter, | |
| scoped_refptr<AsyncWaitableEventImpl> waitable_event_impl) { | |
| TRACE_EVENT0("toplevel", "DawnPlatformImpl::RunWorkerTask"); | |
| callback(user_data); | |
| if (progress_reporter) { | |
| progress_reporter->ReportProgress(); | |
| } | |
| waitable_event_impl->MarkAsComplete(); | |
| } | |
| static void RunWorkerJob(dawn::platform::PostWorkerJobCallback callback, | |
| void* user_data, | |
| base::JobDelegate* delegate) { | |
| dawn::platform::JobStatus status = dawn::platform::JobStatus::Continue; | |
| while (!delegate->ShouldYield()) { | |
| if (status != dawn::platform::JobStatus::Continue) { | |
| return; | |
| } | |
| status = callback(user_data); | |
| } | |
| } | |
| const raw_ptr<gl::ProgressReporter> progress_reporter_; | |
| }; | |
| void RecordDelayedUMA(scoped_refptr<DawnPlatform::CacheCountsMap> cache_map, | |
| std::string uma_prefix) { | |
| base::AutoLock autolock(cache_map->lock); | |
| for (auto const& [base_name, cache_counts] : cache_map->counts) { | |
| // Report Hit counts | |
| base::UmaHistogramCounts10000( | |
| uma_prefix + base_name + "CacheHit.Counts.90SecondsPostStartup", | |
| cache_counts.cache_hit_count); | |
| // Report Miss counts | |
| base::UmaHistogramCounts10000( | |
| uma_prefix + base_name + "CacheMiss.Counts.90SecondsPostStartup", | |
| cache_counts.cache_miss_count); | |
| // Report Percentage | |
| int total_counts = | |
| cache_counts.cache_hit_count + cache_counts.cache_miss_count; | |
| if (total_counts > 0) { | |
| int hit_percentage = (cache_counts.cache_hit_count * 100) / total_counts; | |
| base::UmaHistogramPercentage( | |
| uma_prefix + base_name + "CacheHit.Percentage.90SecondsPostStartup", | |
| hit_percentage); | |
| } | |
| } | |
| } | |
| // Some metrics are subsampled as they are on critical path. | |
| bool ShouldRecordMetric(std::string_view name) { | |
| static constexpr double kSubsamplingProb = 0.01; | |
| return name == "Vulkan.VkQueueSubmitUS" | |
| ? base::ShouldRecordSubsampledMetric(kSubsamplingProb) | |
| : true; | |
| } | |
| #if BUILDFLAG(ENABLE_VULKAN) && BUILDFLAG(IS_ANDROID) | |
| // A collection for which a unified metric is emitted for Ganesh/Graphite Vulkan | |
| // backends. | |
| constexpr auto kUnifiedSkiaMetrics = | |
| base::MakeFixedFlatMap<std::string_view, void (*)(base::TimeDelta)>( | |
| // Also emitted from CreateGraphicsPipelinesHook with same name. | |
| {{"Vulkan.CreateGraphicsPipelines.CacheHit", | |
| &gpu::EmitVkCreateGraphicsPipelinesUMA}, | |
| {"Vulkan.CreateGraphicsPipelines.CacheMiss", | |
| &gpu::EmitVkCreateGraphicsPipelinesUMA}, | |
| {"Vulkan.VkQueueSubmitUS", &gpu::EmitVkQueueSubmitUMA}}); | |
| bool ShouldEmitUnifiedHistogram(const std::string& uma_prefix, | |
| const char* name) { | |
| if (uma_prefix == "GPU.GraphiteDawn." && kUnifiedSkiaMetrics.contains(name)) { | |
| return true; | |
| } | |
| return false; | |
| } | |
| #endif // BUILDFLAG(ENABLE_VULKAN) && BUILDFLAG(IS_ANDROID) | |
| void EmitUnifiedHistogram(const std::string& uma_prefix, | |
| const char* name, | |
| int sample) { | |
| #if BUILDFLAG(ENABLE_VULKAN) && BUILDFLAG(IS_ANDROID) | |
| if (!ShouldEmitUnifiedHistogram(uma_prefix, name)) { | |
| return; | |
| } | |
| kUnifiedSkiaMetrics.at(name)(base::Microseconds(sample)); | |
| #endif // BUILDFLAG(ENABLE_VULKAN) && BUILDFLAG(IS_ANDROID) | |
| } | |
| } // anonymous namespace | |
| DawnPlatform::CacheCountsMap::CacheCountsMap() = default; | |
| DawnPlatform::CacheCountsMap::~CacheCountsMap() = default; | |
| DawnPlatform::DawnPlatform(gl::ProgressReporter* progress_reporter, | |
| const char* uma_prefix, | |
| bool record_cache_count_uma) | |
| : progress_reporter_(progress_reporter), | |
| uma_prefix_(uma_prefix), | |
| cache_map_(base::MakeRefCounted<CacheCountsMap>()), | |
| startup_time_(base::TimeTicks::Now()) { | |
| if (record_cache_count_uma) { | |
| base::ThreadPool::PostDelayedTask( | |
| FROM_HERE, base::BindOnce(&RecordDelayedUMA, cache_map_, uma_prefix_), | |
| base::Seconds(90)); | |
| } | |
| } | |
| DawnPlatform::~DawnPlatform() = default; | |
| double DawnPlatform::MonotonicallyIncreasingTime() { | |
| return (base::TimeTicks::Now() - base::TimeTicks()).InSecondsF(); | |
| } | |
| void DawnPlatform::HistogramCacheCountHelper(std::string_view name, | |
| int sample, | |
| int min, | |
| int max, | |
| int bucketCount) { | |
| if (name.find("Cache") != std::string_view::npos) { | |
| base::AutoLock autolock(cache_map_->lock); | |
| std::string_view base_name = name; | |
| bool is_hit = false; | |
| size_t pos = base_name.find("CacheHit"); | |
| if (pos != std::string_view::npos) { | |
| base_name = base_name.substr(0, pos); | |
| is_hit = true; | |
| } else { | |
| pos = base_name.find("CacheMiss"); | |
| if (pos != std::string_view::npos) { | |
| base_name = base_name.substr(0, pos); | |
| } | |
| } | |
| auto& cache_counts = cache_map_->counts[base_name]; | |
| if (is_hit) { | |
| ++cache_counts.cache_hit_count; | |
| } else { | |
| ++cache_counts.cache_miss_count; | |
| } | |
| if (base::TimeTicks::Now() - startup_time_ <= base::Seconds(90)) { | |
| base::UmaHistogramCustomCounts( | |
| base::StrCat({uma_prefix_, name, ".90SecondsPostStartup"}), sample, | |
| min, max, bucketCount); | |
| } | |
| } | |
| } | |
| void DawnPlatform::HistogramCustomCounts(const char* name, | |
| int sample, | |
| int min, | |
| int max, | |
| int bucketCount) { | |
| base::UmaHistogramCustomCounts(uma_prefix_ + name, sample, min, max, | |
| bucketCount); | |
| HistogramCacheCountHelper(name, sample, min, max, bucketCount); | |
| EmitUnifiedHistogram(uma_prefix_, name, sample); | |
| } | |
| void DawnPlatform::HistogramCustomCountsHPC(const char* name, | |
| int sample, | |
| int min, | |
| int max, | |
| int bucketCount) { | |
| if (base::TimeTicks::IsHighResolution()) { | |
| if (ShouldRecordMetric(name)) { | |
| base::UmaHistogramCustomCounts(uma_prefix_ + name, sample, min, max, | |
| bucketCount); | |
| HistogramCacheCountHelper(name, sample, min, max, bucketCount); | |
| } | |
| EmitUnifiedHistogram(uma_prefix_, name, sample); | |
| } | |
| } | |
| void DawnPlatform::HistogramEnumeration(const char* name, | |
| int sample, | |
| int boundaryValue) { | |
| base::UmaHistogramExactLinear(uma_prefix_ + name, sample, boundaryValue); | |
| } | |
| void DawnPlatform::HistogramSparse(const char* name, int sample) { | |
| base::UmaHistogramSparse(uma_prefix_ + name, sample); | |
| } | |
| void DawnPlatform::HistogramBoolean(const char* name, bool sample) { | |
| base::UmaHistogramBoolean(uma_prefix_ + name, sample); | |
| } | |
| std::unique_ptr<dawn::platform::WorkerTaskPool> | |
| DawnPlatform::CreateWorkerTaskPool() { | |
| return std::make_unique<AsyncWorkerTaskPool>(progress_reporter_); | |
| } | |
| bool DawnPlatform::IsFeatureEnabled(dawn::platform::Features feature) { | |
| switch (feature) { | |
| case dawn::platform::Features::kWebGPUUseDXC: | |
| // (crbug.com/362247692): Feature is enabled by default. | |
| return true; | |
| case dawn::platform::Features::kWebGPUEnableRangeAnalysisForRobustness: | |
| return base::FeatureList::IsEnabled( | |
| features::kWebGPUEnableRangeAnalysisForRobustness); | |
| case dawn::platform::Features::kWebGPUUseSpirv14: | |
| return base::FeatureList::IsEnabled(features::kWebGPUUseSpirv14); | |
| case dawn::platform::Features::kWebGPUUseHLSL2021: | |
| return base::FeatureList::IsEnabled(features::kWebGPUUseHLSL2021); | |
| default: | |
| return false; | |
| } | |
| } | |
| void DawnPlatform::ReportProgress() { | |
| if (progress_reporter_) { | |
| progress_reporter_->ReportProgress(); | |
| } | |
| } | |
| void DawnPlatform::OnFramePresented() { | |
| // Report cache hits' stats but only for first presentation. | |
| base::AutoLock autolock(cache_map_->lock); | |
| if (did_report_1st_present_cache_stats_) { | |
| return; | |
| } | |
| did_report_1st_present_cache_stats_ = true; | |
| for (auto const& [base_name, cache_counts] : cache_map_->counts) { | |
| // Report Hit counts | |
| base::UmaHistogramCounts10000( | |
| uma_prefix_ + base_name + "CacheHit.Counts.1stPresent", | |
| cache_counts.cache_hit_count); | |
| // Report Percentage | |
| int total_counts = | |
| cache_counts.cache_hit_count + cache_counts.cache_miss_count; | |
| if (total_counts > 0) { | |
| int hit_percentage = (cache_counts.cache_hit_count * 100) / total_counts; | |
| base::UmaHistogramPercentage( | |
| uma_prefix_ + base_name + "CacheHit.Percentage.1stPresent", | |
| hit_percentage); | |
| } | |
| } | |
| } | |
| } // namespace gpu::webgpu |