// Copyright 2018 The Chromium Authors. All rights reserved.
// 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/sync_query_collection.h"

#include "base/memory/weak_ptr.h"
#include "cc/base/container_util.h"
#include "components/viz/service/display/resource_fence.h"
#include "gpu/GLES2/gl2extchromium.h"
#include "gpu/command_buffer/client/gles2_interface.h"

namespace viz {
namespace {
// Block or crash if the number of pending sync queries reach this high as
// something is seriously wrong on the service side if this happens.
const size_t kMaxPendingSyncQueries = 16;
}  // anonymous namespace

class SyncQuery {
 public:
  explicit SyncQuery(gpu::gles2::GLES2Interface* gl)
      : gl_(gl), query_id_(0u), is_pending_(false), weak_ptr_factory_(this) {
    gl_->GenQueriesEXT(1, &query_id_);
  }
  virtual ~SyncQuery() { gl_->DeleteQueriesEXT(1, &query_id_); }

  scoped_refptr<ResourceFence> Begin() {
    DCHECK(!IsPending());
    // Invalidate weak pointer held by old fence.
    weak_ptr_factory_.InvalidateWeakPtrs();
    // Note: In case the set of drawing commands issued before End() do not
    // depend on the query, defer BeginQueryEXT call until Set() is called and
    // query is required.
    return base::MakeRefCounted<Fence>(weak_ptr_factory_.GetWeakPtr());
  }

  void Set() {
    if (is_pending_)
      return;

    // Note: BeginQueryEXT on GL_COMMANDS_COMPLETED_CHROMIUM is effectively a
    // noop relative to GL, so it doesn't matter where it happens but we still
    // make sure to issue this command when Set() is called (prior to issuing
    // any drawing commands that depend on query), in case some future extension
    // can take advantage of this.
    gl_->BeginQueryEXT(GL_COMMANDS_COMPLETED_CHROMIUM, query_id_);
    is_pending_ = true;
  }

  void End() {
    if (!is_pending_)
      return;

    gl_->EndQueryEXT(GL_COMMANDS_COMPLETED_CHROMIUM);
  }

  bool IsPending() {
    if (!is_pending_)
      return false;

    unsigned result_available = 1;
    gl_->GetQueryObjectuivEXT(query_id_, GL_QUERY_RESULT_AVAILABLE_EXT,
                              &result_available);
    is_pending_ = !result_available;
    return is_pending_;
  }

  void Wait() {
    if (!is_pending_)
      return;

    unsigned result = 0;
    gl_->GetQueryObjectuivEXT(query_id_, GL_QUERY_RESULT_EXT, &result);
    is_pending_ = false;
  }

 private:
  class Fence : public ResourceFence {
   public:
    explicit Fence(base::WeakPtr<SyncQuery> query) : query_(query) {}

    // ResourceFence implementation.
    void Set() override {
      DCHECK(query_);
      query_->Set();
    }
    bool HasPassed() override { return !query_ || !query_->IsPending(); }
    void Wait() override {
      if (query_)
        query_->Wait();
    }

   private:
    ~Fence() override {}

    base::WeakPtr<SyncQuery> query_;

    DISALLOW_COPY_AND_ASSIGN(Fence);
  };

  gpu::gles2::GLES2Interface* gl_;
  unsigned query_id_;
  bool is_pending_;
  base::WeakPtrFactory<SyncQuery> weak_ptr_factory_;

  DISALLOW_COPY_AND_ASSIGN(SyncQuery);
};

SyncQueryCollection::SyncQueryCollection(gpu::gles2::GLES2Interface* gl)
    : gl_(gl) {}

SyncQueryCollection::~SyncQueryCollection() = default;
SyncQueryCollection::SyncQueryCollection(SyncQueryCollection&&) = default;
SyncQueryCollection& SyncQueryCollection::operator=(SyncQueryCollection&&) =
    default;

scoped_refptr<ResourceFence> SyncQueryCollection::StartNewFrame() {
  // Block until oldest sync query has passed if the number of pending queries
  // ever reach kMaxPendingSyncQueries.
  if (pending_sync_queries_.size() >= kMaxPendingSyncQueries) {
    LOG(ERROR) << "Reached limit of pending sync queries.";

    pending_sync_queries_.front()->Wait();
    DCHECK(!pending_sync_queries_.front()->IsPending());
  }

  while (!pending_sync_queries_.empty()) {
    if (pending_sync_queries_.front()->IsPending())
      break;

    available_sync_queries_.push_back(cc::PopFront(&pending_sync_queries_));
  }

  current_sync_query_ = available_sync_queries_.empty()
                            ? std::make_unique<SyncQuery>(gl_)
                            : cc::PopFront(&available_sync_queries_);

  return current_sync_query_->Begin();
}

void SyncQueryCollection::EndCurrentFrame() {
  DCHECK(current_sync_query_);
  current_sync_query_->End();
  pending_sync_queries_.push_back(std::move(current_sync_query_));
}

}  // namespace viz
