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

Commit fc6df95

Browse files
author
Jonah Williams
authored
[Impeller] Encode render passes concurrently on iOS. (#42028)
Allows pushing encoding of command buffers to a worker thread, relying on the fact that these buffers are always scheduled in the order that they are enqueued. This follows the guidelines from https://developer.apple.com/documentation/metal/mtlcommandbuffer?language=objc
1 parent 3535e0d commit fc6df95

31 files changed

+442
-129
lines changed

impeller/entity/contents/content_context.cc

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -375,11 +375,7 @@ std::shared_ptr<Texture> ContentContext::MakeSubpass(
375375
return nullptr;
376376
}
377377

378-
if (!sub_renderpass->EncodeCommands()) {
379-
return nullptr;
380-
}
381-
382-
if (!sub_command_buffer->SubmitCommands()) {
378+
if (!sub_command_buffer->SubmitCommandsAsync(std::move(sub_renderpass))) {
383379
return nullptr;
384380
}
385381

impeller/entity/inline_pass_context.cc

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -54,15 +54,9 @@ bool InlinePassContext::EndPass() {
5454
return true;
5555
}
5656

57-
if (!pass_->EncodeCommands()) {
58-
VALIDATION_LOG
59-
<< "Failed to encode commands while ending the current render pass.";
60-
return false;
61-
}
62-
63-
if (!command_buffer_->SubmitCommands()) {
64-
VALIDATION_LOG
65-
<< "Failed to submit command buffer while ending render pass.";
57+
if (!command_buffer_->SubmitCommandsAsync(std::move(pass_))) {
58+
VALIDATION_LOG << "Failed to encode and submit command buffer while ending "
59+
"render pass.";
6660
return false;
6761
}
6862

impeller/playground/backend/metal/playground_impl_mtl.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@
66

77
#include <memory>
88

9+
#include "flutter/fml/concurrent_message_loop.h"
910
#include "flutter/fml/macros.h"
11+
#include "flutter/fml/synchronization/sync_switch.h"
1012
#include "impeller/playground/playground_impl.h"
1113

1214
namespace impeller {
@@ -27,6 +29,8 @@ class PlaygroundImplMTL final : public PlaygroundImpl {
2729
// To ensure that ObjC stuff doesn't leak into C++ TUs.
2830
std::unique_ptr<Data> data_;
2931
std::shared_ptr<Context> context_;
32+
std::shared_ptr<fml::ConcurrentMessageLoop> concurrent_loop_;
33+
std::shared_ptr<const fml::SyncSwitch> is_gpu_disabled_sync_switch_;
3034

3135
// |PlaygroundImpl|
3236
std::shared_ptr<Context> GetContext() const override;

impeller/playground/backend/metal/playground_impl_mtl.mm

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -63,16 +63,20 @@
6363
PlaygroundImplMTL::PlaygroundImplMTL(PlaygroundSwitches switches)
6464
: PlaygroundImpl(switches),
6565
handle_(nullptr, &DestroyWindowHandle),
66-
data_(std::make_unique<Data>()) {
66+
data_(std::make_unique<Data>()),
67+
concurrent_loop_(fml::ConcurrentMessageLoop::Create()),
68+
is_gpu_disabled_sync_switch_(new fml::SyncSwitch(false)) {
6769
::glfwDefaultWindowHints();
6870
::glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
6971
::glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE);
7072
auto window = ::glfwCreateWindow(1, 1, "Test", nullptr, nullptr);
7173
if (!window) {
7274
return;
7375
}
74-
auto context = ContextMTL::Create(ShaderLibraryMappingsForPlayground(),
75-
"Playground Library");
76+
auto worker_task_runner = concurrent_loop_->GetTaskRunner();
77+
auto context = ContextMTL::Create(
78+
ShaderLibraryMappingsForPlayground(), worker_task_runner,
79+
is_gpu_disabled_sync_switch_, "Playground Library");
7680
if (!context) {
7781
return;
7882
}

impeller/renderer/backend/metal/command_buffer_mtl.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,9 @@ class CommandBufferMTL final : public CommandBuffer {
3636
// |CommandBuffer|
3737
void OnWaitUntilScheduled() override;
3838

39+
// |CommandBuffer|
40+
bool SubmitCommandsAsync(std::shared_ptr<RenderPass> render_pass) override;
41+
3942
// |CommandBuffer|
4043
std::shared_ptr<RenderPass> OnCreateRenderPass(RenderTarget target) override;
4144

impeller/renderer/backend/metal/command_buffer_mtl.mm

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,13 @@
44

55
#include "impeller/renderer/backend/metal/command_buffer_mtl.h"
66

7+
#include "flutter/fml/make_copyable.h"
8+
#include "flutter/fml/synchronization/semaphore.h"
9+
#include "flutter/fml/trace_event.h"
10+
711
#include "impeller/renderer/backend/metal/blit_pass_mtl.h"
812
#include "impeller/renderer/backend/metal/compute_pass_mtl.h"
13+
#include "impeller/renderer/backend/metal/context_mtl.h"
914
#include "impeller/renderer/backend/metal/render_pass_mtl.h"
1015

1116
namespace impeller {
@@ -171,6 +176,62 @@ static bool LogMTLCommandBufferErrorIfPresent(id<MTLCommandBuffer> buffer) {
171176
return true;
172177
}
173178

179+
bool CommandBufferMTL::SubmitCommandsAsync(
180+
std::shared_ptr<RenderPass> render_pass) {
181+
TRACE_EVENT0("impeller", "CommandBufferMTL::SubmitCommandsAsync");
182+
if (!IsValid() || !render_pass->IsValid()) {
183+
return false;
184+
}
185+
auto context = context_.lock();
186+
if (!context) {
187+
return false;
188+
}
189+
[buffer_ enqueue];
190+
auto buffer = buffer_;
191+
buffer_ = nil;
192+
193+
auto worker_task_runner = ContextMTL::Cast(*context).GetWorkerTaskRunner();
194+
auto mtl_render_pass = static_cast<RenderPassMTL*>(render_pass.get());
195+
196+
// Render command encoder creation has been observed to exceed the stack size
197+
// limit for worker threads, and therefore is intentionally constructed on the
198+
// raster thread.
199+
auto render_command_encoder =
200+
[buffer renderCommandEncoderWithDescriptor:mtl_render_pass->desc_];
201+
if (!render_command_encoder) {
202+
return false;
203+
}
204+
205+
auto task = fml::MakeCopyable([render_pass, buffer, render_command_encoder,
206+
weak_context = context_]() {
207+
auto context = weak_context.lock();
208+
if (!context) {
209+
return;
210+
}
211+
auto is_gpu_disabled_sync_switch =
212+
ContextMTL::Cast(*context).GetIsGpuDisabledSyncSwitch();
213+
is_gpu_disabled_sync_switch->Execute(fml::SyncSwitch::Handlers().SetIfFalse(
214+
[&render_pass, &render_command_encoder, &buffer, &context] {
215+
auto mtl_render_pass = static_cast<RenderPassMTL*>(render_pass.get());
216+
if (!mtl_render_pass->label_.empty()) {
217+
[render_command_encoder
218+
setLabel:@(mtl_render_pass->label_.c_str())];
219+
}
220+
221+
auto result = mtl_render_pass->EncodeCommands(
222+
context->GetResourceAllocator(), render_command_encoder);
223+
[render_command_encoder endEncoding];
224+
if (result) {
225+
[buffer commit];
226+
} else {
227+
VALIDATION_LOG << "Failed to encode command buffer";
228+
}
229+
}));
230+
});
231+
worker_task_runner->PostTask(task);
232+
return true;
233+
}
234+
174235
void CommandBufferMTL::OnWaitUntilScheduled() {}
175236

176237
std::shared_ptr<RenderPass> CommandBufferMTL::OnCreateRenderPass(

impeller/renderer/backend/metal/context_mtl.h

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@
99
#include <string>
1010
#include <vector>
1111

12+
#include "flutter/fml/concurrent_message_loop.h"
1213
#include "flutter/fml/macros.h"
14+
#include "flutter/fml/synchronization/sync_switch.h"
1315
#include "impeller/base/backend_cast.h"
1416
#include "impeller/core/sampler.h"
1517
#include "impeller/renderer/backend/metal/allocator_mtl.h"
@@ -26,10 +28,14 @@ class ContextMTL final : public Context,
2628
public std::enable_shared_from_this<ContextMTL> {
2729
public:
2830
static std::shared_ptr<ContextMTL> Create(
29-
const std::vector<std::string>& shader_library_paths);
31+
const std::vector<std::string>& shader_library_paths,
32+
std::shared_ptr<fml::ConcurrentTaskRunner> worker_task_runner,
33+
std::shared_ptr<const fml::SyncSwitch> is_gpu_disabled_sync_switch);
3034

3135
static std::shared_ptr<ContextMTL> Create(
3236
const std::vector<std::shared_ptr<fml::Mapping>>& shader_libraries_data,
37+
std::shared_ptr<fml::ConcurrentTaskRunner> worker_task_runner,
38+
std::shared_ptr<const fml::SyncSwitch> is_gpu_disabled_sync_switch,
3339
const std::string& label);
3440

3541
// |Context|
@@ -66,6 +72,10 @@ class ContextMTL final : public Context,
6672

6773
id<MTLCommandBuffer> CreateMTLCommandBuffer() const;
6874

75+
const std::shared_ptr<fml::ConcurrentTaskRunner>& GetWorkerTaskRunner() const;
76+
77+
std::shared_ptr<const fml::SyncSwitch> GetIsGpuDisabledSyncSwitch() const;
78+
6979
private:
7080
id<MTLDevice> device_ = nullptr;
7181
id<MTLCommandQueue> command_queue_ = nullptr;
@@ -74,9 +84,15 @@ class ContextMTL final : public Context,
7484
std::shared_ptr<SamplerLibrary> sampler_library_;
7585
std::shared_ptr<AllocatorMTL> resource_allocator_;
7686
std::shared_ptr<const Capabilities> device_capabilities_;
87+
std::shared_ptr<fml::ConcurrentTaskRunner> worker_task_runner_;
88+
std::shared_ptr<const fml::SyncSwitch> is_gpu_disabled_sync_switch_;
7789
bool is_valid_ = false;
7890

79-
ContextMTL(id<MTLDevice> device, NSArray<id<MTLLibrary>>* shader_libraries);
91+
ContextMTL(
92+
id<MTLDevice> device,
93+
NSArray<id<MTLLibrary>>* shader_libraries,
94+
std::shared_ptr<fml::ConcurrentTaskRunner> worker_task_runner,
95+
std::shared_ptr<const fml::SyncSwitch> is_gpu_disabled_sync_switch);
8096

8197
std::shared_ptr<CommandBuffer> CreateCommandBufferInQueue(
8298
id<MTLCommandQueue> queue) const;

impeller/renderer/backend/metal/context_mtl.mm

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -65,9 +65,14 @@ static bool DeviceSupportsComputeSubgroups(id<MTLDevice> device) {
6565
.Build();
6666
}
6767

68-
ContextMTL::ContextMTL(id<MTLDevice> device,
69-
NSArray<id<MTLLibrary>>* shader_libraries)
70-
: device_(device) {
68+
ContextMTL::ContextMTL(
69+
id<MTLDevice> device,
70+
NSArray<id<MTLLibrary>>* shader_libraries,
71+
std::shared_ptr<fml::ConcurrentTaskRunner> worker_task_runner,
72+
std::shared_ptr<const fml::SyncSwitch> is_gpu_disabled_sync_switch)
73+
: device_(device),
74+
worker_task_runner_(std::move(worker_task_runner)),
75+
is_gpu_disabled_sync_switch_(std::move(is_gpu_disabled_sync_switch)) {
7176
// Validate device.
7277
if (!device_) {
7378
VALIDATION_LOG << "Could not setup valid Metal device.";
@@ -200,10 +205,13 @@ static bool DeviceSupportsComputeSubgroups(id<MTLDevice> device) {
200205
}
201206

202207
std::shared_ptr<ContextMTL> ContextMTL::Create(
203-
const std::vector<std::string>& shader_library_paths) {
208+
const std::vector<std::string>& shader_library_paths,
209+
std::shared_ptr<fml::ConcurrentTaskRunner> worker_task_runner,
210+
std::shared_ptr<const fml::SyncSwitch> is_gpu_disabled_sync_switch) {
204211
auto device = CreateMetalDevice();
205212
auto context = std::shared_ptr<ContextMTL>(new ContextMTL(
206-
device, MTLShaderLibraryFromFilePaths(device, shader_library_paths)));
213+
device, MTLShaderLibraryFromFilePaths(device, shader_library_paths),
214+
std::move(worker_task_runner), std::move(is_gpu_disabled_sync_switch)));
207215
if (!context->IsValid()) {
208216
FML_LOG(ERROR) << "Could not create Metal context.";
209217
return nullptr;
@@ -213,11 +221,14 @@ static bool DeviceSupportsComputeSubgroups(id<MTLDevice> device) {
213221

214222
std::shared_ptr<ContextMTL> ContextMTL::Create(
215223
const std::vector<std::shared_ptr<fml::Mapping>>& shader_libraries_data,
224+
std::shared_ptr<fml::ConcurrentTaskRunner> worker_task_runner,
225+
std::shared_ptr<const fml::SyncSwitch> is_gpu_disabled_sync_switch,
216226
const std::string& label) {
217227
auto device = CreateMetalDevice();
218228
auto context = std::shared_ptr<ContextMTL>(new ContextMTL(
219229
device,
220-
MTLShaderLibraryFromFileData(device, shader_libraries_data, label)));
230+
MTLShaderLibraryFromFileData(device, shader_libraries_data, label),
231+
worker_task_runner, std::move(is_gpu_disabled_sync_switch)));
221232
if (!context->IsValid()) {
222233
FML_LOG(ERROR) << "Could not create Metal context.";
223234
return nullptr;
@@ -257,6 +268,16 @@ static bool DeviceSupportsComputeSubgroups(id<MTLDevice> device) {
257268
return CreateCommandBufferInQueue(command_queue_);
258269
}
259270

271+
const std::shared_ptr<fml::ConcurrentTaskRunner>&
272+
ContextMTL::GetWorkerTaskRunner() const {
273+
return worker_task_runner_;
274+
}
275+
276+
std::shared_ptr<const fml::SyncSwitch> ContextMTL::GetIsGpuDisabledSyncSwitch()
277+
const {
278+
return is_gpu_disabled_sync_switch_;
279+
}
280+
260281
std::shared_ptr<CommandBuffer> ContextMTL::CreateCommandBufferInQueue(
261282
id<MTLCommandQueue> queue) const {
262283
if (!IsValid()) {

impeller/renderer/backend/metal/render_pass_mtl.mm

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,13 @@
66

77
#include "flutter/fml/closure.h"
88
#include "flutter/fml/logging.h"
9+
#include "flutter/fml/make_copyable.h"
910
#include "flutter/fml/trace_event.h"
1011
#include "impeller/base/backend_cast.h"
1112
#include "impeller/core/formats.h"
1213
#include "impeller/core/host_buffer.h"
1314
#include "impeller/core/shader_types.h"
15+
#include "impeller/renderer/backend/metal/context_mtl.h"
1416
#include "impeller/renderer/backend/metal/device_buffer_mtl.h"
1517
#include "impeller/renderer/backend/metal/formats_mtl.h"
1618
#include "impeller/renderer/backend/metal/pipeline_mtl.h"

impeller/renderer/command_buffer.cc

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,21 @@ void CommandBuffer::WaitUntilScheduled() {
3636
return OnWaitUntilScheduled();
3737
}
3838

39+
bool CommandBuffer::SubmitCommandsAsync(
40+
std::shared_ptr<RenderPass>
41+
render_pass // NOLINT(performance-unnecessary-value-param)
42+
) {
43+
TRACE_EVENT0("impeller", "CommandBuffer::SubmitCommandsAsync");
44+
if (!render_pass->IsValid() || !IsValid()) {
45+
return false;
46+
}
47+
if (!render_pass->EncodeCommands()) {
48+
return false;
49+
}
50+
51+
return SubmitCommands(nullptr);
52+
}
53+
3954
std::shared_ptr<RenderPass> CommandBuffer::CreateRenderPass(
4055
const RenderTarget& render_target) {
4156
auto pass = OnCreateRenderPass(render_target);

0 commit comments

Comments
 (0)