Skip to content

Commit

Permalink
[Impeller] add async command submission for blit pass. (flutter#48040)
Browse files Browse the repository at this point in the history
This is a requirement for the "Remove Drawable Acquisition Latency" Change, but is otherwise a harmless improvement.

---

Submitting a command buffer causes the backend specific encoding logic to run. Metal is unique in that it is fairly easy to move this work into a background thread, allowing the engine to move onto creating the next command buffer. This improves throughput of the engine, at the cost of needing two slightly different APIs. Currently the GLES and Vulkan versions of this method still submit synchronously - for now that is out of scope as doing background work with those APIs has proved more challenging.

See also:
   * flutter#42028
   * flutter/flutter#131698
   
Separately, as a requirement for the design in "Remove Drawable Acquisition Latency", we need to be able to defer drawable acquisition to this background thread. While this almost already works for render passes, it does not work for blit passes today. if the engine renders a backdrop filter, then the final command buffer submitted will be a blit pass that copies an offscreen onto the drawable. Therefore we need to add an async version of the blit submission, so that we have a hook to move the drawable acquisition onto a background thread for metal.

This hadn't been done until now because most blit cmd buffers have 1 or 2 cmds on them so the benefit of moving to a background thread is minimal.

Part of flutter/flutter#138490
  • Loading branch information
jonahwilliams committed Nov 15, 2023
1 parent 8372f6c commit f178e19
Show file tree
Hide file tree
Showing 10 changed files with 86 additions and 34 deletions.
16 changes: 13 additions & 3 deletions impeller/aiks/aiks_unittests.cc
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,8 @@ TEST_P(AiksTest, CanRenderColorFilterWithInvertColorsDrawPaint) {
namespace {
bool GenerateMipmap(const std::shared_ptr<Context>& context,
std::shared_ptr<Texture> texture,
std::string label) {
std::string label,
bool async_submit) {
auto buffer = context->CreateCommandBuffer();
if (!buffer) {
return false;
Expand All @@ -175,18 +176,23 @@ bool GenerateMipmap(const std::shared_ptr<Context>& context,
return false;
}
pass->GenerateMipmap(std::move(texture), std::move(label));
if (async_submit) {
return buffer->EncodeAndSubmit(pass, context->GetResourceAllocator());
}

pass->EncodeCommands(context->GetResourceAllocator());
return buffer->SubmitCommands();
}

void CanRenderTiledTexture(AiksTest* aiks_test,
Entity::TileMode tile_mode,
bool async_submit = false,
Matrix local_matrix = {}) {
auto context = aiks_test->GetContext();
ASSERT_TRUE(context);
auto texture = aiks_test->CreateTextureForFixture("table_mountain_nx.png",
/*enable_mipmapping=*/true);
GenerateMipmap(context, texture, "table_mountain_nx");
GenerateMipmap(context, texture, "table_mountain_nx", async_submit);
Canvas canvas;
canvas.Scale(aiks_test->GetContentScale());
canvas.Translate({100.0f, 100.0f, 0});
Expand Down Expand Up @@ -233,6 +239,10 @@ TEST_P(AiksTest, CanRenderTiledTextureClamp) {
CanRenderTiledTexture(this, Entity::TileMode::kClamp);
}

TEST_P(AiksTest, CanRenderTiledTextureClampAsync) {
CanRenderTiledTexture(this, Entity::TileMode::kClamp, /*async_submit=*/true);
}

TEST_P(AiksTest, CanRenderTiledTextureRepeat) {
CanRenderTiledTexture(this, Entity::TileMode::kRepeat);
}
Expand All @@ -246,7 +256,7 @@ TEST_P(AiksTest, CanRenderTiledTextureDecal) {
}

TEST_P(AiksTest, CanRenderTiledTextureClampWithTranslate) {
CanRenderTiledTexture(this, Entity::TileMode::kClamp,
CanRenderTiledTexture(this, Entity::TileMode::kClamp, /*async_submit=*/false,
Matrix::MakeTranslation({172.f, 172.f, 0.f}));
}

Expand Down
5 changes: 0 additions & 5 deletions impeller/aiks/testing/context_mock.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,6 @@ class CommandBufferMock : public CommandBuffer {

MOCK_METHOD(void, SetLabel, (const std::string& label), (const, override));

MOCK_METHOD(bool,
SubmitCommandsAsync,
(std::shared_ptr<RenderPass> render_pass),
(override));

MOCK_METHOD(std::shared_ptr<RenderPass>,
OnCreateRenderPass,
(RenderTarget render_target),
Expand Down
6 changes: 0 additions & 6 deletions impeller/aiks/testing/context_spy.cc
Original file line number Diff line number Diff line change
Expand Up @@ -63,12 +63,6 @@ std::shared_ptr<ContextMock> ContextSpy::MakeContext(
return real_buffer->SetLabel(label);
});

ON_CALL(*spy, SubmitCommandsAsync)
.WillByDefault([real_buffer](
std::shared_ptr<RenderPass> render_pass) {
return real_buffer->SubmitCommandsAsync(std::move(render_pass));
});

ON_CALL(*spy, OnCreateRenderPass)
.WillByDefault(
[real_buffer, shared_this](const RenderTarget& render_target) {
Expand Down
2 changes: 1 addition & 1 deletion impeller/entity/contents/content_context.cc
Original file line number Diff line number Diff line change
Expand Up @@ -430,7 +430,7 @@ std::shared_ptr<Texture> ContentContext::MakeSubpass(
return nullptr;
}

if (!sub_command_buffer->SubmitCommandsAsync(std::move(sub_renderpass))) {
if (!sub_command_buffer->EncodeAndSubmit(sub_renderpass)) {
return nullptr;
}

Expand Down
10 changes: 3 additions & 7 deletions impeller/entity/entity_pass.cc
Original file line number Diff line number Diff line change
Expand Up @@ -371,8 +371,8 @@ bool EntityPass::Render(ContentContext& renderer,
offscreen_target.GetRenderTarget().GetRenderTargetTexture(),
root_render_target.GetRenderTargetTexture());

if (!blit_pass->EncodeCommands(
renderer.GetContext()->GetResourceAllocator())) {
if (!command_buffer->EncodeAndSubmit(
blit_pass, renderer.GetContext()->GetResourceAllocator())) {
VALIDATION_LOG << "Failed to encode root pass blit command.";
return false;
}
Expand All @@ -399,15 +399,11 @@ bool EntityPass::Render(ContentContext& renderer,
}
}

if (!render_pass->EncodeCommands()) {
if (!command_buffer->EncodeAndSubmit(render_pass)) {
VALIDATION_LOG << "Failed to encode root pass command buffer.";
return false;
}
}
if (!command_buffer->SubmitCommands()) {
VALIDATION_LOG << "Failed to submit root pass command buffer.";
return false;
}

return true;
}
Expand Down
2 changes: 1 addition & 1 deletion impeller/entity/inline_pass_context.cc
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ bool InlinePassContext::EndPass() {
}

if (command_buffer_) {
if (!command_buffer_->SubmitCommandsAsync(std::move(pass_))) {
if (!command_buffer_->EncodeAndSubmit(pass_)) {
VALIDATION_LOG
<< "Failed to encode and submit command buffer while ending "
"render pass.";
Expand Down
7 changes: 6 additions & 1 deletion impeller/renderer/backend/metal/command_buffer_mtl.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
#include <Metal/Metal.h>

#include "flutter/fml/macros.h"
#include "impeller/core/allocator.h"
#include "impeller/renderer/command_buffer.h"

namespace impeller {
Expand Down Expand Up @@ -37,7 +38,11 @@ class CommandBufferMTL final : public CommandBuffer {
void OnWaitUntilScheduled() override;

// |CommandBuffer|
bool SubmitCommandsAsync(std::shared_ptr<RenderPass> render_pass) override;
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
34 changes: 31 additions & 3 deletions impeller/renderer/backend/metal/command_buffer_mtl.mm
Original file line number Diff line number Diff line change
Expand Up @@ -183,9 +183,9 @@ static bool LogMTLCommandBufferErrorIfPresent(id<MTLCommandBuffer> buffer) {
return true;
}

bool CommandBufferMTL::SubmitCommandsAsync(
std::shared_ptr<RenderPass> render_pass) {
TRACE_EVENT0("impeller", "CommandBufferMTL::SubmitCommandsAsync");
bool CommandBufferMTL::EncodeAndSubmit(
const std::shared_ptr<RenderPass>& render_pass) {
TRACE_EVENT0("impeller", "CommandBufferMTL::EncodeAndSubmit");
if (!IsValid() || !render_pass->IsValid()) {
return false;
}
Expand Down Expand Up @@ -239,6 +239,34 @@ static bool LogMTLCommandBufferErrorIfPresent(id<MTLCommandBuffer> buffer) {
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
22 changes: 17 additions & 5 deletions impeller/renderer/command_buffer.cc
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,9 @@ void CommandBuffer::WaitUntilScheduled() {
return OnWaitUntilScheduled();
}

bool CommandBuffer::SubmitCommandsAsync(
std::shared_ptr<RenderPass>
render_pass // NOLINT(performance-unnecessary-value-param)
) {
TRACE_EVENT0("impeller", "CommandBuffer::SubmitCommandsAsync");
bool CommandBuffer::EncodeAndSubmit(
const std::shared_ptr<RenderPass>& render_pass) {
TRACE_EVENT0("impeller", "CommandBuffer::EncodeAndSubmit");
if (!render_pass->IsValid() || !IsValid()) {
return false;
}
Expand All @@ -51,6 +49,20 @@ bool CommandBuffer::SubmitCommandsAsync(
return SubmitCommands(nullptr);
}

bool CommandBuffer::EncodeAndSubmit(
const std::shared_ptr<BlitPass>& blit_pass,
const std::shared_ptr<Allocator>& allocator) {
TRACE_EVENT0("impeller", "CommandBuffer::EncodeAndSubmit");
if (!blit_pass->IsValid() || !IsValid()) {
return false;
}
if (!blit_pass->EncodeCommands(allocator)) {
return false;
}

return SubmitCommands(nullptr);
}

std::shared_ptr<RenderPass> CommandBuffer::CreateRenderPass(
const RenderTarget& render_target) {
auto pass = OnCreateRenderPass(render_target);
Expand Down
16 changes: 14 additions & 2 deletions impeller/renderer/command_buffer.h
Original file line number Diff line number Diff line change
Expand Up @@ -80,8 +80,20 @@ class CommandBuffer {
///
/// A command buffer may only be committed once.
///
[[nodiscard]] virtual bool SubmitCommandsAsync(
std::shared_ptr<RenderPass> render_pass);
[[nodiscard]] virtual bool EncodeAndSubmit(
const std::shared_ptr<RenderPass>& render_pass);

//----------------------------------------------------------------------------
/// @brief Schedule the command encoded by blit passes within this
/// command buffer on the GPU. The enqueing of this buffer is
/// performed immediately but encoding is pushed to a worker
/// thread if possible.
///
/// A command buffer may only be committed once.
///
[[nodiscard]] virtual bool EncodeAndSubmit(
const std::shared_ptr<BlitPass>& blit_pass,
const std::shared_ptr<Allocator>& allocator);

//----------------------------------------------------------------------------
/// @brief Force execution of pending GPU commands.
Expand Down

0 comments on commit f178e19

Please sign in to comment.