Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Commit 1d37b96

Browse files
Jonah Williamsharryterkelsen
authored andcommitted
[Impeller] GPUTracer for Metal. (#46846)
Impelement GPU frame time tracing for metal. Unlike Vulkan, this can't use dummy cmd buffers as those don't seem to ever get an executed callback. Instead we decorate every submitted cmd buffer with tracing functionality, and min/max the timestamps to compute the range of computation time.
1 parent 5e65f76 commit 1d37b96

File tree

8 files changed

+146
-1
lines changed

8 files changed

+146
-1
lines changed

ci/licenses_golden/licenses_flutter

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2128,6 +2128,8 @@ ORIGIN: ../../../flutter/impeller/renderer/backend/metal/device_buffer_mtl.h + .
21282128
ORIGIN: ../../../flutter/impeller/renderer/backend/metal/device_buffer_mtl.mm + ../../../flutter/LICENSE
21292129
ORIGIN: ../../../flutter/impeller/renderer/backend/metal/formats_mtl.h + ../../../flutter/LICENSE
21302130
ORIGIN: ../../../flutter/impeller/renderer/backend/metal/formats_mtl.mm + ../../../flutter/LICENSE
2131+
ORIGIN: ../../../flutter/impeller/renderer/backend/metal/gpu_tracer_mtl.h + ../../../flutter/LICENSE
2132+
ORIGIN: ../../../flutter/impeller/renderer/backend/metal/gpu_tracer_mtl.mm + ../../../flutter/LICENSE
21312133
ORIGIN: ../../../flutter/impeller/renderer/backend/metal/pipeline_library_mtl.h + ../../../flutter/LICENSE
21322134
ORIGIN: ../../../flutter/impeller/renderer/backend/metal/pipeline_library_mtl.mm + ../../../flutter/LICENSE
21332135
ORIGIN: ../../../flutter/impeller/renderer/backend/metal/pipeline_mtl.h + ../../../flutter/LICENSE
@@ -4895,6 +4897,8 @@ FILE: ../../../flutter/impeller/renderer/backend/metal/device_buffer_mtl.h
48954897
FILE: ../../../flutter/impeller/renderer/backend/metal/device_buffer_mtl.mm
48964898
FILE: ../../../flutter/impeller/renderer/backend/metal/formats_mtl.h
48974899
FILE: ../../../flutter/impeller/renderer/backend/metal/formats_mtl.mm
4900+
FILE: ../../../flutter/impeller/renderer/backend/metal/gpu_tracer_mtl.h
4901+
FILE: ../../../flutter/impeller/renderer/backend/metal/gpu_tracer_mtl.mm
48984902
FILE: ../../../flutter/impeller/renderer/backend/metal/pipeline_library_mtl.h
48994903
FILE: ../../../flutter/impeller/renderer/backend/metal/pipeline_library_mtl.mm
49004904
FILE: ../../../flutter/impeller/renderer/backend/metal/pipeline_mtl.h

impeller/renderer/backend/metal/BUILD.gn

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ impeller_component("metal") {
2424
"device_buffer_mtl.mm",
2525
"formats_mtl.h",
2626
"formats_mtl.mm",
27+
"gpu_tracer_mtl.h",
28+
"gpu_tracer_mtl.mm",
2729
"pipeline_library_mtl.h",
2830
"pipeline_library_mtl.mm",
2931
"pipeline_mtl.h",

impeller/renderer/backend/metal/command_buffer_mtl.mm

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,13 @@ static bool LogMTLCommandBufferErrorIfPresent(id<MTLCommandBuffer> buffer) {
159159
}
160160

161161
bool CommandBufferMTL::OnSubmitCommands(CompletionCallback callback) {
162+
auto context = context_.lock();
163+
if (!context) {
164+
return false;
165+
}
166+
#ifdef IMPELLER_DEBUG
167+
ContextMTL::Cast(*context).GetGPUTracer()->RecordCmdBuffer(buffer_);
168+
#endif // IMPELLER_DEBUG
162169
if (callback) {
163170
[buffer_
164171
addCompletedHandler:^(id<MTLCommandBuffer> buffer) {
@@ -190,6 +197,10 @@ static bool LogMTLCommandBufferErrorIfPresent(id<MTLCommandBuffer> buffer) {
190197
auto buffer = buffer_;
191198
buffer_ = nil;
192199

200+
#ifdef IMPELLER_DEBUG
201+
ContextMTL::Cast(*context).GetGPUTracer()->RecordCmdBuffer(buffer);
202+
#endif // IMPELLER_DEBUG
203+
193204
auto worker_task_runner = ContextMTL::Cast(*context).GetWorkerTaskRunner();
194205
auto mtl_render_pass = static_cast<RenderPassMTL*>(render_pass.get());
195206

impeller/renderer/backend/metal/context_mtl.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
#include "impeller/core/sampler.h"
1717
#include "impeller/renderer/backend/metal/allocator_mtl.h"
1818
#include "impeller/renderer/backend/metal/command_buffer_mtl.h"
19+
#include "impeller/renderer/backend/metal/gpu_tracer_mtl.h"
1920
#include "impeller/renderer/backend/metal/pipeline_library_mtl.h"
2021
#include "impeller/renderer/backend/metal/shader_library_mtl.h"
2122
#include "impeller/renderer/capabilities.h"
@@ -93,6 +94,10 @@ class ContextMTL final : public Context,
9394

9495
std::shared_ptr<const fml::SyncSwitch> GetIsGpuDisabledSyncSwitch() const;
9596

97+
#ifdef IMPELLER_DEBUG
98+
std::shared_ptr<GPUTracerMTL> GetGPUTracer() const;
99+
#endif // IMPELLER_DEBUG
100+
96101
// |Context|
97102
void StoreTaskForGPU(std::function<void()> task) override;
98103

@@ -116,6 +121,9 @@ class ContextMTL final : public Context,
116121
std::shared_ptr<const Capabilities> device_capabilities_;
117122
std::shared_ptr<fml::ConcurrentMessageLoop> raster_message_loop_;
118123
std::shared_ptr<const fml::SyncSwitch> is_gpu_disabled_sync_switch_;
124+
#ifdef IMPELLER_DEBUG
125+
std::shared_ptr<GPUTracerMTL> gpu_tracer_;
126+
#endif // IMPELLER_DEBUG
119127
std::deque<std::function<void()>> tasks_awaiting_gpu_;
120128
std::unique_ptr<SyncSwitchObserver> sync_switch_observer_;
121129
bool is_valid_ = false;

impeller/renderer/backend/metal/context_mtl.mm

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,15 @@
55
#include "impeller/renderer/backend/metal/context_mtl.h"
66

77
#include <Foundation/Foundation.h>
8+
#include <memory>
89

910
#include "flutter/fml/concurrent_message_loop.h"
1011
#include "flutter/fml/file.h"
1112
#include "flutter/fml/logging.h"
1213
#include "flutter/fml/paths.h"
1314
#include "flutter/fml/synchronization/sync_switch.h"
1415
#include "impeller/core/sampler_descriptor.h"
16+
#include "impeller/renderer/backend/metal/gpu_tracer_mtl.h"
1517
#include "impeller/renderer/backend/metal/sampler_library_mtl.h"
1618
#include "impeller/renderer/capabilities.h"
1719

@@ -145,7 +147,9 @@ static bool DeviceSupportsComputeSubgroups(id<MTLDevice> device) {
145147

146148
device_capabilities_ =
147149
InferMetalCapabilities(device_, PixelFormat::kB8G8R8A8UNormInt);
148-
150+
#ifdef IMPELLER_DEBUG
151+
gpu_tracer_ = std::make_shared<GPUTracerMTL>();
152+
#endif // IMPELLER_DEBUG
149153
is_valid_ = true;
150154
}
151155

@@ -330,6 +334,12 @@ new ContextMTL(device, command_queue,
330334
raster_message_loop_.reset();
331335
}
332336

337+
#ifdef IMPELLER_DEBUG
338+
std::shared_ptr<GPUTracerMTL> ContextMTL::GetGPUTracer() const {
339+
return gpu_tracer_;
340+
}
341+
#endif // IMPELLER_DEBUG
342+
333343
const std::shared_ptr<fml::ConcurrentTaskRunner>
334344
ContextMTL::GetWorkerTaskRunner() const {
335345
return raster_message_loop_->GetTaskRunner();
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
// Copyright 2013 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
#pragma once
6+
7+
#include <Metal/Metal.h>
8+
9+
#include <memory>
10+
#include <optional>
11+
#include "impeller/base/thread.h"
12+
#include "impeller/base/thread_safety.h"
13+
#include "impeller/geometry/scalar.h"
14+
15+
namespace impeller {
16+
17+
class ContextMTL;
18+
19+
/// @brief Approximate the GPU frame time by computing a difference between the
20+
/// smallest
21+
/// GPUStartTime and largest GPUEndTime for all cmd buffers submitted in
22+
/// a frame workload.
23+
class GPUTracerMTL : public std::enable_shared_from_this<GPUTracerMTL> {
24+
public:
25+
GPUTracerMTL() = default;
26+
27+
~GPUTracerMTL() = default;
28+
29+
/// @brief Record that the current frame has ended. Any additional cmd buffers
30+
/// will be
31+
/// attributed to the "next" frame.
32+
void MarkFrameEnd();
33+
34+
/// @brief Record the current cmd buffer GPU execution timestamps into an
35+
/// aggregate
36+
/// frame workload metric.
37+
void RecordCmdBuffer(id<MTLCommandBuffer> buffer);
38+
39+
private:
40+
struct GPUTraceState {
41+
Scalar smallest_timestamp = std::numeric_limits<float>::max();
42+
Scalar largest_timestamp = 0;
43+
size_t pending_buffers = 0;
44+
};
45+
46+
mutable Mutex trace_state_mutex_;
47+
GPUTraceState trace_states_[16] IPLR_GUARDED_BY(trace_state_mutex_);
48+
size_t current_state_ IPLR_GUARDED_BY(trace_state_mutex_) = 0u;
49+
};
50+
51+
} // namespace impeller
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
// Copyright 2013 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
#include <Metal/Metal.h>
6+
#include "fml/trace_event.h"
7+
#include "impeller/renderer/backend/metal/context_mtl.h"
8+
#include "impeller/renderer/backend/metal/formats_mtl.h"
9+
10+
#include <memory>
11+
12+
#include "impeller/renderer/backend/metal/gpu_tracer_mtl.h"
13+
14+
namespace impeller {
15+
16+
void GPUTracerMTL::MarkFrameEnd() {
17+
if (@available(ios 10.3, tvos 10.2, macos 10.15, macCatalyst 13.0, *)) {
18+
Lock lock(trace_state_mutex_);
19+
current_state_ = (current_state_ + 1) % 16;
20+
}
21+
}
22+
23+
void GPUTracerMTL::RecordCmdBuffer(id<MTLCommandBuffer> buffer) {
24+
if (@available(ios 10.3, tvos 10.2, macos 10.15, macCatalyst 13.0, *)) {
25+
Lock lock(trace_state_mutex_);
26+
auto current_state = current_state_;
27+
trace_states_[current_state].pending_buffers += 1;
28+
29+
auto weak_self = weak_from_this();
30+
[buffer addCompletedHandler:^(id<MTLCommandBuffer> buffer) {
31+
auto self = weak_self.lock();
32+
if (!self) {
33+
return;
34+
}
35+
Lock lock(self->trace_state_mutex_);
36+
auto& state = self->trace_states_[current_state];
37+
state.pending_buffers--;
38+
state.smallest_timestamp = std::min(
39+
state.smallest_timestamp, static_cast<Scalar>(buffer.GPUStartTime));
40+
state.largest_timestamp = std::max(
41+
state.largest_timestamp, static_cast<Scalar>(buffer.GPUEndTime));
42+
43+
if (state.pending_buffers == 0) {
44+
auto gpu_ms =
45+
(state.largest_timestamp - state.smallest_timestamp) * 1000;
46+
state.smallest_timestamp = std::numeric_limits<float>::max();
47+
state.largest_timestamp = 0;
48+
FML_TRACE_COUNTER("flutter", "GPUTracer",
49+
reinterpret_cast<int64_t>(this), // Trace Counter ID
50+
"FrameTimeMS", gpu_ms);
51+
}
52+
}];
53+
}
54+
}
55+
56+
} // namespace impeller

impeller/renderer/backend/metal/surface_mtl.mm

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,9 @@
246246
return false;
247247
}
248248
}
249+
#ifdef IMPELLER_DEBUG
250+
ContextMTL::Cast(context.get())->GetGPUTracer()->MarkFrameEnd();
251+
#endif // IMPELLER_DEBUG
249252

250253
if (drawable_) {
251254
id<MTLCommandBuffer> command_buffer =

0 commit comments

Comments
 (0)