[Impeller] Encode directly to command buffer for Metal. (flutter/engine#49785)

Part of https://github.com/flutter/flutter/issues/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).
This commit is contained in:
Jonah Williams
2024-01-19 18:57:02 -08:00
committed by GitHub
parent d6d9d8547e
commit 1d85ce2e41
17 changed files with 495 additions and 453 deletions

View File

@@ -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
@@ -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

View File

@@ -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

View File

@@ -78,7 +78,7 @@ void RecordingRenderPass::OnSetLabel(std::string label) {
// |RenderPass|
bool RecordingRenderPass::OnEncodeCommands(const Context& context) const {
return true;
return delegate_->EncodeCommands();
}
// |RenderPass|

View File

@@ -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.

View File

@@ -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

View File

@@ -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",

View File

@@ -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;

View File

@@ -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"
@@ -183,90 +182,6 @@ bool CommandBufferMTL::OnSubmitCommands(CompletionCallback callback) {
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(

View File

@@ -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
@@ -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_;

View File

@@ -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"
@@ -88,24 +87,6 @@ ContextMTL::ContextMTL(
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) {
@@ -330,9 +311,7 @@ std::shared_ptr<CommandBuffer> ContextMTL::CreateCommandBuffer() const {
}
// |Context|
void ContextMTL::Shutdown() {
raster_message_loop_.reset();
}
void ContextMTL::Shutdown() {}
#ifdef IMPELLER_DEBUG
std::shared_ptr<GPUTracerMTL> ContextMTL::GetGPUTracer() const {
@@ -340,11 +319,6 @@ std::shared_ptr<GPUTracerMTL> ContextMTL::GetGPUTracer() const {
}
#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_;

View File

@@ -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_

View File

@@ -0,0 +1,152 @@
// 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.
#include "impeller/renderer/backend/metal/pass_bindings_cache_mtl.h"
namespace impeller {
void PassBindingsCacheMTL::SetEncoder(id<MTLRenderCommandEncoder> encoder) {
encoder_ = encoder;
}
void PassBindingsCacheMTL::SetRenderPipelineState(
id<MTLRenderPipelineState> pipeline) {
if (pipeline == pipeline_) {
return;
}
pipeline_ = pipeline;
[encoder_ setRenderPipelineState:pipeline_];
}
void PassBindingsCacheMTL::SetDepthStencilState(
id<MTLDepthStencilState> depth_stencil) {
if (depth_stencil_ == depth_stencil) {
return;
}
depth_stencil_ = depth_stencil;
[encoder_ setDepthStencilState:depth_stencil_];
}
bool PassBindingsCacheMTL::SetBuffer(ShaderStage stage,
uint64_t index,
uint64_t offset,
id<MTLBuffer> buffer) {
auto& buffers_map = buffers_[stage];
auto found = buffers_map.find(index);
if (found != buffers_map.end() && found->second.buffer == buffer) {
// The right buffer is bound. Check if its offset needs to be updated.
if (found->second.offset == offset) {
// Buffer and its offset is identical. Nothing to do.
return true;
}
// Only the offset needs to be updated.
found->second.offset = offset;
switch (stage) {
case ShaderStage::kVertex:
[encoder_ setVertexBufferOffset:offset atIndex:index];
return true;
case ShaderStage::kFragment:
[encoder_ setFragmentBufferOffset:offset atIndex:index];
return true;
default:
VALIDATION_LOG << "Cannot update buffer offset of an unknown stage.";
return false;
}
return true;
}
buffers_map[index] = {buffer, static_cast<size_t>(offset)};
switch (stage) {
case ShaderStage::kVertex:
[encoder_ setVertexBuffer:buffer offset:offset atIndex:index];
return true;
case ShaderStage::kFragment:
[encoder_ setFragmentBuffer:buffer offset:offset atIndex:index];
return true;
default:
VALIDATION_LOG << "Cannot bind buffer to unknown shader stage.";
return false;
}
return false;
}
bool PassBindingsCacheMTL::SetTexture(ShaderStage stage,
uint64_t index,
id<MTLTexture> texture) {
auto& texture_map = textures_[stage];
auto found = texture_map.find(index);
if (found != texture_map.end() && found->second == texture) {
// Already bound.
return true;
}
texture_map[index] = texture;
switch (stage) {
case ShaderStage::kVertex:
[encoder_ setVertexTexture:texture atIndex:index];
return true;
case ShaderStage::kFragment:
[encoder_ setFragmentTexture:texture atIndex:index];
return true;
default:
VALIDATION_LOG << "Cannot bind buffer to unknown shader stage.";
return false;
}
return false;
}
bool PassBindingsCacheMTL::SetSampler(ShaderStage stage,
uint64_t index,
id<MTLSamplerState> sampler) {
auto& sampler_map = samplers_[stage];
auto found = sampler_map.find(index);
if (found != sampler_map.end() && found->second == sampler) {
// Already bound.
return true;
}
sampler_map[index] = sampler;
switch (stage) {
case ShaderStage::kVertex:
[encoder_ setVertexSamplerState:sampler atIndex:index];
return true;
case ShaderStage::kFragment:
[encoder_ setFragmentSamplerState:sampler atIndex:index];
return true;
default:
VALIDATION_LOG << "Cannot bind buffer to unknown shader stage.";
return false;
}
return false;
}
void PassBindingsCacheMTL::SetViewport(const Viewport& viewport) {
if (viewport_.has_value() && viewport_.value() == viewport) {
return;
}
[encoder_ setViewport:MTLViewport{
.originX = viewport.rect.GetX(),
.originY = viewport.rect.GetY(),
.width = viewport.rect.GetWidth(),
.height = viewport.rect.GetHeight(),
.znear = viewport.depth_range.z_near,
.zfar = viewport.depth_range.z_far,
}];
viewport_ = viewport;
}
void PassBindingsCacheMTL::SetScissor(const IRect& scissor) {
if (scissor_.has_value() && scissor_.value() == scissor) {
return;
}
[encoder_
setScissorRect:MTLScissorRect{
.x = static_cast<NSUInteger>(scissor.GetX()),
.y = static_cast<NSUInteger>(scissor.GetY()),
.width = static_cast<NSUInteger>(scissor.GetWidth()),
.height = static_cast<NSUInteger>(scissor.GetHeight()),
}];
scissor_ = scissor;
}
} // namespace impeller

View File

@@ -8,6 +8,7 @@
#include <Metal/Metal.h>
#include "flutter/fml/macros.h"
#include "impeller/renderer/backend/metal/pass_bindings_cache_mtl.h"
#include "impeller/renderer/render_pass.h"
#include "impeller/renderer/render_target.h"
@@ -22,14 +23,35 @@ class RenderPassMTL final : public RenderPass {
friend class CommandBufferMTL;
id<MTLCommandBuffer> buffer_ = nil;
id<MTLRenderCommandEncoder> encoder_ = nil;
MTLRenderPassDescriptor* desc_ = nil;
std::string label_;
bool is_metal_trace_active_ = false;
bool is_valid_ = false;
// Many parts of the codebase will start writing to a render pass but
// never submit them. This boolean is used to track if a submit happened
// so that in the dtor we can always ensure the render pass is finished.
mutable bool did_finish_encoding_ = false;
PassBindingsCacheMTL pass_bindings_;
// Per-command state
size_t instance_count_ = 1u;
size_t base_vertex_ = 0u;
size_t vertex_count_ = 0u;
bool has_valid_pipeline_ = false;
bool has_label_ = false;
BufferView index_buffer_ = {};
PrimitiveType primitive_type_ = {};
MTLIndexType index_type_ = {};
RenderPassMTL(std::shared_ptr<const Context> context,
const RenderTarget& target,
id<MTLCommandBuffer> buffer);
// |RenderPass|
void ReserveCommands(size_t command_count) override {}
// |RenderPass|
bool IsValid() const override;
@@ -39,8 +61,55 @@ class RenderPassMTL final : public RenderPass {
// |RenderPass|
bool OnEncodeCommands(const Context& context) const override;
bool EncodeCommands(const std::shared_ptr<Allocator>& transients_allocator,
id<MTLRenderCommandEncoder> pass) const;
// |RenderPass|
void SetPipeline(
const std::shared_ptr<Pipeline<PipelineDescriptor>>& pipeline) override;
// |RenderPass|
void SetCommandLabel(std::string_view label) override;
// |RenderPass|
void SetStencilReference(uint32_t value) override;
// |RenderPass|
void SetBaseVertex(uint64_t value) override;
// |RenderPass|
void SetViewport(Viewport viewport) override;
// |RenderPass|
void SetScissor(IRect scissor) override;
// |RenderPass|
void SetInstanceCount(size_t count) override;
// |RenderPass|
bool SetVertexBuffer(VertexBuffer buffer) override;
// |RenderPass|
fml::Status Draw() override;
// |RenderPass|
bool BindResource(ShaderStage stage,
DescriptorType type,
const ShaderUniformSlot& slot,
const ShaderMetadata& metadata,
BufferView view) override;
// |RenderPass|
bool BindResource(ShaderStage stage,
DescriptorType type,
const ShaderUniformSlot& slot,
const std::shared_ptr<const ShaderMetadata>& metadata,
BufferView view) override;
// |RenderPass|
bool BindResource(ShaderStage stage,
DescriptorType type,
const SampledImageSlot& slot,
const ShaderMetadata& metadata,
std::shared_ptr<const Texture> texture,
std::shared_ptr<const Sampler> sampler) override;
RenderPassMTL(const RenderPassMTL&) = delete;

View File

@@ -7,7 +7,8 @@
#include "flutter/fml/closure.h"
#include "flutter/fml/logging.h"
#include "flutter/fml/make_copyable.h"
#include "flutter/fml/trace_event.h"
#include "fml/status.h"
#include "impeller/base/backend_cast.h"
#include "impeller/core/formats.h"
#include "impeller/core/host_buffer.h"
@@ -143,220 +144,49 @@ RenderPassMTL::RenderPassMTL(std::shared_ptr<const Context> context,
if (!buffer_ || !desc_ || !render_target_.IsValid()) {
return;
}
encoder_ = [buffer_ renderCommandEncoderWithDescriptor:desc_];
if (!encoder_) {
return;
}
#ifdef IMPELLER_DEBUG
is_metal_trace_active_ =
[[MTLCaptureManager sharedCaptureManager] isCapturing];
#endif // IMPELLER_DEBUG
pass_bindings_.SetEncoder(encoder_);
pass_bindings_.SetViewport(
Viewport{.rect = Rect::MakeSize(GetRenderTargetSize())});
pass_bindings_.SetScissor(IRect::MakeSize(GetRenderTargetSize()));
is_valid_ = true;
}
RenderPassMTL::~RenderPassMTL() = default;
RenderPassMTL::~RenderPassMTL() {
if (!did_finish_encoding_) {
[encoder_ endEncoding];
did_finish_encoding_ = true;
}
}
bool RenderPassMTL::IsValid() const {
return is_valid_;
}
void RenderPassMTL::OnSetLabel(std::string label) {
#ifdef IMPELLER_DEBUG
if (label.empty()) {
return;
}
label_ = std::move(label);
encoder_.label = @(std::string(label).c_str());
#endif // IMPELLER_DEBUG
}
bool RenderPassMTL::OnEncodeCommands(const Context& context) const {
TRACE_EVENT0("impeller", "RenderPassMTL::EncodeCommands");
if (!IsValid()) {
return false;
}
auto render_command_encoder =
[buffer_ renderCommandEncoderWithDescriptor:desc_];
if (!render_command_encoder) {
return false;
}
if (!label_.empty()) {
[render_command_encoder setLabel:@(label_.c_str())];
}
// Success or failure, the pass must end. The buffer can only process one pass
// at a time.
fml::ScopedCleanupClosure auto_end(
[render_command_encoder]() { [render_command_encoder endEncoding]; });
return EncodeCommands(context.GetResourceAllocator(), render_command_encoder);
did_finish_encoding_ = true;
[encoder_ endEncoding];
return true;
}
//-----------------------------------------------------------------------------
/// @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 PassBindingsCache {
explicit PassBindingsCache(id<MTLRenderCommandEncoder> encoder)
: encoder_(encoder) {}
PassBindingsCache(const PassBindingsCache&) = delete;
PassBindingsCache(PassBindingsCache&&) = delete;
void SetRenderPipelineState(id<MTLRenderPipelineState> pipeline) {
if (pipeline == pipeline_) {
return;
}
pipeline_ = pipeline;
[encoder_ setRenderPipelineState:pipeline_];
}
void SetDepthStencilState(id<MTLDepthStencilState> depth_stencil) {
if (depth_stencil_ == depth_stencil) {
return;
}
depth_stencil_ = depth_stencil;
[encoder_ setDepthStencilState:depth_stencil_];
}
bool SetBuffer(ShaderStage stage,
uint64_t index,
uint64_t offset,
id<MTLBuffer> buffer) {
auto& buffers_map = buffers_[stage];
auto found = buffers_map.find(index);
if (found != buffers_map.end() && found->second.buffer == buffer) {
// The right buffer is bound. Check if its offset needs to be updated.
if (found->second.offset == offset) {
// Buffer and its offset is identical. Nothing to do.
return true;
}
// Only the offset needs to be updated.
found->second.offset = offset;
switch (stage) {
case ShaderStage::kVertex:
[encoder_ setVertexBufferOffset:offset atIndex:index];
return true;
case ShaderStage::kFragment:
[encoder_ setFragmentBufferOffset:offset atIndex:index];
return true;
default:
VALIDATION_LOG << "Cannot update buffer offset of an unknown stage.";
return false;
}
return true;
}
buffers_map[index] = {buffer, static_cast<size_t>(offset)};
switch (stage) {
case ShaderStage::kVertex:
[encoder_ setVertexBuffer:buffer offset:offset atIndex:index];
return true;
case ShaderStage::kFragment:
[encoder_ setFragmentBuffer:buffer offset:offset atIndex:index];
return true;
default:
VALIDATION_LOG << "Cannot bind buffer to unknown shader stage.";
return false;
}
return false;
}
bool SetTexture(ShaderStage stage, uint64_t index, id<MTLTexture> texture) {
auto& texture_map = textures_[stage];
auto found = texture_map.find(index);
if (found != texture_map.end() && found->second == texture) {
// Already bound.
return true;
}
texture_map[index] = texture;
switch (stage) {
case ShaderStage::kVertex:
[encoder_ setVertexTexture:texture atIndex:index];
return true;
case ShaderStage::kFragment:
[encoder_ setFragmentTexture:texture atIndex:index];
return true;
default:
VALIDATION_LOG << "Cannot bind buffer to unknown shader stage.";
return false;
}
return false;
}
bool SetSampler(ShaderStage stage,
uint64_t index,
id<MTLSamplerState> sampler) {
auto& sampler_map = samplers_[stage];
auto found = sampler_map.find(index);
if (found != sampler_map.end() && found->second == sampler) {
// Already bound.
return true;
}
sampler_map[index] = sampler;
switch (stage) {
case ShaderStage::kVertex:
[encoder_ setVertexSamplerState:sampler atIndex:index];
return true;
case ShaderStage::kFragment:
[encoder_ setFragmentSamplerState:sampler atIndex:index];
return true;
default:
VALIDATION_LOG << "Cannot bind buffer to unknown shader stage.";
return false;
}
return false;
}
void SetViewport(const Viewport& viewport) {
if (viewport_.has_value() && viewport_.value() == viewport) {
return;
}
[encoder_ setViewport:MTLViewport{
.originX = viewport.rect.GetX(),
.originY = viewport.rect.GetY(),
.width = viewport.rect.GetWidth(),
.height = viewport.rect.GetHeight(),
.znear = viewport.depth_range.z_near,
.zfar = viewport.depth_range.z_far,
}];
viewport_ = viewport;
}
void SetScissor(const IRect& scissor) {
if (scissor_.has_value() && scissor_.value() == scissor) {
return;
}
[encoder_
setScissorRect:MTLScissorRect{
.x = static_cast<NSUInteger>(scissor.GetX()),
.y = static_cast<NSUInteger>(scissor.GetY()),
.width = static_cast<NSUInteger>(scissor.GetWidth()),
.height =
static_cast<NSUInteger>(scissor.GetHeight()),
}];
scissor_ = 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>>;
const 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_;
};
static bool Bind(PassBindingsCache& pass,
Allocator& allocator,
static bool Bind(PassBindingsCacheMTL& pass,
ShaderStage stage,
size_t bind_index,
const BufferView& view) {
@@ -378,7 +208,7 @@ static bool Bind(PassBindingsCache& pass,
return pass.SetBuffer(stage, bind_index, view.range.offset, buffer);
}
static bool Bind(PassBindingsCache& pass,
static bool Bind(PassBindingsCacheMTL& pass,
ShaderStage stage,
size_t bind_index,
const Sampler& sampler,
@@ -403,147 +233,164 @@ static bool Bind(PassBindingsCache& pass,
SamplerMTL::Cast(sampler).GetMTLSamplerState());
}
bool RenderPassMTL::EncodeCommands(const std::shared_ptr<Allocator>& allocator,
id<MTLRenderCommandEncoder> encoder) const {
PassBindingsCache pass_bindings(encoder);
auto bind_stage_resources = [&allocator, &pass_bindings](
const Bindings& bindings,
ShaderStage stage) -> bool {
for (const BufferAndUniformSlot& buffer : bindings.buffers) {
if (!Bind(pass_bindings, *allocator, stage, buffer.slot.ext_res_0,
buffer.view.resource)) {
return false;
}
}
for (const TextureAndSampler& data : bindings.sampled_images) {
if (!Bind(pass_bindings, stage, data.slot.texture_index, *data.sampler,
*data.texture.resource)) {
return false;
}
}
return true;
};
// |RenderPass|
void RenderPassMTL::SetPipeline(
const std::shared_ptr<Pipeline<PipelineDescriptor>>& pipeline) {
const PipelineDescriptor& pipeline_desc = pipeline->GetDescriptor();
primitive_type_ = pipeline_desc.GetPrimitiveType();
pass_bindings_.SetRenderPipelineState(
PipelineMTL::Cast(*pipeline).GetMTLRenderPipelineState());
pass_bindings_.SetDepthStencilState(
PipelineMTL::Cast(*pipeline).GetMTLDepthStencilState());
const auto target_sample_count = render_target_.GetSampleCount();
[encoder_ setFrontFacingWinding:pipeline_desc.GetWindingOrder() ==
WindingOrder::kClockwise
? MTLWindingClockwise
: MTLWindingCounterClockwise];
[encoder_ setCullMode:ToMTLCullMode(pipeline_desc.GetCullMode())];
[encoder_ setTriangleFillMode:ToMTLTriangleFillMode(
pipeline_desc.GetPolygonMode())];
has_valid_pipeline_ = true;
}
fml::closure pop_debug_marker = [encoder]() { [encoder popDebugGroup]; };
for (const auto& command : commands_) {
// |RenderPass|
void RenderPassMTL::SetCommandLabel(std::string_view label) {
#ifdef IMPELLER_DEBUG
fml::ScopedCleanupClosure auto_pop_debug_marker(pop_debug_marker);
if (!command.label.empty()) {
[encoder pushDebugGroup:@(command.label.c_str())];
} else {
auto_pop_debug_marker.Release();
}
if (is_metal_trace_active_) {
has_label_ = true;
std::string label_copy(label);
[encoder_ pushDebugGroup:@(label_copy.c_str())];
}
#endif // IMPELLER_DEBUG
}
const auto& pipeline_desc = command.pipeline->GetDescriptor();
if (target_sample_count != pipeline_desc.GetSampleCount()) {
VALIDATION_LOG << "Pipeline for command and the render target disagree "
"on sample counts (target was "
<< static_cast<uint64_t>(target_sample_count)
<< " but pipeline wanted "
<< static_cast<uint64_t>(pipeline_desc.GetSampleCount())
<< ").";
return false;
}
// |RenderPass|
void RenderPassMTL::SetStencilReference(uint32_t value) {
[encoder_ setStencilReferenceValue:value];
}
pass_bindings.SetRenderPipelineState(
PipelineMTL::Cast(*command.pipeline).GetMTLRenderPipelineState());
pass_bindings.SetDepthStencilState(
PipelineMTL::Cast(*command.pipeline).GetMTLDepthStencilState());
pass_bindings.SetViewport(command.viewport.value_or<Viewport>(
{.rect = Rect::MakeSize(GetRenderTargetSize())}));
pass_bindings.SetScissor(
command.scissor.value_or(IRect::MakeSize(GetRenderTargetSize())));
// |RenderPass|
void RenderPassMTL::SetBaseVertex(uint64_t value) {
base_vertex_ = value;
}
[encoder setFrontFacingWinding:pipeline_desc.GetWindingOrder() ==
WindingOrder::kClockwise
? MTLWindingClockwise
: MTLWindingCounterClockwise];
[encoder setCullMode:ToMTLCullMode(pipeline_desc.GetCullMode())];
[encoder setTriangleFillMode:ToMTLTriangleFillMode(
pipeline_desc.GetPolygonMode())];
[encoder setStencilReferenceValue:command.stencil_reference];
// |RenderPass|
void RenderPassMTL::SetViewport(Viewport viewport) {
pass_bindings_.SetViewport(viewport);
}
if (!Bind(pass_bindings, *allocator, ShaderStage::kVertex,
VertexDescriptor::kReservedVertexBufferIndex,
command.vertex_buffer.vertex_buffer)) {
return false;
}
// |RenderPass|
void RenderPassMTL::SetScissor(IRect scissor) {
pass_bindings_.SetScissor(scissor);
}
if (!bind_stage_resources(command.vertex_bindings, ShaderStage::kVertex)) {
return false;
}
if (!bind_stage_resources(command.fragment_bindings,
ShaderStage::kFragment)) {
return false;
}
// |RenderPass|
void RenderPassMTL::SetInstanceCount(size_t count) {
instance_count_ = count;
}
const PrimitiveType primitive_type = pipeline_desc.GetPrimitiveType();
if (command.vertex_buffer.index_type == IndexType::kNone) {
if (command.instance_count != 1u) {
#if TARGET_OS_SIMULATOR
VALIDATION_LOG << "iOS Simulator does not support instanced rendering.";
return false;
#else // TARGET_OS_SIMULATOR
[encoder drawPrimitives:ToMTLPrimitiveType(primitive_type)
vertexStart:command.base_vertex
vertexCount:command.vertex_buffer.vertex_count
instanceCount:command.instance_count
baseInstance:0u];
#endif // TARGET_OS_SIMULATOR
} else {
[encoder drawPrimitives:ToMTLPrimitiveType(primitive_type)
vertexStart:command.base_vertex
vertexCount:command.vertex_buffer.vertex_count];
}
continue;
}
// |RenderPass|
bool RenderPassMTL::SetVertexBuffer(VertexBuffer buffer) {
if (buffer.index_type == IndexType::kUnknown) {
return false;
}
if (command.vertex_buffer.index_type == IndexType::kUnknown) {
return false;
}
auto index_buffer = command.vertex_buffer.index_buffer.buffer;
if (!index_buffer) {
return false;
}
auto mtl_index_buffer = DeviceBufferMTL::Cast(*index_buffer).GetMTLBuffer();
if (!mtl_index_buffer) {
return false;
}
if (!Bind(pass_bindings_, ShaderStage::kVertex,
VertexDescriptor::kReservedVertexBufferIndex,
buffer.vertex_buffer)) {
return false;
}
FML_DCHECK(
command.vertex_buffer.vertex_count *
(command.vertex_buffer.index_type == IndexType::k16bit ? 2 : 4) ==
command.vertex_buffer.index_buffer.range.length);
if (command.instance_count != 1u) {
#if TARGET_OS_SIMULATOR
VALIDATION_LOG << "iOS Simulator does not support instanced rendering.";
return false;
#else // TARGET_OS_SIMULATOR
[encoder
drawIndexedPrimitives:ToMTLPrimitiveType(primitive_type)
indexCount:command.vertex_buffer.vertex_count
indexType:ToMTLIndexType(command.vertex_buffer.index_type)
indexBuffer:mtl_index_buffer
indexBufferOffset:command.vertex_buffer.index_buffer.range.offset
instanceCount:command.instance_count
baseVertex:command.base_vertex
baseInstance:0u];
#endif // TARGET_OS_SIMULATOR
} else {
[encoder
drawIndexedPrimitives:ToMTLPrimitiveType(primitive_type)
indexCount:command.vertex_buffer.vertex_count
indexType:ToMTLIndexType(command.vertex_buffer.index_type)
indexBuffer:mtl_index_buffer
indexBufferOffset:command.vertex_buffer.index_buffer.range
.offset];
}
vertex_count_ = buffer.vertex_count;
if (buffer.index_type != IndexType::kNone) {
index_type_ = ToMTLIndexType(buffer.index_type);
index_buffer_ = std::move(buffer.index_buffer);
}
return true;
}
// |RenderPass|
fml::Status RenderPassMTL::Draw() {
if (!has_valid_pipeline_) {
return fml::Status(fml::StatusCode::kCancelled, "Invalid pipeline.");
}
if (!index_buffer_) {
if (instance_count_ != 1u) {
[encoder_ drawPrimitives:ToMTLPrimitiveType(primitive_type_)
vertexStart:base_vertex_
vertexCount:vertex_count_
instanceCount:instance_count_
baseInstance:0u];
} else {
[encoder_ drawPrimitives:ToMTLPrimitiveType(primitive_type_)
vertexStart:base_vertex_
vertexCount:vertex_count_];
}
} else {
id<MTLBuffer> mtl_index_buffer =
DeviceBufferMTL::Cast(*index_buffer_.buffer).GetMTLBuffer();
if (instance_count_ != 1u) {
[encoder_ drawIndexedPrimitives:ToMTLPrimitiveType(primitive_type_)
indexCount:vertex_count_
indexType:index_type_
indexBuffer:mtl_index_buffer
indexBufferOffset:index_buffer_.range.offset
instanceCount:instance_count_
baseVertex:base_vertex_
baseInstance:0u];
} else {
[encoder_ drawIndexedPrimitives:ToMTLPrimitiveType(primitive_type_)
indexCount:vertex_count_
indexType:index_type_
indexBuffer:mtl_index_buffer
indexBufferOffset:index_buffer_.range.offset];
}
}
#ifdef IMPELLER_DEBUG
if (has_label_) {
[encoder_ popDebugGroup];
}
#endif // IMPELLER_DEBUG
vertex_count_ = 0u;
base_vertex_ = 0u;
instance_count_ = 1u;
index_buffer_ = {};
has_valid_pipeline_ = false;
has_label_ = false;
return fml::Status();
}
// |RenderPass|
bool RenderPassMTL::BindResource(ShaderStage stage,
DescriptorType type,
const ShaderUniformSlot& slot,
const ShaderMetadata& metadata,
BufferView view) {
return Bind(pass_bindings_, stage, slot.ext_res_0, view);
}
// |RenderPass|
bool RenderPassMTL::BindResource(
ShaderStage stage,
DescriptorType type,
const ShaderUniformSlot& slot,
const std::shared_ptr<const ShaderMetadata>& metadata,
BufferView view) {
return Bind(pass_bindings_, stage, slot.ext_res_0, view);
}
// |RenderPass|
bool RenderPassMTL::BindResource(ShaderStage stage,
DescriptorType type,
const SampledImageSlot& slot,
const ShaderMetadata& metadata,
std::shared_ptr<const Texture> texture,
std::shared_ptr<const Sampler> sampler) {
return Bind(pass_bindings_, stage, slot.texture_index, *sampler, *texture);
}
} // namespace impeller

View File

@@ -4,7 +4,6 @@
#include "impeller/renderer/command_buffer.h"
#include "flutter/fml/trace_event.h"
#include "impeller/renderer/compute_pass.h"
#include "impeller/renderer/render_pass.h"
#include "impeller/renderer/render_target.h"
@@ -17,7 +16,6 @@ CommandBuffer::CommandBuffer(std::weak_ptr<const Context> context)
CommandBuffer::~CommandBuffer() = default;
bool CommandBuffer::SubmitCommands(const CompletionCallback& callback) {
TRACE_EVENT0("impeller", "CommandBuffer::SubmitCommands");
if (!IsValid()) {
// Already committed or was never valid. Either way, this is caller error.
if (callback) {

View File

@@ -46,6 +46,9 @@ class RenderPass : public ResourceBinder {
void SetLabel(std::string label);
/// @brief Reserve [command_count] commands in the HAL command buffer.
///
/// Note: this is not the native command buffer.
virtual void ReserveCommands(size_t command_count) {
commands_.reserve(command_count);
}

View File

@@ -1272,6 +1272,7 @@ TEST_P(RendererTest, CanLookupRenderTargetProperties) {
render_target.GetStencilAttachment().has_value());
EXPECT_EQ(render_pass->GetRenderTargetSize(),
render_target.GetRenderTargetSize());
render_pass->EncodeCommands();
}
} // namespace testing