| // 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/context_mtl.h" |
| |
| #include <Foundation/Foundation.h> |
| #include <memory> |
| |
| #include "flutter/fml/concurrent_message_loop.h" |
| #include "flutter/fml/file.h" |
| #include "flutter/fml/logging.h" |
| #include "flutter/fml/paths.h" |
| #include "flutter/fml/synchronization/sync_switch.h" |
| #include "impeller/core/formats.h" |
| #include "impeller/core/sampler_descriptor.h" |
| #include "impeller/renderer/backend/metal/gpu_tracer_mtl.h" |
| #include "impeller/renderer/backend/metal/sampler_library_mtl.h" |
| #include "impeller/renderer/capabilities.h" |
| |
| namespace impeller { |
| |
| static bool DeviceSupportsFramebufferFetch(id<MTLDevice> device) { |
| // The iOS simulator lies about supporting framebuffer fetch. |
| #if FML_OS_IOS_SIMULATOR |
| return false; |
| #else // FML_OS_IOS_SIMULATOR |
| |
| if (@available(macOS 10.15, iOS 13, tvOS 13, *)) { |
| return [device supportsFamily:MTLGPUFamilyApple2]; |
| } |
| // According to |
| // https://developer.apple.com/metal/Metal-Feature-Set-Tables.pdf , Apple2 |
| // corresponds to iOS GPU family 2, which supports A8 devices. |
| #if FML_OS_IOS |
| return [device supportsFeatureSet:MTLFeatureSet_iOS_GPUFamily2_v1]; |
| #else |
| return false; |
| #endif // FML_OS_IOS |
| #endif // FML_OS_IOS_SIMULATOR |
| } |
| |
| static bool DeviceSupportsComputeSubgroups(id<MTLDevice> device) { |
| bool supports_subgroups = false; |
| // Refer to the "SIMD-scoped reduction operations" feature in the table |
| // below: https://developer.apple.com/metal/Metal-Feature-Set-Tables.pdf |
| if (@available(ios 13.0, tvos 13.0, macos 10.15, *)) { |
| supports_subgroups = [device supportsFamily:MTLGPUFamilyApple7] || |
| [device supportsFamily:MTLGPUFamilyMac2]; |
| } |
| return supports_subgroups; |
| } |
| |
| static std::unique_ptr<Capabilities> InferMetalCapabilities( |
| id<MTLDevice> device, |
| PixelFormat color_format) { |
| return CapabilitiesBuilder() |
| .SetSupportsOffscreenMSAA(true) |
| .SetSupportsSSBO(true) |
| .SetSupportsBufferToTextureBlits(true) |
| .SetSupportsTextureToTextureBlits(true) |
| .SetSupportsDecalSamplerAddressMode(true) |
| .SetSupportsFramebufferFetch(DeviceSupportsFramebufferFetch(device)) |
| .SetDefaultColorFormat(color_format) |
| .SetDefaultStencilFormat(PixelFormat::kS8UInt) |
| .SetDefaultDepthStencilFormat(PixelFormat::kD32FloatS8UInt) |
| .SetSupportsCompute(true) |
| .SetSupportsComputeSubgroups(DeviceSupportsComputeSubgroups(device)) |
| .SetSupportsReadFromResolve(true) |
| .SetSupportsDeviceTransientTextures(true) |
| .Build(); |
| } |
| |
| ContextMTL::ContextMTL( |
| id<MTLDevice> device, |
| id<MTLCommandQueue> command_queue, |
| NSArray<id<MTLLibrary>>* shader_libraries, |
| std::shared_ptr<const fml::SyncSwitch> is_gpu_disabled_sync_switch) |
| : device_(device), |
| command_queue_(command_queue), |
| is_gpu_disabled_sync_switch_(std::move(is_gpu_disabled_sync_switch)) { |
| // Validate device. |
| if (!device_) { |
| VALIDATION_LOG << "Could not set up valid Metal device."; |
| return; |
| } |
| |
| 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, ¶m)) { |
| param.sched_priority = 50; |
| pthread_setschedparam(thread, policy, ¶m); |
| } |
| }); |
| } |
| |
| // Setup the shader library. |
| { |
| if (shader_libraries == nil) { |
| VALIDATION_LOG << "Shader libraries were null."; |
| return; |
| } |
| |
| // std::make_shared disallowed because of private friend ctor. |
| auto library = std::shared_ptr<ShaderLibraryMTL>( |
| new ShaderLibraryMTL(shader_libraries)); |
| if (!library->IsValid()) { |
| VALIDATION_LOG << "Could not create valid Metal shader library."; |
| return; |
| } |
| shader_library_ = std::move(library); |
| } |
| |
| // Setup the pipeline library. |
| { |
| pipeline_library_ = |
| std::shared_ptr<PipelineLibraryMTL>(new PipelineLibraryMTL(device_)); |
| } |
| |
| // Setup the sampler library. |
| { |
| sampler_library_ = |
| std::shared_ptr<SamplerLibraryMTL>(new SamplerLibraryMTL(device_)); |
| } |
| |
| // Setup the resource allocator. |
| { |
| resource_allocator_ = std::shared_ptr<AllocatorMTL>( |
| new AllocatorMTL(device_, "Impeller Permanents Allocator")); |
| if (!resource_allocator_) { |
| VALIDATION_LOG << "Could not set up the resource allocator."; |
| return; |
| } |
| } |
| |
| device_capabilities_ = |
| InferMetalCapabilities(device_, PixelFormat::kB8G8R8A8UNormInt); |
| #ifdef IMPELLER_DEBUG |
| gpu_tracer_ = std::make_shared<GPUTracerMTL>(); |
| #endif // IMPELLER_DEBUG |
| is_valid_ = true; |
| } |
| |
| static NSArray<id<MTLLibrary>>* MTLShaderLibraryFromFilePaths( |
| id<MTLDevice> device, |
| const std::vector<std::string>& libraries_paths) { |
| NSMutableArray<id<MTLLibrary>>* found_libraries = [NSMutableArray array]; |
| for (const auto& library_path : libraries_paths) { |
| if (!fml::IsFile(library_path)) { |
| VALIDATION_LOG << "Shader library does not exist at path '" |
| << library_path << "'"; |
| return nil; |
| } |
| NSError* shader_library_error = nil; |
| auto library = [device newLibraryWithFile:@(library_path.c_str()) |
| error:&shader_library_error]; |
| if (!library) { |
| FML_LOG(ERROR) << "Could not create shader library: " |
| << shader_library_error.localizedDescription.UTF8String; |
| return nil; |
| } |
| [found_libraries addObject:library]; |
| } |
| return found_libraries; |
| } |
| |
| static NSArray<id<MTLLibrary>>* MTLShaderLibraryFromFileData( |
| id<MTLDevice> device, |
| const std::vector<std::shared_ptr<fml::Mapping>>& libraries_data, |
| const std::string& label) { |
| NSMutableArray<id<MTLLibrary>>* found_libraries = [NSMutableArray array]; |
| for (const auto& library_data : libraries_data) { |
| if (library_data == nullptr) { |
| FML_LOG(ERROR) << "Shader library data was null."; |
| return nil; |
| } |
| |
| __block auto data = library_data; |
| |
| auto dispatch_data = |
| ::dispatch_data_create(library_data->GetMapping(), // buffer |
| library_data->GetSize(), // size |
| dispatch_get_main_queue(), // queue |
| ^() { |
| // We just need a reference. |
| data.reset(); |
| } // destructor |
| ); |
| if (!dispatch_data) { |
| FML_LOG(ERROR) << "Could not wrap shader data in dispatch data."; |
| return nil; |
| } |
| |
| NSError* shader_library_error = nil; |
| auto library = [device newLibraryWithData:dispatch_data |
| error:&shader_library_error]; |
| if (!library) { |
| FML_LOG(ERROR) << "Could not create shader library: " |
| << shader_library_error.localizedDescription.UTF8String; |
| return nil; |
| } |
| if (!label.empty()) { |
| library.label = @(label.c_str()); |
| } |
| [found_libraries addObject:library]; |
| } |
| return found_libraries; |
| } |
| |
| static id<MTLDevice> CreateMetalDevice() { |
| return ::MTLCreateSystemDefaultDevice(); |
| } |
| |
| static id<MTLCommandQueue> CreateMetalCommandQueue(id<MTLDevice> device) { |
| auto command_queue = device.newCommandQueue; |
| if (!command_queue) { |
| VALIDATION_LOG << "Could not set up the command queue."; |
| return nullptr; |
| } |
| command_queue.label = @"Impeller Command Queue"; |
| return command_queue; |
| } |
| |
| std::shared_ptr<ContextMTL> ContextMTL::Create( |
| const std::vector<std::string>& shader_library_paths, |
| std::shared_ptr<const fml::SyncSwitch> is_gpu_disabled_sync_switch) { |
| auto device = CreateMetalDevice(); |
| auto command_queue = CreateMetalCommandQueue(device); |
| if (!command_queue) { |
| return nullptr; |
| } |
| auto context = std::shared_ptr<ContextMTL>(new ContextMTL( |
| device, command_queue, |
| MTLShaderLibraryFromFilePaths(device, shader_library_paths), |
| std::move(is_gpu_disabled_sync_switch))); |
| if (!context->IsValid()) { |
| FML_LOG(ERROR) << "Could not create Metal context."; |
| return nullptr; |
| } |
| return context; |
| } |
| |
| std::shared_ptr<ContextMTL> ContextMTL::Create( |
| const std::vector<std::shared_ptr<fml::Mapping>>& shader_libraries_data, |
| std::shared_ptr<const fml::SyncSwitch> is_gpu_disabled_sync_switch, |
| const std::string& library_label) { |
| auto device = CreateMetalDevice(); |
| auto command_queue = CreateMetalCommandQueue(device); |
| if (!command_queue) { |
| return nullptr; |
| } |
| auto context = std::shared_ptr<ContextMTL>( |
| new ContextMTL(device, command_queue, |
| MTLShaderLibraryFromFileData(device, shader_libraries_data, |
| library_label), |
| std::move(is_gpu_disabled_sync_switch))); |
| if (!context->IsValid()) { |
| FML_LOG(ERROR) << "Could not create Metal context."; |
| return nullptr; |
| } |
| return context; |
| } |
| |
| std::shared_ptr<ContextMTL> ContextMTL::Create( |
| id<MTLDevice> device, |
| id<MTLCommandQueue> command_queue, |
| const std::vector<std::shared_ptr<fml::Mapping>>& shader_libraries_data, |
| std::shared_ptr<const fml::SyncSwitch> is_gpu_disabled_sync_switch, |
| const std::string& library_label) { |
| auto context = std::shared_ptr<ContextMTL>( |
| new ContextMTL(device, command_queue, |
| MTLShaderLibraryFromFileData(device, shader_libraries_data, |
| library_label), |
| std::move(is_gpu_disabled_sync_switch))); |
| if (!context->IsValid()) { |
| FML_LOG(ERROR) << "Could not create Metal context."; |
| return nullptr; |
| } |
| return context; |
| } |
| |
| ContextMTL::~ContextMTL() { |
| is_gpu_disabled_sync_switch_->RemoveObserver(sync_switch_observer_.get()); |
| } |
| |
| Context::BackendType ContextMTL::GetBackendType() const { |
| return Context::BackendType::kMetal; |
| } |
| |
| // |Context| |
| std::string ContextMTL::DescribeGpuModel() const { |
| return std::string([[device_ name] UTF8String]); |
| } |
| |
| // |Context| |
| bool ContextMTL::IsValid() const { |
| return is_valid_; |
| } |
| |
| // |Context| |
| std::shared_ptr<ShaderLibrary> ContextMTL::GetShaderLibrary() const { |
| return shader_library_; |
| } |
| |
| // |Context| |
| std::shared_ptr<PipelineLibrary> ContextMTL::GetPipelineLibrary() const { |
| return pipeline_library_; |
| } |
| |
| // |Context| |
| std::shared_ptr<SamplerLibrary> ContextMTL::GetSamplerLibrary() const { |
| return sampler_library_; |
| } |
| |
| // |Context| |
| std::shared_ptr<CommandBuffer> ContextMTL::CreateCommandBuffer() const { |
| return CreateCommandBufferInQueue(command_queue_); |
| } |
| |
| // |Context| |
| void ContextMTL::Shutdown() { |
| raster_message_loop_.reset(); |
| } |
| |
| #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_; |
| } |
| |
| std::shared_ptr<CommandBuffer> ContextMTL::CreateCommandBufferInQueue( |
| id<MTLCommandQueue> queue) const { |
| if (!IsValid()) { |
| return nullptr; |
| } |
| |
| auto buffer = std::shared_ptr<CommandBufferMTL>( |
| new CommandBufferMTL(weak_from_this(), queue)); |
| if (!buffer->IsValid()) { |
| return nullptr; |
| } |
| return buffer; |
| } |
| |
| std::shared_ptr<Allocator> ContextMTL::GetResourceAllocator() const { |
| return resource_allocator_; |
| } |
| |
| id<MTLDevice> ContextMTL::GetMTLDevice() const { |
| return device_; |
| } |
| |
| const std::shared_ptr<const Capabilities>& ContextMTL::GetCapabilities() const { |
| return device_capabilities_; |
| } |
| |
| // |Context| |
| bool ContextMTL::UpdateOffscreenLayerPixelFormat(PixelFormat format) { |
| device_capabilities_ = InferMetalCapabilities(device_, format); |
| return true; |
| } |
| |
| id<MTLCommandBuffer> ContextMTL::CreateMTLCommandBuffer( |
| const std::string& label) const { |
| auto buffer = [command_queue_ commandBuffer]; |
| if (!label.empty()) { |
| [buffer setLabel:@(label.data())]; |
| } |
| return buffer; |
| } |
| |
| void ContextMTL::StoreTaskForGPU(const std::function<void()>& task) { |
| tasks_awaiting_gpu_.emplace_back(task); |
| while (tasks_awaiting_gpu_.size() > kMaxTasksAwaitingGPU) { |
| tasks_awaiting_gpu_.front()(); |
| tasks_awaiting_gpu_.pop_front(); |
| } |
| } |
| |
| void ContextMTL::FlushTasksAwaitingGPU() { |
| for (const auto& task : tasks_awaiting_gpu_) { |
| task(); |
| } |
| tasks_awaiting_gpu_.clear(); |
| } |
| |
| ContextMTL::SyncSwitchObserver::SyncSwitchObserver(ContextMTL& parent) |
| : parent_(parent) {} |
| |
| void ContextMTL::SyncSwitchObserver::OnSyncSwitchUpdate(bool new_is_disabled) { |
| if (!new_is_disabled) { |
| parent_.FlushTasksAwaitingGPU(); |
| } |
| } |
| |
| } // namespace impeller |