// Copyright 2018 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/gles2_query_manager.h"

#include <stddef.h>
#include <stdint.h>

#include "base/atomicops.h"
#include "base/check_op.h"
#include "base/functional/bind.h"
#include "base/notreached.h"
#include "base/time/time.h"
#include "gpu/command_buffer/service/error_state.h"
#include "gpu/command_buffer/service/feature_info.h"
#include "gpu/command_buffer/service/gles2_cmd_decoder.h"
#include "ui/gl/gl_bindings.h"
#include "ui/gl/gl_context.h"
#include "ui/gl/gl_fence.h"
#include "ui/gl/gpu_timing.h"

namespace gpu {
namespace gles2 {

namespace {

class AbstractIntegerQuery : public QueryManager::Query {
 public:
  AbstractIntegerQuery(QueryManager* manager,
                       GLenum target,
                       scoped_refptr<gpu::Buffer> buffer,
                       QuerySync* sync);
  void Begin() override;
  void End(base::subtle::Atomic32 submit_count) override;
  void QueryCounter(base::subtle::Atomic32 submit_count) override;
  void Pause() override;
  void Resume() override;
  void Destroy(bool have_context) override;

 protected:
  ~AbstractIntegerQuery() override;
  bool AreAllResultsAvailable();

  // Service side query ids.
  std::vector<GLuint> service_ids_;
};

AbstractIntegerQuery::AbstractIntegerQuery(QueryManager* manager,
                                           GLenum target,
                                           scoped_refptr<gpu::Buffer> buffer,
                                           QuerySync* sync)
    : Query(manager, target, std::move(buffer), sync) {
  GLuint service_id = 0;
  glGenQueries(1, &service_id);
  DCHECK_NE(0u, service_id);
  service_ids_.push_back(service_id);
}

void AbstractIntegerQuery::Begin() {
  MarkAsActive();
  // Delete all but the first one when beginning a new query.
  if (service_ids_.size() > 1) {
    glDeleteQueries(service_ids_.size() - 1, &service_ids_[1]);
    service_ids_.resize(1);
  }
  BeginQueryHelper(target(), service_ids_.back());
}

void AbstractIntegerQuery::End(base::subtle::Atomic32 submit_count) {
  EndQueryHelper(target());
  AddToPendingQueue(submit_count);
}

void AbstractIntegerQuery::QueryCounter(base::subtle::Atomic32 submit_count) {
  NOTREACHED();
}

void AbstractIntegerQuery::Pause() {
  MarkAsPaused();
  EndQueryHelper(target());
}

void AbstractIntegerQuery::Resume() {
  MarkAsActive();

  GLuint service_id = 0;
  glGenQueries(1, &service_id);
  DCHECK_NE(0u, service_id);
  service_ids_.push_back(service_id);
  BeginQueryHelper(target(), service_ids_.back());
}

void AbstractIntegerQuery::Destroy(bool have_context) {
  if (have_context && !IsDeleted()) {
    glDeleteQueries(service_ids_.size(), &service_ids_[0]);
    service_ids_.clear();
    MarkAsDeleted();
  }
}

AbstractIntegerQuery::~AbstractIntegerQuery() = default;

bool AbstractIntegerQuery::AreAllResultsAvailable() {
  GLuint available = 0;
  glGetQueryObjectuiv(service_ids_.back(), GL_QUERY_RESULT_AVAILABLE_EXT,
                      &available);
  return !!available;
}

class BooleanQuery : public AbstractIntegerQuery {
 public:
  BooleanQuery(QueryManager* manager,
               GLenum target,
               scoped_refptr<gpu::Buffer> buffer,
               QuerySync* sync);
  void Process(bool did_finish) override;

 protected:
  ~BooleanQuery() override;
};

BooleanQuery::BooleanQuery(QueryManager* manager,
                           GLenum target,
                           scoped_refptr<gpu::Buffer> buffer,
                           QuerySync* sync)
    : AbstractIntegerQuery(manager, target, std::move(buffer), sync) {}

BooleanQuery::~BooleanQuery() = default;

void BooleanQuery::Process(bool did_finish) {
  if (!AreAllResultsAvailable())
    return;
  for (const GLuint& service_id : service_ids_) {
    GLuint result = 0;
    glGetQueryObjectuiv(service_id, GL_QUERY_RESULT_EXT, &result);
    if (result != 0) {
      MarkAsCompleted(1);
      return;
    }
  }
  MarkAsCompleted(0);
}

class SummedIntegerQuery : public AbstractIntegerQuery {
 public:
  SummedIntegerQuery(QueryManager* manager,
                     GLenum target,
                     scoped_refptr<gpu::Buffer> buffer,
                     QuerySync* sync);
  void Process(bool did_finish) override;

 protected:
  ~SummedIntegerQuery() override;
};

SummedIntegerQuery::SummedIntegerQuery(QueryManager* manager,
                                       GLenum target,
                                       scoped_refptr<gpu::Buffer> buffer,
                                       QuerySync* sync)
    : AbstractIntegerQuery(manager, target, std::move(buffer), sync) {}

SummedIntegerQuery::~SummedIntegerQuery() = default;

void SummedIntegerQuery::Process(bool did_finish) {
  if (!AreAllResultsAvailable())
    return;
  GLuint summed_result = 0;
  for (const GLuint& service_id : service_ids_) {
    GLuint result = 0;
    glGetQueryObjectuiv(service_id, GL_QUERY_RESULT_EXT, &result);
    summed_result += result;
  }
  MarkAsCompleted(summed_result);
}

class CommandLatencyQuery : public QueryManager::Query {
 public:
  CommandLatencyQuery(QueryManager* manager,
                      GLenum target,
                      scoped_refptr<gpu::Buffer> buffer,
                      QuerySync* sync);

  void Begin() override;
  void End(base::subtle::Atomic32 submit_count) override;
  void QueryCounter(base::subtle::Atomic32 submit_count) override;
  void Pause() override;
  void Resume() override;
  void Process(bool did_finish) override;
  void Destroy(bool have_context) override;

 protected:
  ~CommandLatencyQuery() override;
};

CommandLatencyQuery::CommandLatencyQuery(QueryManager* manager,
                                         GLenum target,
                                         scoped_refptr<gpu::Buffer> buffer,
                                         QuerySync* sync)
    : Query(manager, target, std::move(buffer), sync) {}

void CommandLatencyQuery::Begin() {
  MarkAsActive();
}

void CommandLatencyQuery::Pause() {
  MarkAsPaused();
}

void CommandLatencyQuery::Resume() {
  MarkAsActive();
}

void CommandLatencyQuery::End(base::subtle::Atomic32 submit_count) {
  base::TimeDelta now = base::TimeTicks::Now() - base::TimeTicks();
  MarkAsPending(submit_count);
  MarkAsCompleted(now.InMicroseconds());
}

void CommandLatencyQuery::QueryCounter(base::subtle::Atomic32 submit_count) {
  NOTREACHED();
}

void CommandLatencyQuery::Process(bool did_finish) {
  NOTREACHED();
}

void CommandLatencyQuery::Destroy(bool /* have_context */) {
  if (!IsDeleted()) {
    MarkAsDeleted();
  }
}

CommandLatencyQuery::~CommandLatencyQuery() = default;

class AsyncReadPixelsCompletedQuery
    : public GLES2QueryManager::GLES2Query,
      public base::SupportsWeakPtr<AsyncReadPixelsCompletedQuery> {
 public:
  AsyncReadPixelsCompletedQuery(GLES2QueryManager* manager,
                                GLenum target,
                                scoped_refptr<gpu::Buffer> buffer,
                                QuerySync* sync);

  void Begin() override;
  void End(base::subtle::Atomic32 submit_count) override;
  void QueryCounter(base::subtle::Atomic32 submit_count) override;
  void Pause() override;
  void Resume() override;
  void Process(bool did_finish) override;
  void Destroy(bool have_context) override;

 protected:
  void Complete();
  ~AsyncReadPixelsCompletedQuery() override;
};

AsyncReadPixelsCompletedQuery::AsyncReadPixelsCompletedQuery(
    GLES2QueryManager* manager,
    GLenum target,
    scoped_refptr<gpu::Buffer> buffer,
    QuerySync* sync)
    : GLES2Query(manager, target, std::move(buffer), sync) {}

void AsyncReadPixelsCompletedQuery::Begin() {
  MarkAsActive();
}

void AsyncReadPixelsCompletedQuery::Pause() {
  MarkAsPaused();
}

void AsyncReadPixelsCompletedQuery::Resume() {
  MarkAsActive();
}

void AsyncReadPixelsCompletedQuery::End(base::subtle::Atomic32 submit_count) {
  MarkAsPending(submit_count);
  gles2_query_manager()->decoder()->WaitForReadPixels(
      base::BindOnce(&AsyncReadPixelsCompletedQuery::Complete, AsWeakPtr()));
}

void AsyncReadPixelsCompletedQuery::QueryCounter(
    base::subtle::Atomic32 submit_count) {
  NOTREACHED();
}

void AsyncReadPixelsCompletedQuery::Complete() {
  MarkAsCompleted(1);
}

void AsyncReadPixelsCompletedQuery::Process(bool did_finish) {
  NOTREACHED();
}

void AsyncReadPixelsCompletedQuery::Destroy(bool /* have_context */) {
  if (!IsDeleted()) {
    MarkAsDeleted();
  }
}

AsyncReadPixelsCompletedQuery::~AsyncReadPixelsCompletedQuery() = default;

class GetErrorQuery : public GLES2QueryManager::GLES2Query {
 public:
  GetErrorQuery(GLES2QueryManager* manager,
                GLenum target,
                scoped_refptr<gpu::Buffer> buffer,
                QuerySync* sync);

  void Begin() override;
  void End(base::subtle::Atomic32 submit_count) override;
  void QueryCounter(base::subtle::Atomic32 submit_count) override;
  void Pause() override;
  void Resume() override;
  void Process(bool did_finish) override;
  void Destroy(bool have_context) override;

 protected:
  ~GetErrorQuery() override;
};

GetErrorQuery::GetErrorQuery(GLES2QueryManager* manager,
                             GLenum target,
                             scoped_refptr<gpu::Buffer> buffer,
                             QuerySync* sync)
    : GLES2Query(manager, target, std::move(buffer), sync) {}

void GetErrorQuery::Begin() {
  MarkAsActive();
}

void GetErrorQuery::Pause() {
  MarkAsPaused();
}

void GetErrorQuery::Resume() {
  MarkAsActive();
}

void GetErrorQuery::End(base::subtle::Atomic32 submit_count) {
  MarkAsPending(submit_count);
  MarkAsCompleted(
      gles2_query_manager()->decoder()->GetErrorState()->GetGLError());
}

void GetErrorQuery::QueryCounter(base::subtle::Atomic32 submit_count) {
  NOTREACHED();
}

void GetErrorQuery::Process(bool did_finish) {
  NOTREACHED();
}

void GetErrorQuery::Destroy(bool /* have_context */) {
  if (!IsDeleted()) {
    MarkAsDeleted();
  }
}

GetErrorQuery::~GetErrorQuery() = default;

class TimeElapsedQuery : public GLES2QueryManager::GLES2Query {
 public:
  TimeElapsedQuery(GLES2QueryManager* manager,
                   GLenum target,
                   scoped_refptr<gpu::Buffer> buffer,
                   QuerySync* sync);

  // Overridden from QueryManager::Query:
  void Begin() override;
  void End(base::subtle::Atomic32 submit_count) override;
  void QueryCounter(base::subtle::Atomic32 submit_count) override;
  void Pause() override;
  void Resume() override;
  void Process(bool did_finish) override;
  void Destroy(bool have_context) override;

 protected:
  ~TimeElapsedQuery() override;

 private:
  std::unique_ptr<gl::GPUTimer> gpu_timer_;
};

TimeElapsedQuery::TimeElapsedQuery(GLES2QueryManager* manager,
                                   GLenum target,
                                   scoped_refptr<gpu::Buffer> buffer,
                                   QuerySync* sync)
    : GLES2Query(manager, target, std::move(buffer), sync),
      gpu_timer_(manager->CreateGPUTimer(true)) {}

void TimeElapsedQuery::Begin() {
  // Reset the disjoint value before the query begins if it is safe.
  SafelyResetDisjointValue();
  MarkAsActive();
  gpu_timer_->Start();
}

void TimeElapsedQuery::End(base::subtle::Atomic32 submit_count) {
  gpu_timer_->End();
  AddToPendingQueue(submit_count);
}

void TimeElapsedQuery::QueryCounter(base::subtle::Atomic32 submit_count) {
  NOTREACHED();
}

void TimeElapsedQuery::Pause() {
  MarkAsPaused();
}

void TimeElapsedQuery::Resume() {
  MarkAsActive();
}

void TimeElapsedQuery::Process(bool did_finish) {
  if (!gpu_timer_->IsAvailable())
    return;

  // Make sure disjoint value is up to date. This disjoint check is the only one
  // that needs to be done to validate that this query is valid. If a disjoint
  // occurs before the client checks the query value we will just hide the
  // disjoint state since it did not affect this query.
  UpdateDisjointValue();

  const uint64_t nano_seconds =
      gpu_timer_->GetDeltaElapsed() * base::Time::kNanosecondsPerMicrosecond;
  MarkAsCompleted(nano_seconds);
}

void TimeElapsedQuery::Destroy(bool have_context) {
  gpu_timer_->Destroy(have_context);
}

TimeElapsedQuery::~TimeElapsedQuery() = default;

class TimeStampQuery : public GLES2QueryManager::GLES2Query {
 public:
  TimeStampQuery(GLES2QueryManager* manager,
                 GLenum target,
                 scoped_refptr<gpu::Buffer> buffer,
                 QuerySync* sync);

  // Overridden from QueryManager::Query:
  void Begin() override;
  void End(base::subtle::Atomic32 submit_count) override;
  void QueryCounter(base::subtle::Atomic32 submit_count) override;
  void Pause() override;
  void Resume() override;
  void Process(bool did_finish) override;
  void Destroy(bool have_context) override;

 protected:
  ~TimeStampQuery() override;

 private:
  std::unique_ptr<gl::GPUTimer> gpu_timer_;
};

TimeStampQuery::TimeStampQuery(GLES2QueryManager* manager,
                               GLenum target,
                               scoped_refptr<gpu::Buffer> buffer,
                               QuerySync* sync)
    : GLES2Query(manager, target, std::move(buffer), sync),
      gpu_timer_(manager->CreateGPUTimer(false)) {}

void TimeStampQuery::Begin() {
  NOTREACHED();
}

void TimeStampQuery::End(base::subtle::Atomic32 submit_count) {
  NOTREACHED();
}

void TimeStampQuery::Pause() {
  MarkAsPaused();
}

void TimeStampQuery::Resume() {
  MarkAsActive();
}

void TimeStampQuery::QueryCounter(base::subtle::Atomic32 submit_count) {
  // Reset the disjoint value before the query begins if it is safe.
  SafelyResetDisjointValue();
  MarkAsActive();
  // After a timestamp has begun, we will want to continually detect
  // the disjoint value every frame until the context is destroyed.
  BeginContinualDisjointUpdate();

  gpu_timer_->QueryTimeStamp();
  AddToPendingQueue(submit_count);
}

void TimeStampQuery::Process(bool did_finish) {
  if (!gpu_timer_->IsAvailable())
    return;

  // Make sure disjoint value is up to date. This disjoint check is the only one
  // that needs to be done to validate that this query is valid. If a disjoint
  // occurs before the client checks the query value we will just hide the
  // disjoint state since it did not affect this query.
  UpdateDisjointValue();

  int64_t start = 0;
  int64_t end = 0;
  gpu_timer_->GetStartEndTimestamps(&start, &end);
  DCHECK(start == end);

  const uint64_t nano_seconds = start * base::Time::kNanosecondsPerMicrosecond;
  MarkAsCompleted(nano_seconds);
}

void TimeStampQuery::Destroy(bool have_context) {
  if (gpu_timer_.get()) {
    gpu_timer_->Destroy(have_context);
    gpu_timer_.reset();
  }
}

TimeStampQuery::~TimeStampQuery() = default;

}  // namespace

GLES2QueryManager::GLES2Query::GLES2Query(GLES2QueryManager* manager,
                                          GLenum target,
                                          scoped_refptr<gpu::Buffer> buffer,
                                          QuerySync* sync)
    : QueryManager::Query(manager, target, buffer, sync),
      gles2_query_manager_(manager) {}

GLES2QueryManager::GLES2Query::~GLES2Query() = default;

GLES2QueryManager::GLES2QueryManager(GLES2Decoder* decoder,
                                     FeatureInfo* feature_info)
    : decoder_(decoder),
      use_arb_occlusion_query2_for_occlusion_query_boolean_(
          feature_info->feature_flags()
              .use_arb_occlusion_query2_for_occlusion_query_boolean),
      use_arb_occlusion_query_for_occlusion_query_boolean_(
          feature_info->feature_flags()
              .use_arb_occlusion_query_for_occlusion_query_boolean),
      update_disjoints_continually_(false),
      disjoint_notify_shm_id_(-1),
      disjoint_notify_shm_offset_(0),
      disjoints_notified_(0) {
  DCHECK(!(use_arb_occlusion_query_for_occlusion_query_boolean_ &&
           use_arb_occlusion_query2_for_occlusion_query_boolean_));
  DCHECK(decoder);
  gl::GLContext* context = decoder_->GetGLContext();
  if (context) {
    gpu_timing_client_ = context->CreateGPUTimingClient();
  } else {
    gpu_timing_client_ = new gl::GPUTimingClient();
  }
}

GLES2QueryManager::~GLES2QueryManager() = default;

QueryManager::Query* GLES2QueryManager::CreateQuery(
    GLenum target,
    GLuint client_id,
    scoped_refptr<gpu::Buffer> buffer,
    QuerySync* sync) {
  scoped_refptr<Query> query;
  switch (target) {
    case GL_LATENCY_QUERY_CHROMIUM:
      query = new CommandLatencyQuery(this, target, std::move(buffer), sync);
      break;
    case GL_ASYNC_PIXEL_PACK_COMPLETED_CHROMIUM:
      query = new AsyncReadPixelsCompletedQuery(this, target, std::move(buffer),
                                                sync);
      break;
    case GL_GET_ERROR_QUERY_CHROMIUM:
      query = new GetErrorQuery(this, target, std::move(buffer), sync);
      break;
    case GL_TIME_ELAPSED:
      query = new TimeElapsedQuery(this, target, std::move(buffer), sync);
      break;
    case GL_TIMESTAMP:
      query = new TimeStampQuery(this, target, std::move(buffer), sync);
      break;
    case GL_ANY_SAMPLES_PASSED:
    case GL_ANY_SAMPLES_PASSED_CONSERVATIVE:
      query = new BooleanQuery(this, target, std::move(buffer), sync);
      break;
    case GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN:
      query = new SummedIntegerQuery(this, target, std::move(buffer), sync);
      break;
    case GL_SAMPLES_PASSED:
      query = new SummedIntegerQuery(this, target, std::move(buffer), sync);
      break;
    default:
      return QueryManager::CreateQuery(target, client_id, buffer, sync);
  }
  std::pair<QueryMap::iterator, bool> result =
      queries_.insert(std::make_pair(client_id, query));
  DCHECK(result.second);
  return query.get();
}

void GLES2QueryManager::ProcessFrameBeginUpdates() {
  if (update_disjoints_continually_)
    UpdateDisjointValue();
}

error::Error GLES2QueryManager::SetDisjointSync(int32_t shm_id,
                                                uint32_t shm_offset) {
  if (disjoint_notify_shm_id_ != -1 || shm_id == -1)
    return error::kInvalidArguments;

  DisjointValueSync* sync = decoder_->GetSharedMemoryAs<DisjointValueSync*>(
      shm_id, shm_offset, sizeof(*sync));
  if (!sync)
    return error::kOutOfBounds;

  sync->Reset();
  disjoints_notified_ = 0;

  disjoint_notify_shm_id_ = shm_id;
  disjoint_notify_shm_offset_ = shm_offset;
  return error::kNoError;
}

std::unique_ptr<gl::GPUTimer> GLES2QueryManager::CreateGPUTimer(
    bool elapsed_time) {
  return gpu_timing_client_->CreateGPUTimer(elapsed_time);
}

bool GLES2QueryManager::GPUTimingAvailable() {
  return gpu_timing_client_->IsAvailable();
}

GLenum GLES2QueryManager::AdjustTargetForEmulation(GLenum target) {
  switch (target) {
    case GL_ANY_SAMPLES_PASSED_CONSERVATIVE_EXT:
    case GL_ANY_SAMPLES_PASSED_EXT:
      if (use_arb_occlusion_query2_for_occlusion_query_boolean_) {
        // ARB_occlusion_query2 does not have a
        // GL_ANY_SAMPLES_PASSED_CONSERVATIVE_EXT
        // target.
        target = GL_ANY_SAMPLES_PASSED_EXT;
      } else if (use_arb_occlusion_query_for_occlusion_query_boolean_) {
        // ARB_occlusion_query does not have a
        // GL_ANY_SAMPLES_PASSED_EXT
        // target.
        target = GL_SAMPLES_PASSED_ARB;
      }
      break;
    default:
      break;
  }
  return target;
}

void GLES2QueryManager::UpdateDisjointValue() {
  if (disjoint_notify_shm_id_ != -1) {
    if (gpu_timing_client_->CheckAndResetTimerErrors()) {
      disjoints_notified_++;

      DisjointValueSync* sync = decoder_->GetSharedMemoryAs<DisjointValueSync*>(
          disjoint_notify_shm_id_, disjoint_notify_shm_offset_, sizeof(*sync));
      if (!sync) {
        // Shared memory does not seem to be valid, ignore the shm id/offset.
        disjoint_notify_shm_id_ = -1;
        disjoint_notify_shm_offset_ = 0;
      } else {
        sync->SetDisjointCount(disjoints_notified_);
      }
    }
  }
}

void GLES2QueryManager::SafelyResetDisjointValue() {
  // It is only safe to reset the disjoint value is there is no active
  // elapsed timer and we are not continually updating the disjoint value.
  if (!update_disjoints_continually_ && !GetActiveQuery(GL_TIME_ELAPSED)) {
    // Reset the error state without storing the result.
    gpu_timing_client_->CheckAndResetTimerErrors();
  }
}

}  // namespace gles2
}  // namespace gpu