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

#include "gpu/command_buffer/service/shared_image/test_image_backing.h"

#include "base/memory/raw_ptr.h"
#include "build/build_config.h"
#include "gpu/command_buffer/common/shared_image_info.h"
#include "gpu/command_buffer/service/shared_context_state.h"
#include "gpu/command_buffer/service/shared_image/shared_image_format_service_utils.h"
#include "gpu/command_buffer/service/shared_image/skia_graphite_dawn_image_representation.h"
#include "skia/ext/legacy_display_globals.h"
#include "third_party/skia/include/core/SkSurface.h"
#include "third_party/skia/include/gpu/ganesh/GrBackendSurface.h"
#include "third_party/skia/include/gpu/ganesh/gl/GrGLBackendSurface.h"
#include "third_party/skia/include/gpu/ganesh/gl/GrGLTypes.h"
#include "third_party/skia/include/gpu/ganesh/mock/GrMockTypes.h"
#include "third_party/skia/include/private/chromium/GrPromiseImageTexture.h"

#if BUILDFLAG(SKIA_USE_DAWN)
#include "gpu/command_buffer/service/dawn_context_provider.h"
#endif

namespace gpu {
namespace {
class TestGLTextureImageRepresentation : public GLTextureImageRepresentation {
 public:
  TestGLTextureImageRepresentation(
      SharedImageManager* manager,
      SharedImageBacking* backing,
      MemoryTypeTracker* tracker,
      const std::vector<raw_ptr<gles2::Texture>>& textures)
      : GLTextureImageRepresentation(manager, backing, tracker),
        textures_(std::move(textures)) {}

  gles2::Texture* GetTexture(size_t plane_index) override {
    DCHECK(backing()->format().IsValidPlaneIndex(plane_index));
    return textures_[plane_index];
  }
  bool BeginAccess(GLenum mode) override {
    return static_cast<TestImageBacking*>(backing())->can_access();
  }
  void EndAccess() override {}

 private:
  std::vector<raw_ptr<gles2::Texture>> textures_;
};

class TestGLTexturePassthroughImageRepresentation
    : public GLTexturePassthroughImageRepresentation {
 public:
  TestGLTexturePassthroughImageRepresentation(
      SharedImageManager* manager,
      SharedImageBacking* backing,
      MemoryTypeTracker* tracker,
      const std::vector<scoped_refptr<gles2::TexturePassthrough>>& textures)
      : GLTexturePassthroughImageRepresentation(manager, backing, tracker),
        textures_(std::move(textures)) {}

  const scoped_refptr<gles2::TexturePassthrough>& GetTexturePassthrough(
      size_t plane_index) override {
    DCHECK(backing()->format().IsValidPlaneIndex(plane_index));
    return textures_[plane_index];
  }
  bool BeginAccess(GLenum mode) override {
    return static_cast<TestImageBacking*>(backing())->can_access();
  }
  void EndAccess() override {}

 private:
  std::vector<scoped_refptr<gles2::TexturePassthrough>> textures_;
};

class TestSkiaImageRepresentation : public SkiaGaneshImageRepresentation {
 public:
  TestSkiaImageRepresentation(
      GrDirectContext* gr_context,
      SharedImageManager* manager,
      SharedImageBacking* backing,
      MemoryTypeTracker* tracker,
      std::vector<sk_sp<GrPromiseImageTexture>> promise_textures)
      : SkiaGaneshImageRepresentation(gr_context, manager, backing, tracker),
        promise_textures_(std::move(promise_textures)) {}

 protected:
  std::vector<sk_sp<SkSurface>> BeginWriteAccess(
      int final_msaa_count,
      const SkSurfaceProps& surface_props,
      const gfx::Rect& update_rect,
      std::vector<GrBackendSemaphore>* begin_semaphores,
      std::vector<GrBackendSemaphore>* end_semaphores,
      std::unique_ptr<skgpu::MutableTextureState>* end_state) override {
    if (!static_cast<TestImageBacking*>(backing())->can_access()) {
      return {};
    }
    SkSurfaceProps props = skia::LegacyDisplayGlobals::GetSkSurfaceProps();
    auto surface = SkSurfaces::Raster(
        SkImageInfo::MakeN32Premul(size().width(), size().height()), &props);
    if (!surface)
      return {};
    return {surface};
  }
  std::vector<sk_sp<GrPromiseImageTexture>> BeginWriteAccess(
      std::vector<GrBackendSemaphore>* begin_semaphores,
      std::vector<GrBackendSemaphore>* end_semaphores,
      std::unique_ptr<skgpu::MutableTextureState>* end_state) override {
    if (!static_cast<TestImageBacking*>(backing())->can_access()) {
      return {};
    }
    return promise_textures_;
  }
  void EndWriteAccess() override {}
  std::vector<sk_sp<GrPromiseImageTexture>> BeginReadAccess(
      std::vector<GrBackendSemaphore>* begin_semaphores,
      std::vector<GrBackendSemaphore>* end_semaphores,
      std::unique_ptr<skgpu::MutableTextureState>* end_state) override {
    if (!static_cast<TestImageBacking*>(backing())->can_access()) {
      return {};
    }
    return promise_textures_;
  }
  void EndReadAccess() override {}

 private:
  std::vector<sk_sp<GrPromiseImageTexture>> promise_textures_;
};

class TestDawnImageRepresentation : public DawnImageRepresentation {
 public:
  TestDawnImageRepresentation(SharedImageManager* manager,
                              SharedImageBacking* backing,
                              MemoryTypeTracker* tracker)
      : DawnImageRepresentation(manager, backing, tracker) {}

  wgpu::Texture BeginAccess(wgpu::TextureUsage usage,
                            wgpu::TextureUsage internal_usage) override {
    if (!static_cast<TestImageBacking*>(backing())->can_access()) {
      return nullptr;
    }

    return wgpu::Texture(reinterpret_cast<WGPUTexture>(203));
  }

  void EndAccess() override {}
};

}  // namespace

TestImageBacking::TestImageBacking(const Mailbox& mailbox,
                                   const SharedImageInfo& si_info,
                                   size_t estimated_size,
                                   GLuint texture_id)
    : SharedImageBacking(mailbox,
                         si_info,
                         estimated_size,
                         /*is_thread_safe=*/false) {
  auto format = si_info.format;
  const int num_textures =
      format.PrefersExternalSampler() ? 1 : format.NumberOfPlanes();
  textures_.reserve(num_textures);
  passthrough_textures_.reserve(num_textures);

  const int num_planes = format.NumberOfPlanes();
  for (int plane = 0; plane < num_planes; ++plane) {
    auto* texture = new gles2::Texture(texture_id + plane);
    texture->SetLightweightRef();
    texture->SetTarget(GL_TEXTURE_2D, 1);
    texture->set_min_filter(GL_LINEAR);
    texture->set_mag_filter(GL_LINEAR);
    texture->set_wrap_t(GL_CLAMP_TO_EDGE);
    texture->set_wrap_s(GL_CLAMP_TO_EDGE);
    GLFormatDesc format_desc =
        GLFormatCaps().ToGLFormatDesc(format, /*plane_index=*/plane);
    gfx::Size plane_size = format.GetPlaneSize(plane, si_info.size);
    texture->SetLevelInfo(GL_TEXTURE_2D, 0, format_desc.image_internal_format,
                          plane_size.width(), plane_size.height(), 1, 0,
                          format_desc.data_format, format_desc.data_type,
                          gfx::Rect());
    texture->SetImmutable(true, true);
    auto passthrough_texture = base::MakeRefCounted<gles2::TexturePassthrough>(
        texture_id + plane, GL_TEXTURE_2D);
    textures_.push_back(texture);
    passthrough_textures_.push_back(std::move(passthrough_texture));
  }
}

TestImageBacking::TestImageBacking(const Mailbox& mailbox,
                                   const SharedImageInfo& si_info,
                                   size_t estimated_size)
    : TestImageBacking(mailbox,
                       si_info,
                       estimated_size,
                       /*texture_id=*/203) {
  // Using a dummy |texture_id|, so lose our context so we don't do anything
  // real with it.
  OnContextLost();
}

TestImageBacking::~TestImageBacking() {
  // Pretend our context is lost to avoid actual cleanup in |textures_| or
  // |passthrough_textures_|.
  GLuint texture_id = service_id();
  for (auto& texture : textures_) {
    texture.ExtractAsDangling()->RemoveLightweightRef(/*have_context=*/false);
  }
  for (auto& passthrough_texture : passthrough_textures_) {
    passthrough_texture->MarkContextLost();
    passthrough_texture.reset();
  }
  if (have_context())
    glDeleteTextures(1, &texture_id);
}

bool TestImageBacking::GetUploadFromMemoryCalledAndReset() {
  return std::exchange(upload_from_memory_called_, false);
}

bool TestImageBacking::GetReadbackToMemoryCalledAndReset() {
  return std::exchange(readback_to_memory_called_, false);
}

SharedImageBackingType TestImageBacking::GetType() const {
  return SharedImageBackingType::kTest;
}

gfx::Rect TestImageBacking::ClearedRect() const {
  return textures_[0]->GetLevelClearedRect(textures_[0]->target(), 0);
}

void TestImageBacking::SetClearedRect(const gfx::Rect& cleared_rect) {
  textures_[0]->SetLevelClearedRect(textures_[0]->target(), 0, cleared_rect);
}

void TestImageBacking::SetPurgeable(bool purgeable) {
  if (purgeable) {
    if (set_purgeable_callback_)
      set_purgeable_callback_.Run(mailbox());
  } else {
    if (set_not_purgeable_callback_)
      set_not_purgeable_callback_.Run(mailbox());
  }
}

bool TestImageBacking::UploadFromMemory(const std::vector<SkPixmap>& pixmap) {
  DCHECK_EQ(format().NumberOfPlanes(), static_cast<int>(pixmap.size()));
  upload_from_memory_called_ = true;
  return upload_from_memory_succeeds_;
}

bool TestImageBacking::ReadbackToMemory(const std::vector<SkPixmap>& pixmaps) {
  DCHECK_EQ(format().NumberOfPlanes(), static_cast<int>(pixmaps.size()));
  readback_to_memory_called_ = true;
  return true;
}

std::unique_ptr<GLTextureImageRepresentation>
TestImageBacking::ProduceGLTexture(SharedImageManager* manager,
                                   MemoryTypeTracker* tracker) {
  return std::make_unique<TestGLTextureImageRepresentation>(manager, this,
                                                            tracker, textures_);
}

std::unique_ptr<GLTexturePassthroughImageRepresentation>
TestImageBacking::ProduceGLTexturePassthrough(SharedImageManager* manager,
                                              MemoryTypeTracker* tracker) {
  return std::make_unique<TestGLTexturePassthroughImageRepresentation>(
      manager, this, tracker, passthrough_textures_);
}

std::unique_ptr<SkiaGaneshImageRepresentation>
TestImageBacking::ProduceSkiaGanesh(
    SharedImageManager* manager,
    MemoryTypeTracker* tracker,
    scoped_refptr<SharedContextState> context_state) {
  DCHECK(!textures_.empty());
  std::vector<sk_sp<GrPromiseImageTexture>> promise_textures;
  const GLFormatCaps caps =
      context_state ? context_state->GetGLFormatCaps() : GLFormatCaps();
  if (format().is_single_plane() || format().PrefersExternalSampler()) {
    GLFormatDesc format_desc =
        format().PrefersExternalSampler()
            ? caps.ToGLFormatDescExternalSampler(format())
            : caps.ToGLFormatDesc(format(), /*plane_index=*/0);
    // TODO(b/346406519): Investigate if possible to change target to
    // GL_TEXTURE_2D.
    auto backend_texture = GrBackendTextures::MakeGL(
        size().width(), size().height(), skgpu::Mipmapped::kNo,
        GrGLTextureInfo{
            GL_TEXTURE_EXTERNAL_OES, textures_[0]->service_id(),
            static_cast<GrGLenum>(format_desc.storage_internal_format)});
    if (!backend_texture.isValid()) {
      return nullptr;
    }

    auto promise_texture = GrPromiseImageTexture::Make(backend_texture);
    if (!promise_texture) {
      return nullptr;
    }
    promise_textures.push_back(std::move(promise_texture));
  } else {
    DCHECK_EQ(format().NumberOfPlanes(), static_cast<int>(textures_.size()));
    for (int plane_index = 0; plane_index < format().NumberOfPlanes();
         plane_index++) {
      // Use the format and size per plane for multiplanar formats.
      gfx::Size plane_size = format().GetPlaneSize(plane_index, size());
      GLFormatDesc format_desc = caps.ToGLFormatDesc(format(), plane_index);
      // TODO(b/346406519): Investigate if possible to change target to
      // GL_TEXTURE_2D.
      auto backend_texture = GrBackendTextures::MakeGL(
          plane_size.width(), plane_size.height(), skgpu::Mipmapped::kNo,
          GrGLTextureInfo{
              GL_TEXTURE_EXTERNAL_OES, textures_[plane_index]->service_id(),
              static_cast<GrGLenum>(format_desc.storage_internal_format)});

      if (!backend_texture.isValid()) {
        return nullptr;
      }

      auto promise_texture = GrPromiseImageTexture::Make(backend_texture);
      if (!promise_texture) {
        return nullptr;
      }
      promise_textures.push_back(std::move(promise_texture));
    }
  }
  return std::make_unique<TestSkiaImageRepresentation>(
      context_state ? context_state->gr_context() : nullptr, manager, this,
      tracker, std::move(promise_textures));
}

std::unique_ptr<DawnImageRepresentation> TestImageBacking::ProduceDawn(
    SharedImageManager* manager,
    MemoryTypeTracker* tracker,
    const wgpu::Device& device,
    wgpu::BackendType backend_type,
    std::vector<wgpu::TextureFormat> view_formats,
    scoped_refptr<SharedContextState> context_state) {
  return std::make_unique<TestDawnImageRepresentation>(manager, this, tracker);
}

std::unique_ptr<SkiaGraphiteImageRepresentation>
TestImageBacking::ProduceSkiaGraphite(
    SharedImageManager* manager,
    MemoryTypeTracker* tracker,
    scoped_refptr<SharedContextState> context_state) {
#if BUILDFLAG(SKIA_USE_DAWN)
  if (context_state->IsGraphiteDawn()) {
    return std::make_unique<SkiaGraphiteDawnImageRepresentation>(
        ProduceDawn(manager, tracker,
                    context_state->dawn_context_provider()->GetDevice(),
                    context_state->dawn_context_provider()->backend_type(),
                    /*view_formats=*/{}, context_state),
        context_state, context_state->gpu_main_graphite_recorder(), manager,
        this, tracker);
  }
#endif
  return nullptr;
}

std::unique_ptr<OverlayImageRepresentation> TestImageBacking::ProduceOverlay(
    SharedImageManager* manager,
    MemoryTypeTracker* tracker) {
  return std::make_unique<TestOverlayImageRepresentation>(manager, this,
                                                          tracker);
}

bool TestOverlayImageRepresentation::BeginReadAccess(
    gfx::GpuFenceHandle& acquire_fence) {
  return true;
}

void TestOverlayImageRepresentation::EndReadAccess(
    gfx::GpuFenceHandle release_fence) {}

#if BUILDFLAG(IS_ANDROID)
std::unique_ptr<base::android::ScopedHardwareBufferFenceSync>
TestOverlayImageRepresentation::GetAHardwareBufferFenceSync() {
  return nullptr;
}
#endif

#if BUILDFLAG(IS_APPLE)
bool TestOverlayImageRepresentation::IsInUseByWindowServer() const {
  return static_cast<TestImageBacking*>(backing())->in_use_by_window_server();
}
#endif  // BUILDFLAG(IS_APPLE)

}  // namespace gpu
