// 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/test_metal_context.h" #include #include #include "flutter/fml/logging.h" #include "flutter/fml/platform/darwin/scoped_nsobject.h" #include "third_party/skia/include/core/SkSurface.h" #include "third_party/skia/include/gpu/ganesh/GrDirectContext.h" #include "third_party/skia/include/gpu/ganesh/mtl/GrMtlBackendContext.h" #include "third_party/skia/include/gpu/ganesh/mtl/GrMtlDirectContext.h" namespace flutter { TestMetalContext::TestMetalContext() { auto device = fml::scoped_nsprotocol{MTLCreateSystemDefaultDevice()}; if (!device) { FML_LOG(ERROR) << "Could not acquire Metal device."; return; } auto command_queue = fml::scoped_nsobject{[device.get() newCommandQueue]}; if (!command_queue) { FML_LOG(ERROR) << "Could not create the default command queue."; return; } [command_queue.get() setLabel:@"Flutter Test Queue"]; GrMtlBackendContext backendContext = {}; // Skia expect arguments to `MakeMetal` transfer ownership of the reference in for release later // when the GrDirectContext is collected. backendContext.fDevice.reset([device.get() retain]); backendContext.fQueue.reset([command_queue.get() retain]); skia_context_ = GrDirectContexts::MakeMetal(backendContext); if (!skia_context_) { FML_LOG(ERROR) << "Could not create the GrDirectContext from the Metal Device " "and command queue."; } device_ = [device.get() retain]; command_queue_ = [command_queue.get() retain]; } TestMetalContext::~TestMetalContext() { std::scoped_lock lock(textures_mutex_); textures_.clear(); if (device_) { [(__bridge id)device_ release]; } if (command_queue_) { [(__bridge id)command_queue_ release]; } } void* TestMetalContext::GetMetalDevice() const { return device_; } void* TestMetalContext::GetMetalCommandQueue() const { return command_queue_; } sk_sp TestMetalContext::GetSkiaContext() const { return skia_context_; } TestMetalContext::TextureInfo TestMetalContext::CreateMetalTexture(const SkISize& size) { std::scoped_lock lock(textures_mutex_); auto texture_descriptor = fml::scoped_nsobject{ [[MTLTextureDescriptor texture2DDescriptorWithPixelFormat:MTLPixelFormatBGRA8Unorm width:size.width() height:size.height() mipmapped:NO] retain]}; // The most pessimistic option and disables all optimizations but allows tests // the most flexible access to the surface. They may read and write to the // surface from shaders or use as a pixel view. texture_descriptor.get().usage = MTLTextureUsageUnknown; if (!texture_descriptor) { FML_CHECK(false) << "Invalid texture descriptor."; return {.texture_id = -1, .texture = nullptr}; } id device = (__bridge id)GetMetalDevice(); sk_cfp texture = sk_cfp{[device newTextureWithDescriptor:texture_descriptor.get()]}; if (!texture) { FML_CHECK(false) << "Could not create texture from texture descriptor."; return {.texture_id = -1, .texture = nullptr}; } const int64_t texture_id = texture_id_ctr_++; textures_[texture_id] = texture; return { .texture_id = texture_id, .texture = texture.get(), }; } // Don't remove the texture from the map here. bool TestMetalContext::Present(int64_t texture_id) { std::scoped_lock lock(textures_mutex_); if (textures_.find(texture_id) == textures_.end()) { return false; } else { return true; } } TestMetalContext::TextureInfo TestMetalContext::GetTextureInfo(int64_t texture_id) { std::scoped_lock lock(textures_mutex_); if (textures_.find(texture_id) == textures_.end()) { FML_CHECK(false) << "Invalid texture id: " << texture_id; return {.texture_id = -1, .texture = nullptr}; } else { return { .texture_id = texture_id, .texture = textures_[texture_id].get(), }; } } } // namespace flutter