diff --git a/ci/licenses_golden/excluded_files b/ci/licenses_golden/excluded_files index e518702b32318..9a55770ceb142 100644 --- a/ci/licenses_golden/excluded_files +++ b/ci/licenses_golden/excluded_files @@ -162,6 +162,7 @@ ../../../flutter/impeller/image/README.md ../../../flutter/impeller/playground ../../../flutter/impeller/renderer/backend/gles/test +../../../flutter/impeller/renderer/backend/metal/texture_mtl_unittests.mm ../../../flutter/impeller/renderer/backend/vulkan/blit_command_vk_unittests.cc ../../../flutter/impeller/renderer/backend/vulkan/command_encoder_vk_unittests.cc ../../../flutter/impeller/renderer/backend/vulkan/command_pool_vk_unittests.cc diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter index 27569036ed116..a80638af22c21 100644 --- a/ci/licenses_golden/licenses_flutter +++ b/ci/licenses_golden/licenses_flutter @@ -3317,6 +3317,8 @@ ORIGIN: ../../../flutter/impeller/renderer/backend/metal/formats_mtl.h + ../../. ORIGIN: ../../../flutter/impeller/renderer/backend/metal/formats_mtl.mm + ../../../flutter/LICENSE ORIGIN: ../../../flutter/impeller/renderer/backend/metal/gpu_tracer_mtl.h + ../../../flutter/LICENSE 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/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 @@ -6081,6 +6083,8 @@ FILE: ../../../flutter/impeller/renderer/backend/metal/formats_mtl.h FILE: ../../../flutter/impeller/renderer/backend/metal/formats_mtl.mm 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/pipeline_library_mtl.h FILE: ../../../flutter/impeller/renderer/backend/metal/pipeline_library_mtl.mm FILE: ../../../flutter/impeller/renderer/backend/metal/pipeline_mtl.h diff --git a/impeller/BUILD.gn b/impeller/BUILD.gn index 2eaab0e2b76a8..3237235fe9154 100644 --- a/impeller/BUILD.gn +++ b/impeller/BUILD.gn @@ -87,6 +87,7 @@ impeller_component("impeller_unittests") { "core:allocator_unittests", "display_list:skia_conversions_unittests", "geometry:geometry_unittests", + "renderer/backend/metal:metal_unittests", "runtime_stage:runtime_stage_unittests", "scene/importer:importer_unittests", "shader_archive:shader_archive_unittests", diff --git a/impeller/renderer/backend/metal/BUILD.gn b/impeller/renderer/backend/metal/BUILD.gn index 254cae97f063a..e038782fb4ec2 100644 --- a/impeller/renderer/backend/metal/BUILD.gn +++ b/impeller/renderer/backend/metal/BUILD.gn @@ -26,6 +26,8 @@ impeller_component("metal") { "formats_mtl.mm", "gpu_tracer_mtl.h", "gpu_tracer_mtl.mm", + "lazy_drawable_holder.h", + "lazy_drawable_holder.mm", "pipeline_library_mtl.h", "pipeline_library_mtl.mm", "pipeline_mtl.h", @@ -57,3 +59,19 @@ impeller_component("metal") { frameworks = [ "Metal.framework" ] } + +impeller_component("metal_unittests") { + testonly = true + + sources = [ "texture_mtl_unittests.mm" ] + + deps = [ + ":metal", + "//flutter/testing:testing_lib", + ] + + frameworks = [ + "AppKit.framework", + "QuartzCore.framework", + ] +} diff --git a/impeller/renderer/backend/metal/allocator_mtl.mm b/impeller/renderer/backend/metal/allocator_mtl.mm index 76e3083b7fbcd..150576699df62 100644 --- a/impeller/renderer/backend/metal/allocator_mtl.mm +++ b/impeller/renderer/backend/metal/allocator_mtl.mm @@ -217,7 +217,7 @@ static MTLStorageMode ToMTLStorageMode(StorageMode mode, if (!texture) { return nullptr; } - return std::make_shared(desc, texture); + return TextureMTL::Create(desc, texture); } uint16_t AllocatorMTL::MinimumBytesPerRow(PixelFormat format) const { diff --git a/impeller/renderer/backend/metal/device_buffer_mtl.mm b/impeller/renderer/backend/metal/device_buffer_mtl.mm index a8534c9a5da2a..860e216264975 100644 --- a/impeller/renderer/backend/metal/device_buffer_mtl.mm +++ b/impeller/renderer/backend/metal/device_buffer_mtl.mm @@ -50,7 +50,7 @@ if (!texture) { return nullptr; } - return std::make_shared(descriptor, texture); + return TextureMTL::Create(descriptor, texture); } [[nodiscard]] bool DeviceBufferMTL::OnCopyHostBuffer(const uint8_t* source, diff --git a/impeller/renderer/backend/metal/lazy_drawable_holder.h b/impeller/renderer/backend/metal/lazy_drawable_holder.h new file mode 100644 index 0000000000000..5062e453e5d00 --- /dev/null +++ b/impeller/renderer/backend/metal/lazy_drawable_holder.h @@ -0,0 +1,30 @@ +// 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. + +#pragma once + +#include + +#include +#include "impeller/core/texture_descriptor.h" +#include "impeller/renderer/backend/metal/texture_mtl.h" + +@protocol CAMetalDrawable; +@class CAMetalLayer; + +namespace impeller { + +/// @brief Create a deferred drawable from a CAMetalLayer. +std::shared_future> GetDrawableDeferred( + CAMetalLayer* layer); + +/// @brief Create a TextureMTL from a deferred drawable. +/// +/// This function is safe to call multiple times and will only call +/// nextDrawable once. +std::shared_ptr CreateTextureFromDrawableFuture( + TextureDescriptor desc, + const std::shared_future>& drawble_future); + +} // namespace impeller diff --git a/impeller/renderer/backend/metal/lazy_drawable_holder.mm b/impeller/renderer/backend/metal/lazy_drawable_holder.mm new file mode 100644 index 0000000000000..b847791a288b1 --- /dev/null +++ b/impeller/renderer/backend/metal/lazy_drawable_holder.mm @@ -0,0 +1,51 @@ +// 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/lazy_drawable_holder.h" + +#include +#include +#include + +#include "flutter/fml/trace_event.h" +#include "impeller/base/validation.h" + +namespace impeller { + +#pragma GCC diagnostic push +// Disable the diagnostic for iOS Simulators. Metal without emulation isn't +// available prior to iOS 13 and that's what the simulator headers say when +// support for CAMetalLayer begins. CAMetalLayer is available on iOS 8.0 and +// above which is well below Flutters support level. +#pragma GCC diagnostic ignored "-Wunguarded-availability-new" + +std::shared_future> GetDrawableDeferred( + CAMetalLayer* layer) { + auto future = + std::async(std::launch::deferred, [layer]() -> id { + id current_drawable = nil; + { + TRACE_EVENT0("impeller", "WaitForNextDrawable"); + current_drawable = [layer nextDrawable]; + } + if (!current_drawable) { + VALIDATION_LOG << "Could not acquire current drawable."; + return nullptr; + } + return current_drawable; + }); + return std::shared_future>(std::move(future)); +} + +std::shared_ptr CreateTextureFromDrawableFuture( + TextureDescriptor desc, + const std::shared_future>& drawble_future) { + return std::make_shared( + desc, [drawble_future]() { return drawble_future.get().texture; }, + /*wrapped=*/false, /*drawable=*/true); +} + +#pragma GCC diagnostic pop + +} // namespace impeller diff --git a/impeller/renderer/backend/metal/surface_mtl.mm b/impeller/renderer/backend/metal/surface_mtl.mm index beb900a89be13..1d73324e3f734 100644 --- a/impeller/renderer/backend/metal/surface_mtl.mm +++ b/impeller/renderer/backend/metal/surface_mtl.mm @@ -79,7 +79,7 @@ resolve_tex_desc.compression_type = CompressionType::kLossy; resolve_tex = allocator.CreateTexture(resolve_tex_desc); } else { - resolve_tex = std::make_shared(resolve_tex_desc, texture); + resolve_tex = TextureMTL::Create(resolve_tex_desc, texture); } if (!resolve_tex) { diff --git a/impeller/renderer/backend/metal/texture_mtl.h b/impeller/renderer/backend/metal/texture_mtl.h index 9fa5512973c16..632b0491e56b3 100644 --- a/impeller/renderer/backend/metal/texture_mtl.h +++ b/impeller/renderer/backend/metal/texture_mtl.h @@ -15,15 +15,23 @@ namespace impeller { class TextureMTL final : public Texture, public BackendCast { public: + /// @brief This callback needs to always return the same texture when called + /// multiple times. + using AcquireTextureProc = std::function()>; + TextureMTL(TextureDescriptor desc, - id texture, - bool wrapped = false); + const AcquireTextureProc& aquire_proc, + bool wrapped = false, + bool drawable = false); static std::shared_ptr Wrapper( TextureDescriptor desc, id texture, std::function deletion_proc = nullptr); + static std::shared_ptr Create(TextureDescriptor desc, + id texture); + // |Texture| ~TextureMTL() override; @@ -31,12 +39,19 @@ class TextureMTL final : public Texture, bool IsWrapped() const; + /// @brief Whether or not this texture is wrapping a Metal drawable. + bool IsDrawable() const; + + // |Texture| + bool IsValid() const override; + bool GenerateMipmap(id encoder); private: - id texture_ = nullptr; + AcquireTextureProc aquire_proc_ = {}; bool is_valid_ = false; bool is_wrapped_ = false; + bool is_drawable_ = false; // |Texture| void SetLabel(std::string_view label) override; @@ -49,10 +64,6 @@ class TextureMTL final : public Texture, // |Texture| bool OnSetContents(std::shared_ptr mapping, size_t slice) override; - - // |Texture| - bool IsValid() const override; - // |Texture| ISize GetSize() const override; diff --git a/impeller/renderer/backend/metal/texture_mtl.mm b/impeller/renderer/backend/metal/texture_mtl.mm index a2c2d95481528..d65937e8b9f09 100644 --- a/impeller/renderer/backend/metal/texture_mtl.mm +++ b/impeller/renderer/backend/metal/texture_mtl.mm @@ -3,6 +3,7 @@ // found in the LICENSE file. #include "impeller/renderer/backend/metal/texture_mtl.h" +#include #include "impeller/base/validation.h" #include "impeller/core/texture_descriptor.h" @@ -17,12 +18,13 @@ } TextureMTL::TextureMTL(TextureDescriptor p_desc, - id texture, - bool wrapped) - : Texture(p_desc), texture_(texture) { + const AcquireTextureProc& aquire_proc, + bool wrapped, + bool drawable) + : Texture(p_desc), aquire_proc_(aquire_proc), is_drawable_(drawable) { const auto& desc = GetTextureDescriptor(); - if (!desc.IsValid() || !texture_) { + if (!desc.IsValid() || !aquire_proc) { return; } @@ -41,19 +43,29 @@ std::function deletion_proc) { if (deletion_proc) { return std::shared_ptr( - new TextureMTL(desc, texture, true), + new TextureMTL( + desc, [texture]() { return texture; }, true), [deletion_proc = std::move(deletion_proc)](TextureMTL* t) { deletion_proc(); delete t; }); } - return std::shared_ptr(new TextureMTL(desc, texture, true)); + return std::shared_ptr( + new TextureMTL(desc, [texture]() { return texture; }, true)); +} + +std::shared_ptr TextureMTL::Create(TextureDescriptor desc, + id texture) { + return std::make_shared(desc, [texture]() { return texture; }); } TextureMTL::~TextureMTL() = default; void TextureMTL::SetLabel(std::string_view label) { - [texture_ setLabel:@(label.data())]; + if (is_drawable_) { + return; + } + [aquire_proc_() setLabel:@(label.data())]; } // |Texture| @@ -68,7 +80,7 @@ new TextureMTL(desc, texture, true), bool TextureMTL::OnSetContents(const uint8_t* contents, size_t length, size_t slice) { - if (!IsValid() || !contents || is_wrapped_) { + if (!IsValid() || !contents || is_wrapped_ || is_drawable_) { return false; } @@ -81,24 +93,28 @@ new TextureMTL(desc, texture, true), const auto region = MTLRegionMake2D(0u, 0u, desc.size.width, desc.size.height); - [texture_ replaceRegion:region // - mipmapLevel:0u // - slice:slice // - withBytes:contents // - bytesPerRow:desc.GetBytesPerRow() // - bytesPerImage:desc.GetByteSizeOfBaseMipLevel() // + [aquire_proc_() replaceRegion:region // + mipmapLevel:0u // + slice:slice // + withBytes:contents // + bytesPerRow:desc.GetBytesPerRow() // + bytesPerImage:desc.GetByteSizeOfBaseMipLevel() // ]; return true; } ISize TextureMTL::GetSize() const { - return {static_cast(texture_.width), - static_cast(texture_.height)}; + if (is_drawable_) { + return GetTextureDescriptor().size; + } + const auto& texture = aquire_proc_(); + return {static_cast(texture.width), + static_cast(texture.height)}; } id TextureMTL::GetMTLTexture() const { - return texture_; + return aquire_proc_(); } bool TextureMTL::IsValid() const { @@ -109,12 +125,21 @@ new TextureMTL(desc, texture, true), return is_wrapped_; } +bool TextureMTL::IsDrawable() const { + return is_drawable_; +} + bool TextureMTL::GenerateMipmap(id encoder) { - if (!texture_) { + if (is_drawable_) { + return false; + } + + auto texture = aquire_proc_(); + if (!texture) { return false; } - [encoder generateMipmapsForTexture:texture_]; + [encoder generateMipmapsForTexture:texture]; mipmap_generated_ = true; return true; diff --git a/impeller/renderer/backend/metal/texture_mtl_unittests.mm b/impeller/renderer/backend/metal/texture_mtl_unittests.mm new file mode 100644 index 0000000000000..2e263eb0e7db4 --- /dev/null +++ b/impeller/renderer/backend/metal/texture_mtl_unittests.mm @@ -0,0 +1,53 @@ +// 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 "flutter/testing/testing.h" +#include "impeller/core/formats.h" +#include "impeller/core/texture_descriptor.h" +#include "impeller/renderer/backend/metal/formats_mtl.h" +#include "impeller/renderer/backend/metal/lazy_drawable_holder.h" +#include "impeller/renderer/backend/metal/texture_mtl.h" +#include "impeller/renderer/capabilities.h" + +#include +#include + +#include "gtest/gtest.h" + +namespace impeller { +namespace testing { + +TEST(TextureMTL, CreateFromDrawable) { + auto device = MTLCreateSystemDefaultDevice(); + auto layer = [[CAMetalLayer alloc] init]; + layer.device = device; + layer.drawableSize = CGSize{100, 100}; + layer.pixelFormat = ToMTLPixelFormat(PixelFormat::kB8G8R8A8UNormInt); + + TextureDescriptor desc; + desc.size = {100, 100}; + desc.format = PixelFormat::kB8G8R8A8UNormInt; + auto drawable_future = GetDrawableDeferred(layer); + auto drawable_texture = + CreateTextureFromDrawableFuture(desc, drawable_future); + + ASSERT_TRUE(drawable_texture->IsValid()); + EXPECT_TRUE(drawable_texture->IsDrawable()); + + // Spawn a thread and acquire the drawable in the thread. + auto thread = std::thread([&drawable_texture]() { + // Force the drawable to be acquired. + drawable_texture->GetMTLTexture(); + }); + thread.join(); + // Block until drawable is acquired. + EXPECT_TRUE(drawable_future.get() != nil); + // Drawable is cached. + EXPECT_TRUE(drawable_texture->GetMTLTexture() != nil); + // Once more for good measure. + EXPECT_TRUE(drawable_texture->GetMTLTexture() != nil); +} + +} // namespace testing +} // namespace impeller