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

Commit 0d1da88

Browse files
author
Jonah Williams
authored
Merge branch 'main' into gpu_tracing_metal
2 parents 0d3b630 + c24f303 commit 0d1da88

File tree

13 files changed

+283
-28
lines changed

13 files changed

+283
-28
lines changed

DEPS

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ vars = {
1818
'llvm_git': 'https://llvm.googlesource.com',
1919
# OCMock is for testing only so there is no google clone
2020
'ocmock_git': 'https://github.com/erikdoe/ocmock.git',
21-
'skia_revision': '675f088b9ac40837541db9ee05b17c702097a72b',
21+
'skia_revision': '68de6e3525856f9475eb27d3fc808c7e339eead1',
2222

2323
# WARNING: DO NOT EDIT canvaskit_cipd_instance MANUALLY
2424
# See `lib/web_ui/README.md` for how to roll CanvasKit to a new version.

ci/licenses_golden/licenses_skia

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
Signature: 95b229c06464b5b2380d14193495ab12
1+
Signature: 717f85ae89c540094a7fba29a8df7720
22

33
====================================================================================================
44
LIBRARY: etc1
@@ -8749,6 +8749,7 @@ ORIGIN: ../../../third_party/skia/include/core/SkColorTable.h + ../../../third_p
87498749
ORIGIN: ../../../third_party/skia/include/core/SkPoint.h + ../../../third_party/skia/LICENSE
87508750
ORIGIN: ../../../third_party/skia/include/core/SkTextureCompressionType.h + ../../../third_party/skia/LICENSE
87518751
ORIGIN: ../../../third_party/skia/include/core/SkTiledImageUtils.h + ../../../third_party/skia/LICENSE
8752+
ORIGIN: ../../../third_party/skia/include/docs/SkMultiPictureDocument.h + ../../../third_party/skia/LICENSE
87528753
ORIGIN: ../../../third_party/skia/include/gpu/ganesh/GrExternalTextureGenerator.h + ../../../third_party/skia/LICENSE
87538754
ORIGIN: ../../../third_party/skia/include/gpu/ganesh/SkImageGanesh.h + ../../../third_party/skia/LICENSE
87548755
ORIGIN: ../../../third_party/skia/include/gpu/ganesh/SkMeshGanesh.h + ../../../third_party/skia/LICENSE
@@ -9013,6 +9014,7 @@ FILE: ../../../third_party/skia/include/core/SkColorTable.h
90139014
FILE: ../../../third_party/skia/include/core/SkPoint.h
90149015
FILE: ../../../third_party/skia/include/core/SkTextureCompressionType.h
90159016
FILE: ../../../third_party/skia/include/core/SkTiledImageUtils.h
9017+
FILE: ../../../third_party/skia/include/docs/SkMultiPictureDocument.h
90169018
FILE: ../../../third_party/skia/include/gpu/ganesh/GrExternalTextureGenerator.h
90179019
FILE: ../../../third_party/skia/include/gpu/ganesh/SkImageGanesh.h
90189020
FILE: ../../../third_party/skia/include/gpu/ganesh/SkMeshGanesh.h

fml/synchronization/sync_switch.cc

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,26 @@ void SyncSwitch::Execute(const SyncSwitch::Handlers& handlers) const {
3232
}
3333

3434
void SyncSwitch::SetSwitch(bool value) {
35+
{
36+
fml::UniqueLock lock(*mutex_);
37+
value_ = value;
38+
}
39+
for (Observer* observer : observers_) {
40+
observer->OnSyncSwitchUpdate(value);
41+
}
42+
}
43+
44+
void SyncSwitch::AddObserver(Observer* observer) const {
3545
fml::UniqueLock lock(*mutex_);
36-
value_ = value;
46+
if (std::find(observers_.begin(), observers_.end(), observer) ==
47+
observers_.end()) {
48+
observers_.push_back(observer);
49+
}
3750
}
3851

52+
void SyncSwitch::RemoveObserver(Observer* observer) const {
53+
fml::UniqueLock lock(*mutex_);
54+
observers_.erase(std::remove(observers_.begin(), observers_.end(), observer),
55+
observers_.end());
56+
}
3957
} // namespace fml

fml/synchronization/sync_switch.h

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,16 @@ namespace fml {
2121
/// at a time.
2222
class SyncSwitch {
2323
public:
24+
/// Observes changes to the SyncSwitch.
25+
class Observer {
26+
public:
27+
virtual ~Observer() = default;
28+
/// `new_is_disabled` not guaranteed to be the value of the SyncSwitch
29+
/// during execution, it should be checked with calls to
30+
/// SyncSwitch::Execute.
31+
virtual void OnSyncSwitchUpdate(bool new_is_disabled) = 0;
32+
};
33+
2434
/// Represents the 2 code paths available when calling |SyncSwitch::Execute|.
2535
struct Handlers {
2636
/// Sets the handler that will be executed if the |SyncSwitch| is true.
@@ -53,8 +63,15 @@ class SyncSwitch {
5363
/// @param[in] value New value for the |SyncSwitch|.
5464
void SetSwitch(bool value);
5565

66+
/// Threadsafe.
67+
void AddObserver(Observer* observer) const;
68+
69+
/// Threadsafe.
70+
void RemoveObserver(Observer* observer) const;
71+
5672
private:
5773
mutable std::unique_ptr<fml::SharedMutex> mutex_;
74+
mutable std::vector<Observer*> observers_;
5875
bool value_;
5976

6077
FML_DISALLOW_COPY_AND_ASSIGN(SyncSwitch);

impeller/renderer/backend/metal/context_mtl.h

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@
66

77
#include <Metal/Metal.h>
88

9+
#include <deque>
910
#include <string>
10-
#include <vector>
1111

1212
#include "flutter/fml/concurrent_message_loop.h"
1313
#include "flutter/fml/macros.h"
@@ -98,7 +98,20 @@ class ContextMTL final : public Context,
9898
std::shared_ptr<GPUTracerMTL> GetGPUTracer() const;
9999
#endif // IMPELLER_DEBUG
100100

101+
// |Context|
102+
void StoreTaskForGPU(std::function<void()> task) override;
103+
101104
private:
105+
class SyncSwitchObserver : public fml::SyncSwitch::Observer {
106+
public:
107+
SyncSwitchObserver(ContextMTL& parent);
108+
virtual ~SyncSwitchObserver() = default;
109+
void OnSyncSwitchUpdate(bool new_value) override;
110+
111+
private:
112+
ContextMTL& parent_;
113+
};
114+
102115
id<MTLDevice> device_ = nullptr;
103116
id<MTLCommandQueue> command_queue_ = nullptr;
104117
std::shared_ptr<ShaderLibraryMTL> shader_library_;
@@ -111,6 +124,8 @@ class ContextMTL final : public Context,
111124
#ifdef IMPELLER_DEBUG
112125
std::shared_ptr<GPUTracerMTL> gpu_tracer_;
113126
#endif // IMPELLER_DEBUG
127+
std::deque<std::function<void()>> tasks_awaiting_gpu_;
128+
std::unique_ptr<SyncSwitchObserver> sync_switch_observer_;
114129
bool is_valid_ = false;
115130

116131
ContextMTL(
@@ -122,6 +137,8 @@ class ContextMTL final : public Context,
122137
std::shared_ptr<CommandBuffer> CreateCommandBufferInQueue(
123138
id<MTLCommandQueue> queue) const;
124139

140+
void FlushTasksAwaitingGPU();
141+
125142
FML_DISALLOW_COPY_AND_ASSIGN(ContextMTL);
126143
};
127144

impeller/renderer/backend/metal/context_mtl.mm

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,9 @@ static bool DeviceSupportsComputeSubgroups(id<MTLDevice> device) {
8585
return;
8686
}
8787

88+
sync_switch_observer_.reset(new SyncSwitchObserver(*this));
89+
is_gpu_disabled_sync_switch_->AddObserver(sync_switch_observer_.get());
90+
8891
// Worker task runner.
8992
{
9093
raster_message_loop_ = fml::ConcurrentMessageLoop::Create(
@@ -288,7 +291,9 @@ new ContextMTL(device, command_queue,
288291
return context;
289292
}
290293

291-
ContextMTL::~ContextMTL() = default;
294+
ContextMTL::~ContextMTL() {
295+
is_gpu_disabled_sync_switch_->RemoveObserver(sync_switch_observer_.get());
296+
}
292297

293298
Context::BackendType ContextMTL::GetBackendType() const {
294299
return Context::BackendType::kMetal;
@@ -386,4 +391,28 @@ new ContextMTL(device, command_queue,
386391
return buffer;
387392
}
388393

394+
void ContextMTL::StoreTaskForGPU(std::function<void()> task) {
395+
tasks_awaiting_gpu_.emplace_back(std::move(task));
396+
while (tasks_awaiting_gpu_.size() > kMaxTasksAwaitingGPU) {
397+
tasks_awaiting_gpu_.front()();
398+
tasks_awaiting_gpu_.pop_front();
399+
}
400+
}
401+
402+
void ContextMTL::FlushTasksAwaitingGPU() {
403+
for (const auto& task : tasks_awaiting_gpu_) {
404+
task();
405+
}
406+
tasks_awaiting_gpu_.clear();
407+
}
408+
409+
ContextMTL::SyncSwitchObserver::SyncSwitchObserver(ContextMTL& parent)
410+
: parent_(parent) {}
411+
412+
void ContextMTL::SyncSwitchObserver::OnSyncSwitchUpdate(bool new_is_disabled) {
413+
if (!new_is_disabled) {
414+
parent_.FlushTasksAwaitingGPU();
415+
}
416+
}
417+
389418
} // namespace impeller

impeller/renderer/context.h

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,14 @@ class Context {
5252
kVulkan,
5353
};
5454

55+
/// The maximum number of tasks that should ever be stored for
56+
/// `StoreTaskForGPU`.
57+
///
58+
/// This number was arbitrarily chosen. The idea is that this is a somewhat
59+
/// rare situation where tasks happen to get executed in that tiny amount of
60+
/// time while an app is being backgrounded but still executing.
61+
static constexpr int32_t kMaxTasksAwaitingGPU = 10;
62+
5563
//----------------------------------------------------------------------------
5664
/// @brief Destroys an Impeller context.
5765
///
@@ -176,6 +184,19 @@ class Context {
176184

177185
CaptureContext capture;
178186

187+
/// Stores a task on the `ContextMTL` that is awaiting access for the GPU.
188+
///
189+
/// The task will be executed in the event that the GPU access has changed to
190+
/// being available or that the task has been canceled. The task should
191+
/// operate with the `SyncSwitch` to make sure the GPU is accessible.
192+
///
193+
/// Threadsafe.
194+
///
195+
/// `task` will be executed on the platform thread.
196+
virtual void StoreTaskForGPU(std::function<void()> task) {
197+
FML_CHECK(false && "not supported in this context");
198+
}
199+
179200
protected:
180201
Context();
181202

lib/ui/fixtures/ui_test.dart

Lines changed: 36 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -325,7 +325,11 @@ external void _validateExternal(Uint8List result);
325325
@pragma('vm:external-name', 'ValidateError')
326326
external void _validateError(String? error);
327327
@pragma('vm:external-name', 'TurnOffGPU')
328-
external void _turnOffGPU();
328+
external void _turnOffGPU(bool value);
329+
@pragma('vm:external-name', 'FlushGpuAwaitingTasks')
330+
external void _flushGpuAwaitingTasks();
331+
@pragma('vm:external-name', 'ValidateNotNull')
332+
external void _validateNotNull(Object? object);
329333

330334
@pragma('vm:entry-point')
331335
Future<void> toByteDataWithoutGPU() async {
@@ -338,12 +342,40 @@ Future<void> toByteDataWithoutGPU() async {
338342
canvas.drawCircle(c, 25.0, paint);
339343
final Picture picture = pictureRecorder.endRecording();
340344
final Image image = await picture.toImage(100, 100);
341-
_turnOffGPU();
345+
_turnOffGPU(true);
346+
Timer flusher = Timer.periodic(Duration(milliseconds: 1), (timer) {
347+
_flushGpuAwaitingTasks();
348+
});
342349
try {
343350
ByteData? byteData = await image.toByteData();
344351
_validateError(null);
345-
} catch (ex) {
346-
_validateError(ex.toString());
352+
} catch (error) {
353+
_validateError(error.toString());
354+
} finally {
355+
flusher.cancel();
356+
}
357+
}
358+
359+
@pragma('vm:entry-point')
360+
Future<void> toByteDataRetries() async {
361+
final PictureRecorder pictureRecorder = PictureRecorder();
362+
final Canvas canvas = Canvas(pictureRecorder);
363+
final Paint paint = Paint()
364+
..color = Color.fromRGBO(255, 255, 255, 1.0)
365+
..style = PaintingStyle.fill;
366+
final Offset c = Offset(50.0, 50.0);
367+
canvas.drawCircle(c, 25.0, paint);
368+
final Picture picture = pictureRecorder.endRecording();
369+
final Image image = await picture.toImage(100, 100);
370+
_turnOffGPU(true);
371+
Future<void>.delayed(Duration(milliseconds: 10), () {
372+
_turnOffGPU(false);
373+
});
374+
try {
375+
ByteData? byteData = await image.toByteData();
376+
_validateNotNull(byteData);
377+
} catch (error) {
378+
_validateNotNull(null);
347379
}
348380
}
349381

lib/ui/painting/image_encoding_impeller.cc

Lines changed: 62 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -56,21 +56,68 @@ sk_sp<SkImage> ConvertBufferToSkImage(
5656
return raster_image;
5757
}
5858

59-
void DoConvertImageToRasterImpeller(
59+
[[nodiscard]] fml::Status DoConvertImageToRasterImpeller(
6060
const sk_sp<DlImage>& dl_image,
61-
std::function<void(fml::StatusOr<sk_sp<SkImage>>)> encode_task,
61+
const std::function<void(fml::StatusOr<sk_sp<SkImage>>)>& encode_task,
6262
const std::shared_ptr<const fml::SyncSwitch>& is_gpu_disabled_sync_switch,
6363
const std::shared_ptr<impeller::Context>& impeller_context) {
64+
fml::Status result;
6465
is_gpu_disabled_sync_switch->Execute(
6566
fml::SyncSwitch::Handlers()
66-
.SetIfTrue([&encode_task] {
67-
encode_task(
68-
fml::Status(fml::StatusCode::kUnavailable, "GPU unavailable."));
67+
.SetIfTrue([&result] {
68+
result =
69+
fml::Status(fml::StatusCode::kUnavailable, "GPU unavailable.");
6970
})
7071
.SetIfFalse([&dl_image, &encode_task, &impeller_context] {
7172
ImageEncodingImpeller::ConvertDlImageToSkImage(
72-
dl_image, std::move(encode_task), impeller_context);
73+
dl_image, encode_task, impeller_context);
7374
}));
75+
return result;
76+
}
77+
78+
/// Same as `DoConvertImageToRasterImpeller` but it will attempt to retry the
79+
/// operation if `DoConvertImageToRasterImpeller` returns kUnavailable when the
80+
/// GPU becomes available again.
81+
void DoConvertImageToRasterImpellerWithRetry(
82+
const sk_sp<DlImage>& dl_image,
83+
std::function<void(fml::StatusOr<sk_sp<SkImage>>)>&& encode_task,
84+
const std::shared_ptr<const fml::SyncSwitch>& is_gpu_disabled_sync_switch,
85+
const std::shared_ptr<impeller::Context>& impeller_context,
86+
const fml::RefPtr<fml::TaskRunner>& retry_runner) {
87+
fml::Status status = DoConvertImageToRasterImpeller(
88+
dl_image, encode_task, is_gpu_disabled_sync_switch, impeller_context);
89+
if (!status.ok()) {
90+
// If the conversion failed because of the GPU is unavailable, store the
91+
// task on the Context so it can be executed when the GPU becomes available.
92+
if (status.code() == fml::StatusCode::kUnavailable) {
93+
impeller_context->StoreTaskForGPU(
94+
[dl_image, encode_task = std::move(encode_task),
95+
is_gpu_disabled_sync_switch, impeller_context,
96+
retry_runner]() mutable {
97+
auto retry_task = [dl_image, encode_task = std::move(encode_task),
98+
is_gpu_disabled_sync_switch, impeller_context] {
99+
fml::Status retry_status = DoConvertImageToRasterImpeller(
100+
dl_image, encode_task, is_gpu_disabled_sync_switch,
101+
impeller_context);
102+
if (!retry_status.ok()) {
103+
// The retry failed for some reason, maybe the GPU became
104+
// unavailable again. Don't retry again, just fail in this case.
105+
encode_task(retry_status);
106+
}
107+
};
108+
// If a `retry_runner` is specified, post the retry to it, otherwise
109+
// execute it directly.
110+
if (retry_runner) {
111+
retry_runner->PostTask(retry_task);
112+
} else {
113+
retry_task();
114+
}
115+
});
116+
} else {
117+
// Pass on errors that are not `kUnavailable`.
118+
encode_task(status);
119+
}
120+
}
74121
}
75122

76123
} // namespace
@@ -153,18 +200,20 @@ void ImageEncodingImpeller::ConvertImageToRaster(
153200
};
154201

155202
if (dl_image->owning_context() != DlImage::OwningContext::kRaster) {
156-
DoConvertImageToRasterImpeller(dl_image, std::move(encode_task),
157-
is_gpu_disabled_sync_switch,
158-
impeller_context);
203+
DoConvertImageToRasterImpellerWithRetry(dl_image, std::move(encode_task),
204+
is_gpu_disabled_sync_switch,
205+
impeller_context,
206+
/*retry_runner=*/nullptr);
159207
return;
160208
}
161209

162210
raster_task_runner->PostTask([dl_image, encode_task = std::move(encode_task),
163211
io_task_runner, is_gpu_disabled_sync_switch,
164-
impeller_context]() mutable {
165-
DoConvertImageToRasterImpeller(dl_image, std::move(encode_task),
166-
is_gpu_disabled_sync_switch,
167-
impeller_context);
212+
impeller_context,
213+
raster_task_runner]() mutable {
214+
DoConvertImageToRasterImpellerWithRetry(
215+
dl_image, std::move(encode_task), is_gpu_disabled_sync_switch,
216+
impeller_context, raster_task_runner);
168217
});
169218
}
170219

0 commit comments

Comments
 (0)