Skip to content

Commit

Permalink
[Impeller] Encode directly to command buffer for Metal. (flutter#49785)
Browse files Browse the repository at this point in the history
Part of flutter/flutter#140804

Rather than using impeller::Command, the impeller::RenderPass records most state directly into the Vulkan command buffer. This should remove allocation/free overhead of the intermediary structures and make further improvements to the backend even easier.

This completely removes the background worker threads used for encoding, which should lower overall CPU usage without decreasing performance by much (though it may be a tad slower or a tad faster).
  • Loading branch information
jonahwilliams authored Jan 20, 2024
1 parent 53a2436 commit 4d67f26
Show file tree
Hide file tree
Showing 17 changed files with 497 additions and 455 deletions.
4 changes: 4 additions & 0 deletions ci/licenses_golden/licenses_flutter
Original file line number Diff line number Diff line change
Expand Up @@ -5401,6 +5401,8 @@ ORIGIN: ../../../flutter/impeller/renderer/backend/metal/gpu_tracer_mtl.h + ../.
ORIGIN: ../../../flutter/impeller/renderer/backend/metal/gpu_tracer_mtl.mm + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/impeller/renderer/backend/metal/lazy_drawable_holder.h + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/impeller/renderer/backend/metal/lazy_drawable_holder.mm + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/impeller/renderer/backend/metal/pass_bindings_cache_mtl.h + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/impeller/renderer/backend/metal/pass_bindings_cache_mtl.mm + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/impeller/renderer/backend/metal/pipeline_library_mtl.h + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/impeller/renderer/backend/metal/pipeline_library_mtl.mm + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/impeller/renderer/backend/metal/pipeline_mtl.h + ../../../flutter/LICENSE
Expand Down Expand Up @@ -8243,6 +8245,8 @@ FILE: ../../../flutter/impeller/renderer/backend/metal/gpu_tracer_mtl.h
FILE: ../../../flutter/impeller/renderer/backend/metal/gpu_tracer_mtl.mm
FILE: ../../../flutter/impeller/renderer/backend/metal/lazy_drawable_holder.h
FILE: ../../../flutter/impeller/renderer/backend/metal/lazy_drawable_holder.mm
FILE: ../../../flutter/impeller/renderer/backend/metal/pass_bindings_cache_mtl.h
FILE: ../../../flutter/impeller/renderer/backend/metal/pass_bindings_cache_mtl.mm
FILE: ../../../flutter/impeller/renderer/backend/metal/pipeline_library_mtl.h
FILE: ../../../flutter/impeller/renderer/backend/metal/pipeline_library_mtl.mm
FILE: ../../../flutter/impeller/renderer/backend/metal/pipeline_mtl.h
Expand Down
4 changes: 4 additions & 0 deletions impeller/entity/contents/checkerboard_contents_unittests.cc
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,10 @@ TEST_P(EntityTest, RendersWithoutError) {
ASSERT_TRUE(recording_pass->GetCommands().empty());
ASSERT_TRUE(contents->Render(*content_context, entity, *recording_pass));
ASSERT_FALSE(recording_pass->GetCommands().empty());

if (GetParam() == PlaygroundBackend::kMetal) {
recording_pass->EncodeCommands();
}
}
#endif // IMPELLER_DEBUG

Expand Down
2 changes: 1 addition & 1 deletion impeller/entity/contents/test/recording_render_pass.cc
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ void RecordingRenderPass::OnSetLabel(std::string label) {

// |RenderPass|
bool RecordingRenderPass::OnEncodeCommands(const Context& context) const {
return true;
return delegate_->EncodeCommands();
}

// |RenderPass|
Expand Down
4 changes: 4 additions & 0 deletions impeller/entity/contents/tiled_texture_contents_unittests.cc
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,10 @@ TEST_P(EntityTest, TiledTextureContentsRendersWithCorrectPipeline) {
ASSERT_EQ(commands.size(), 1u);
ASSERT_STREQ(commands[0].pipeline->GetDescriptor().GetLabel().c_str(),
"TextureFill Pipeline V#1");

if (GetParam() == PlaygroundBackend::kMetal) {
recording_pass->EncodeCommands();
}
}

// GL_OES_EGL_image_external isn't supported on MacOS hosts.
Expand Down
4 changes: 4 additions & 0 deletions impeller/entity/contents/vertices_contents_unittests.cc
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,10 @@ TEST_P(EntityTest, RendersDstPerColorWithAlpha) {

ASSERT_TRUE(frag_uniforms);
ASSERT_EQ(frag_uniforms->alpha, 0.5);

if (GetParam() == PlaygroundBackend::kMetal) {
recording_pass->EncodeCommands();
}
}

} // namespace testing
Expand Down
2 changes: 2 additions & 0 deletions impeller/renderer/backend/metal/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ impeller_component("metal") {
"gpu_tracer_mtl.mm",
"lazy_drawable_holder.h",
"lazy_drawable_holder.mm",
"pass_bindings_cache_mtl.h",
"pass_bindings_cache_mtl.mm",
"pipeline_library_mtl.h",
"pipeline_library_mtl.mm",
"pipeline_mtl.h",
Expand Down
7 changes: 0 additions & 7 deletions impeller/renderer/backend/metal/command_buffer_mtl.h
Original file line number Diff line number Diff line change
Expand Up @@ -38,13 +38,6 @@ class CommandBufferMTL final : public CommandBuffer {
// |CommandBuffer|
void OnWaitUntilScheduled() override;

// |CommandBuffer|
bool EncodeAndSubmit(const std::shared_ptr<RenderPass>& render_pass) override;

// |CommandBuffer|
bool EncodeAndSubmit(const std::shared_ptr<BlitPass>& blit_ass,
const std::shared_ptr<Allocator>& allocator) override;

// |CommandBuffer|
std::shared_ptr<RenderPass> OnCreateRenderPass(RenderTarget target) override;

Expand Down
85 changes: 0 additions & 85 deletions impeller/renderer/backend/metal/command_buffer_mtl.mm
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@

#include "flutter/fml/make_copyable.h"
#include "flutter/fml/synchronization/semaphore.h"
#include "flutter/fml/trace_event.h"

#include "impeller/renderer/backend/metal/blit_pass_mtl.h"
#include "impeller/renderer/backend/metal/compute_pass_mtl.h"
Expand Down Expand Up @@ -183,90 +182,6 @@ static bool LogMTLCommandBufferErrorIfPresent(id<MTLCommandBuffer> buffer) {
return true;
}

bool CommandBufferMTL::EncodeAndSubmit(
const std::shared_ptr<RenderPass>& render_pass) {
TRACE_EVENT0("impeller", "CommandBufferMTL::EncodeAndSubmit");
if (!IsValid() || !render_pass->IsValid()) {
return false;
}
auto context = context_.lock();
if (!context) {
return false;
}
[buffer_ enqueue];
auto buffer = buffer_;
buffer_ = nil;

#ifdef IMPELLER_DEBUG
ContextMTL::Cast(*context).GetGPUTracer()->RecordCmdBuffer(buffer);
#endif // IMPELLER_DEBUG

auto worker_task_runner = ContextMTL::Cast(*context).GetWorkerTaskRunner();
auto mtl_render_pass = static_cast<RenderPassMTL*>(render_pass.get());

// Render command encoder creation has been observed to exceed the stack size
// limit for worker threads, and therefore is intentionally constructed on the
// raster thread.
auto render_command_encoder =
[buffer renderCommandEncoderWithDescriptor:mtl_render_pass->desc_];
if (!render_command_encoder) {
return false;
}

auto task = fml::MakeCopyable(
[render_pass, buffer, render_command_encoder, weak_context = context_]() {
auto context = weak_context.lock();
if (!context) {
[render_command_encoder endEncoding];
return;
}

auto mtl_render_pass = static_cast<RenderPassMTL*>(render_pass.get());
if (!mtl_render_pass->label_.empty()) {
[render_command_encoder setLabel:@(mtl_render_pass->label_.c_str())];
}

auto result = mtl_render_pass->EncodeCommands(
context->GetResourceAllocator(), render_command_encoder);
[render_command_encoder endEncoding];
if (result) {
[buffer commit];
} else {
VALIDATION_LOG << "Failed to encode command buffer";
}
});
worker_task_runner->PostTask(task);
return true;
}

bool CommandBufferMTL::EncodeAndSubmit(
const std::shared_ptr<BlitPass>& blit_pass,
const std::shared_ptr<Allocator>& allocator) {
if (!IsValid() || !blit_pass->IsValid()) {
return false;
}
auto context = context_.lock();
if (!context) {
return false;
}
[buffer_ enqueue];
auto buffer = buffer_;
buffer_ = nil;

auto worker_task_runner = ContextMTL::Cast(*context).GetWorkerTaskRunner();
auto task = fml::MakeCopyable(
[blit_pass, buffer, weak_context = context_, allocator]() {
auto context = weak_context.lock();
if (!blit_pass->EncodeCommands(allocator)) {
VALIDATION_LOG << "Failed to encode blit pass.";
return;
}
[buffer commit];
});
worker_task_runner->PostTask(task);
return true;
}

void CommandBufferMTL::OnWaitUntilScheduled() {}

std::shared_ptr<RenderPass> CommandBufferMTL::OnCreateRenderPass(
Expand Down
3 changes: 0 additions & 3 deletions impeller/renderer/backend/metal/context_mtl.h
Original file line number Diff line number Diff line change
Expand Up @@ -93,8 +93,6 @@ class ContextMTL final : public Context,

id<MTLCommandBuffer> CreateMTLCommandBuffer(const std::string& label) const;

const std::shared_ptr<fml::ConcurrentTaskRunner> GetWorkerTaskRunner() const;

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

#ifdef IMPELLER_DEBUG
Expand Down Expand Up @@ -122,7 +120,6 @@ class ContextMTL final : public Context,
std::shared_ptr<SamplerLibrary> sampler_library_;
std::shared_ptr<AllocatorMTL> resource_allocator_;
std::shared_ptr<const Capabilities> device_capabilities_;
std::shared_ptr<fml::ConcurrentMessageLoop> raster_message_loop_;
std::shared_ptr<const fml::SyncSwitch> is_gpu_disabled_sync_switch_;
#ifdef IMPELLER_DEBUG
std::shared_ptr<GPUTracerMTL> gpu_tracer_;
Expand Down
28 changes: 1 addition & 27 deletions impeller/renderer/backend/metal/context_mtl.mm
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@

#include "impeller/renderer/backend/metal/context_mtl.h"

#include <Foundation/Foundation.h>
#include <memory>

#include "flutter/fml/concurrent_message_loop.h"
Expand Down Expand Up @@ -88,24 +87,6 @@ static bool DeviceSupportsComputeSubgroups(id<MTLDevice> device) {
sync_switch_observer_.reset(new SyncSwitchObserver(*this));
is_gpu_disabled_sync_switch_->AddObserver(sync_switch_observer_.get());

// Worker task runner.
{
raster_message_loop_ = fml::ConcurrentMessageLoop::Create(
std::min(4u, std::thread::hardware_concurrency()));
raster_message_loop_->PostTaskToAllWorkers([]() {
// See https://github.com/flutter/flutter/issues/65752
// Intentionally opt out of QoS for raster task workloads.
[[NSThread currentThread] setThreadPriority:1.0];
sched_param param;
int policy;
pthread_t thread = pthread_self();
if (!pthread_getschedparam(thread, &policy, &param)) {
param.sched_priority = 50;
pthread_setschedparam(thread, policy, &param);
}
});
}

// Setup the shader library.
{
if (shader_libraries == nil) {
Expand Down Expand Up @@ -330,21 +311,14 @@ new ContextMTL(device, command_queue,
}

// |Context|
void ContextMTL::Shutdown() {
raster_message_loop_.reset();
}
void ContextMTL::Shutdown() {}

#ifdef IMPELLER_DEBUG
std::shared_ptr<GPUTracerMTL> ContextMTL::GetGPUTracer() const {
return gpu_tracer_;
}
#endif // IMPELLER_DEBUG

const std::shared_ptr<fml::ConcurrentTaskRunner>
ContextMTL::GetWorkerTaskRunner() const {
return raster_message_loop_->GetTaskRunner();
}

std::shared_ptr<const fml::SyncSwitch> ContextMTL::GetIsGpuDisabledSyncSwitch()
const {
return is_gpu_disabled_sync_switch_;
Expand Down
75 changes: 75 additions & 0 deletions impeller/renderer/backend/metal/pass_bindings_cache_mtl.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
// 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.

#ifndef FLUTTER_IMPELLER_RENDERER_BACKEND_METAL_PASS_BINDINGS_CACHE_MTL_H_
#define FLUTTER_IMPELLER_RENDERER_BACKEND_METAL_PASS_BINDINGS_CACHE_MTL_H_

#include <Metal/Metal.h>

#include "impeller/renderer/render_pass.h"
#include "impeller/renderer/render_target.h"

namespace impeller {

//-----------------------------------------------------------------------------
/// @brief Ensures that bindings on the pass are not redundantly set or
/// updated. Avoids making the driver do additional checks and makes
/// the frame insights during profiling and instrumentation not
/// complain about the same.
///
/// There should be no change to rendering if this caching was
/// absent.
///
struct PassBindingsCacheMTL {
explicit PassBindingsCacheMTL() {}

~PassBindingsCacheMTL() = default;

PassBindingsCacheMTL(const PassBindingsCacheMTL&) = delete;

PassBindingsCacheMTL(PassBindingsCacheMTL&&) = delete;

void SetEncoder(id<MTLRenderCommandEncoder> encoder);

void SetRenderPipelineState(id<MTLRenderPipelineState> pipeline);

void SetDepthStencilState(id<MTLDepthStencilState> depth_stencil);

bool SetBuffer(ShaderStage stage,
uint64_t index,
uint64_t offset,
id<MTLBuffer> buffer);

bool SetTexture(ShaderStage stage, uint64_t index, id<MTLTexture> texture);

bool SetSampler(ShaderStage stage,
uint64_t index,
id<MTLSamplerState> sampler);

void SetViewport(const Viewport& viewport);

void SetScissor(const IRect& scissor);

private:
struct BufferOffsetPair {
id<MTLBuffer> buffer = nullptr;
size_t offset = 0u;
};
using BufferMap = std::map<uint64_t, BufferOffsetPair>;
using TextureMap = std::map<uint64_t, id<MTLTexture>>;
using SamplerMap = std::map<uint64_t, id<MTLSamplerState>>;

id<MTLRenderCommandEncoder> encoder_;
id<MTLRenderPipelineState> pipeline_ = nullptr;
id<MTLDepthStencilState> depth_stencil_ = nullptr;
std::map<ShaderStage, BufferMap> buffers_;
std::map<ShaderStage, TextureMap> textures_;
std::map<ShaderStage, SamplerMap> samplers_;
std::optional<Viewport> viewport_;
std::optional<IRect> scissor_;
};

} // namespace impeller

#endif // FLUTTER_IMPELLER_RENDERER_BACKEND_METAL_PASS_BINDINGS_CACHE_MTL_H_
Loading

0 comments on commit 4d67f26

Please sign in to comment.