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

#import "flutter/shell/platform/darwin/graphics/FlutterDarwinContextMetalSkia.h"

#include "flutter/common/graphics/persistent_cache.h"
#include "flutter/fml/logging.h"
#include "flutter/shell/common/context_options.h"
#import "flutter/shell/platform/darwin/common/framework/Headers/FlutterMacros.h"

FLUTTER_ASSERT_ARC

@implementation FlutterDarwinContextMetalSkia

- (instancetype)initWithDefaultMTLDevice {
  id<MTLDevice> device = MTLCreateSystemDefaultDevice();
  return [self initWithMTLDevice:device commandQueue:[device newCommandQueue]];
}

- (instancetype)initWithMTLDevice:(id<MTLDevice>)device
                     commandQueue:(id<MTLCommandQueue>)commandQueue {
  self = [super init];
  if (self != nil) {
    _device = device;

    if (!_device) {
      FML_DLOG(ERROR) << "Could not acquire Metal device.";
      return nil;
    }

    _commandQueue = commandQueue;

    if (!_commandQueue) {
      FML_DLOG(ERROR) << "Could not create Metal command queue.";
      return nil;
    }

    [_commandQueue setLabel:@"Flutter Main Queue"];

    CVReturn cvReturn = CVMetalTextureCacheCreate(kCFAllocatorDefault,  // allocator
                                                  nil,      // cache attributes (nil default)
                                                  _device,  // metal device
                                                  nil,      // texture attributes (nil default)
                                                  &_textureCache  // [out] cache
    );
    if (cvReturn != kCVReturnSuccess) {
      FML_DLOG(ERROR) << "Could not create Metal texture cache.";
      return nil;
    }

    // The devices are in the same "sharegroup" because they share the same device and command
    // queues for now. When the resource context gets its own transfer queue, this will have to be
    // refactored.
    _mainContext = [self createGrContext];
    _resourceContext = [self createGrContext];

    if (!_mainContext || !_resourceContext) {
      FML_DLOG(ERROR) << "Could not create Skia Metal contexts.";
      return nil;
    }

    _resourceContext->setResourceCacheLimit(0u);
  }
  return self;
}

- (sk_sp<GrDirectContext>)createGrContext {
  const auto contextOptions =
      flutter::MakeDefaultContextOptions(flutter::ContextType::kRender, GrBackendApi::kMetal);
  id<MTLDevice> device = _device;
  id<MTLCommandQueue> commandQueue = _commandQueue;
  return [FlutterDarwinContextMetalSkia createGrContext:device commandQueue:commandQueue];
}

+ (sk_sp<GrDirectContext>)createGrContext:(id<MTLDevice>)device
                             commandQueue:(id<MTLCommandQueue>)commandQueue {
  const auto contextOptions =
      flutter::MakeDefaultContextOptions(flutter::ContextType::kRender, GrBackendApi::kMetal);
  // Skia expect arguments to `MakeMetal` transfer ownership of the reference in for release later
  // when the GrDirectContext is collected.
  return GrDirectContext::MakeMetal((__bridge_retained void*)device,
                                    (__bridge_retained void*)commandQueue, contextOptions);
}

- (void)dealloc {
  if (_textureCache) {
    CFRelease(_textureCache);
  }
}

- (FlutterDarwinExternalTextureMetal*)
    createExternalTextureWithIdentifier:(int64_t)textureID
                                texture:(NSObject<FlutterTexture>*)texture {
  return [[FlutterDarwinExternalTextureMetal alloc] initWithTextureCache:_textureCache
                                                               textureID:textureID
                                                                 texture:texture
                                                          enableImpeller:NO];
}

@end
