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

#include "ui/gfx/gpu_fence_handle.h"
#include <atomic>
#include <cstddef>

#include "base/debug/alias.h"
#include "base/feature_list.h"
#include "base/files/scoped_file.h"
#include "base/logging.h"
#include "base/memory/scoped_refptr.h"
#include "base/notreached.h"
#include "build/build_config.h"
#include "ui/gfx/switches.h"

#if BUILDFLAG(IS_POSIX)
#include <unistd.h>
#include "base/posix/eintr_wrapper.h"
#endif

#if BUILDFLAG(IS_FUCHSIA)
#include "base/fuchsia/fuchsia_logging.h"
#endif

#if BUILDFLAG(IS_WIN)
#include <windows.h>

#include "base/process/process_handle.h"
#endif

namespace {
std::atomic<uint32_t> g_num_clones_counter{0};

bool IsEnabledUseSmartRefForGPUFenceHandle() {
  static bool is_enabled =
      base::FeatureList::IsEnabled(features::kUseSmartRefForGPUFenceHandle);
  return is_enabled;
}  // namespace

gfx::GpuFenceHandle::ScopedPlatformFence PlatformDuplicate(
    const gfx::GpuFenceHandle::ScopedPlatformFence& scoped_fence) {
  g_num_clones_counter++;
#if BUILDFLAG(IS_POSIX)
  return base::ScopedFD(HANDLE_EINTR(dup(scoped_fence.get())));
#elif BUILDFLAG(IS_FUCHSIA)
  zx::event temp_event;
  zx_status_t status =
      scoped_fence.duplicate(ZX_RIGHT_SAME_RIGHTS, &temp_event);
  if (status != ZX_OK) {
    ZX_DLOG(ERROR, status) << "zx_handle_duplicate";
    return gfx::GpuFenceHandle::ScopedPlatformFence();
  }
  return temp_event;
#elif BUILDFLAG(IS_WIN)
  const base::ProcessHandle process = ::GetCurrentProcess();
  HANDLE duplicated_handle = INVALID_HANDLE_VALUE;
  const BOOL result =
      ::DuplicateHandle(process, scoped_fence.Get(), process,
                        &duplicated_handle, 0, FALSE, DUPLICATE_SAME_ACCESS);
  if (!result) {
    const DWORD last_error = ::GetLastError();
    base::debug::Alias(&last_error);
    NOTREACHED();
  }
  return base::win::ScopedHandle(duplicated_handle);
#else
  NOTREACHED();
#endif
}

}  // namespace

namespace gfx {

GpuFenceHandle::GpuFenceHandle() = default;

GpuFenceHandle::GpuFenceHandle(GpuFenceHandle&& other)
    : smart_fence_(std::move(other.smart_fence_)) {}

GpuFenceHandle& GpuFenceHandle::operator=(GpuFenceHandle&& other) {
  if (this != &other) {
    smart_fence_ = std::move(other.smart_fence_);
  }
  return *this;
}

void GpuFenceHandle::Reset() {
  smart_fence_.reset();
}

GpuFenceHandle::~GpuFenceHandle() = default;

bool GpuFenceHandle::is_null() const {
  if (!smart_fence_.get()) {
    return true;
  }

#if BUILDFLAG(IS_POSIX) || BUILDFLAG(IS_FUCHSIA)
  return !smart_fence_.get()->scoped_fence_.is_valid();
#elif BUILDFLAG(IS_WIN)
  return !smart_fence_.get()->scoped_fence_.is_valid();
#else
  return true;
#endif
}

GpuFenceHandle::RefCountedScopedFence::RefCountedScopedFence(
    ScopedPlatformFence scoped_fence)
    : scoped_fence_(std::move(scoped_fence)) {}

GpuFenceHandle::RefCountedScopedFence::~RefCountedScopedFence() = default;

#if BUILDFLAG(IS_POSIX)
int GpuFenceHandle::Peek() const {
  return is_null() ? base::ScopedFD().get()
                   : smart_fence_.get()->scoped_fence_.get();
}
#elif BUILDFLAG(IS_WIN)
HANDLE GpuFenceHandle::Peek() const {
  return is_null() ? INVALID_HANDLE_VALUE
                   : smart_fence_.get()->scoped_fence_.Get();
}
#endif

void GpuFenceHandle::Adopt(ScopedPlatformFence scoped_fence) {
  if (scoped_fence.is_valid()) {
    smart_fence_ =
        base::MakeRefCounted<RefCountedScopedFence>(std::move(scoped_fence));
  } else {
    Reset();
  }
}

GpuFenceHandle::ScopedPlatformFence GpuFenceHandle::Release() {
  if (is_null()) {
    return ScopedPlatformFence();
  }

  GpuFenceHandle::ScopedPlatformFence return_fence;

  if (smart_fence_->HasOneRef()) {
    // Sole owner of this FD. Non dup optimization below.
    return_fence = std::move(smart_fence_->scoped_fence_);
  } else {
    DCHECK(IsEnabledUseSmartRefForGPUFenceHandle());
    return_fence = PlatformDuplicate(smart_fence_->scoped_fence_);
  }

  Reset();
  return return_fence;
}

// static
uint32_t GpuFenceHandle::GetAndClearNumberOfClones() {
  return g_num_clones_counter.exchange(0);
}

GpuFenceHandle GpuFenceHandle::Clone() const {
  if (is_null()) {
    return GpuFenceHandle();
  }

  gfx::GpuFenceHandle handle;

  if (IsEnabledUseSmartRefForGPUFenceHandle()) {
    handle.smart_fence_ = this->smart_fence_;
  } else {
    handle.Adopt(PlatformDuplicate(smart_fence_->scoped_fence_));
  }
  return handle;
}

}  // namespace gfx
