// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "components/viz/service/display/display_resource_provider_skia.h"

#include <algorithm>
#include <memory>
#include <utility>
#include <vector>

#include "base/containers/flat_set.h"
#include "build/build_config.h"
#include "components/viz/service/display/resource_fence.h"
#include "gpu/command_buffer/service/scheduler_sequence.h"
#include "third_party/skia/include/core/SkColorSpace.h"

namespace viz {

class ScopedAllowGpuAccessForDisplayResourceProvider {
 public:
  ~ScopedAllowGpuAccessForDisplayResourceProvider() = default;

  explicit ScopedAllowGpuAccessForDisplayResourceProvider(
      DisplayResourceProvider* provider) {
    DCHECK(provider->can_access_gpu_thread_);
  }

 private:
  gpu::ScopedAllowScheduleGpuTask allow_gpu_;
};

DisplayResourceProviderSkia::DisplayResourceProviderSkia()
    : DisplayResourceProvider(DisplayResourceProvider::kGpu) {}

DisplayResourceProviderSkia::~DisplayResourceProviderSkia() {
  Destroy();
}

std::vector<ReturnedResourceViz>
DisplayResourceProviderSkia::DeleteAndReturnUnusedResourcesToChildImpl(
    Child& child_info,
    DeleteStyle style,
    const std::vector<ResourceId>& unused) {
  std::vector<ReturnedResourceViz> to_return;
  std::vector<std::unique_ptr<ExternalUseClient::ImageContext>>
      image_contexts_to_return;
  std::vector<ReturnedResourceViz*> external_used_resources;

  // Reserve enough space to avoid re-allocating, so we can keep item pointers
  // for later using.
  to_return.reserve(unused.size());
  image_contexts_to_return.reserve(unused.size());
  external_used_resources.reserve(unused.size());

  DCHECK(external_use_client_);
  std::vector<ResourceId>* batch_return = nullptr;

  for (ResourceId local_id : unused) {
    auto it = resources_.find(local_id);
    CHECK(it != resources_.end());
    ChildResource& resource = it->second;

    if (!can_access_gpu_thread_) {
      // We should always has access to gpu thread during shutdown.
      DCHECK(style != DeleteStyle::FOR_SHUTDOWN);

      // If we don't have access to gpu thread, we can't free it right now.
      if (resource.image_context) {
        if (!batch_return) {
          batch_return = &batched_returning_resources_[child_info.id];
        }
        batch_return->push_back(local_id);
        continue;
      }
    }

    ResourceId child_id = resource.transferable.id;
    DCHECK(child_info.child_to_parent_map.count(child_id));
    DCHECK(resource.is_gpu_resource_type());

    auto can_delete = CanDeleteNow(child_info, resource, style);
    if (can_delete == CanDeleteNowResult::kNo) {
      // Defer this resource deletion.
      resource.marked_for_deletion = true;
      continue;
    }

    if (resource.transferable.synchronization_type ==
        TransferableResource::SynchronizationType::kReleaseFence) {
      // The resource might have never been used.
      if (resource.resource_fence)
        resource.release_fence = resource.resource_fence->GetGpuFenceHandle();
    }

    const bool is_lost = can_delete == CanDeleteNowResult::kYesButLoseResource;

    to_return.emplace_back(child_id, resource.sync_token(),
                           std::move(resource.release_fence),
                           resource.imported_count, is_lost);
    auto& returned = to_return.back();

    if (resource.image_context) {
      image_contexts_to_return.emplace_back(std::move(resource.image_context));
      external_used_resources.push_back(&returned);
    }

    child_info.child_to_parent_map.erase(child_id);
    resource.imported_count = 0;
    resources_.erase(it);
  }

  if (!image_contexts_to_return.empty()) {
    ScopedAllowGpuAccessForDisplayResourceProvider allow_gpu(this);
    gpu::SyncToken sync_token = external_use_client_->ReleaseImageContexts(
        std::move(image_contexts_to_return));
    for (auto* resource : external_used_resources) {
      resource->sync_token = sync_token;
    }
  }

  return to_return;
}

DisplayResourceProviderSkia::LockSetForExternalUse::LockSetForExternalUse(
    DisplayResourceProviderSkia* resource_provider,
    ExternalUseClient* client)
    : resource_provider_(resource_provider) {
  DCHECK(!resource_provider_->external_use_client_);
  DCHECK(client);
  resource_provider_->external_use_client_ = client;
}

DisplayResourceProviderSkia::LockSetForExternalUse::~LockSetForExternalUse() {
  DCHECK(resources_.empty());
}

ExternalUseClient::ImageContext*
DisplayResourceProviderSkia::LockSetForExternalUse::LockResource(
    ResourceId id,
    bool maybe_concurrent_reads,
    bool raw_draw_is_possible) {
  auto it = resource_provider_->resources_.find(id);
  CHECK(it != resource_provider_->resources_.end());

  ChildResource& resource = it->second;
  DCHECK(resource.is_gpu_resource_type());

  if (!resource.locked_for_external_use) {
    DCHECK(!std::ranges::contains(resources_, std::make_pair(id, &resource)));
    resources_.emplace_back(id, &resource);

    if (!resource.image_context) {
      uint32_t client_id =
          resource_provider_->GetSurfaceId(id).frame_sink_id().client_id();
      resource.image_context =
          resource_provider_->external_use_client_->CreateImageContext(
              resource.transferable, maybe_concurrent_reads,
              raw_draw_is_possible, client_id);
    }
    resource.locked_for_external_use = true;

    switch (resource.transferable.synchronization_type) {
      case TransferableResource::SynchronizationType::kGpuCommandsCompleted:
        resource.resource_fence =
            resource_provider_->current_gpu_commands_completed_fence_;
        break;
      case TransferableResource::SynchronizationType::kReleaseFence:
        resource.resource_fence = resource_provider_->current_release_fence_;
        break;
      default:
        break;
    }

    if (resource.resource_fence) {
      resource.resource_fence->set();
      resource.resource_fence->TrackDeferredResource(id);
    }
  }

  DCHECK(std::ranges::contains(resources_, std::make_pair(id, &resource)));
  return resource.image_context.get();
}

void DisplayResourceProviderSkia::LockSetForExternalUse::UnlockResources(
    const gpu::SyncToken& sync_token) {
  DCHECK(sync_token.verified_flush());
  for (const auto& pair : resources_) {
    auto id = pair.first;
    auto* resource = pair.second;
    DCHECK(resource->locked_for_external_use);

    // TODO(penghuang): support software resource.
    DCHECK(resource->is_gpu_resource_type());

    // Update the resource sync token to |sync_token|. When the next frame is
    // being composited, the DeclareUsedResourcesFromChild() will be called with
    // resources belong to every child for the next frame. If the resource is
    // not used by the next frame, the resource will be returned to a child
    // which owns it with the |sync_token|. The child is responsible for issuing
    // a WaitSyncToken GL command with the |sync_token| before reusing it.
    resource->UpdateSyncToken(sync_token);
    resource->locked_for_external_use = false;

    resource_provider_->TryReleaseResource(id, resource);
  }
  resources_.clear();
}

DisplayResourceProviderSkia::ScopedExclusiveReadLockSharedImage::
    ScopedExclusiveReadLockSharedImage(
        DisplayResourceProviderSkia* resource_provider,
        ResourceId resource_id)
    : ScopedReadLockSharedImage(resource_provider, resource_id) {
  ChildResource& resource = *this->resource();
  if (resource.image_context) {
    DCHECK(!resource.locked_for_external_use)
        << "Resource already locked, can't get exclusive lock!";
    DCHECK(resource_provider->can_access_gpu_thread_)
        << "Can't release |image_context| without access to gpu thread";

    std::vector<std::unique_ptr<ExternalUseClient::ImageContext>>
        image_contexts;
    image_contexts.push_back(std::move(resource.image_context));

    gpu::SyncToken sync_token =
        resource_provider->external_use_client_->ReleaseImageContexts(
            std::move(image_contexts));
    resource.UpdateSyncToken(sync_token);
  }
}
DisplayResourceProviderSkia::ScopedExclusiveReadLockSharedImage::
    ~ScopedExclusiveReadLockSharedImage() = default;

DisplayResourceProviderSkia::ScopedExclusiveReadLockSharedImage::
    ScopedExclusiveReadLockSharedImage(
        ScopedExclusiveReadLockSharedImage&& other) = default;

}  // namespace viz
