// Copyright (c) 2012 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 "gpu/command_buffer/client/query_tracker.h"

#include <GLES2/gl2.h>
#include <GLES2/gl2ext.h>
#include <GLES2/gl2extchromium.h>

#include "base/atomicops.h"
#include "gpu/command_buffer/client/gles2_cmd_helper.h"
#include "gpu/command_buffer/client/gles2_implementation.h"
#include "gpu/command_buffer/client/mapped_memory.h"
#include "gpu/command_buffer/common/time.h"

namespace gpu {
namespace gles2 {

QuerySyncManager::QuerySyncManager(MappedMemoryManager* manager)
    : mapped_memory_(manager) {
  DCHECK(manager);
}

QuerySyncManager::~QuerySyncManager() {
  while (!buckets_.empty()) {
    mapped_memory_->Free(buckets_.front()->syncs);
    delete buckets_.front();
    buckets_.pop_front();
  }
}

bool QuerySyncManager::Alloc(QuerySyncManager::QueryInfo* info) {
  DCHECK(info);
  if (free_queries_.empty()) {
    int32 shm_id;
    unsigned int shm_offset;
    void* mem = mapped_memory_->Alloc(
        kSyncsPerBucket * sizeof(QuerySync), &shm_id, &shm_offset);
    if (!mem) {
      return false;
    }
    QuerySync* syncs = static_cast<QuerySync*>(mem);
    Bucket* bucket = new Bucket(syncs);
    buckets_.push_back(bucket);
    for (size_t ii = 0; ii < kSyncsPerBucket; ++ii) {
      free_queries_.push_back(QueryInfo(bucket, shm_id, shm_offset, syncs));
      ++syncs;
      shm_offset += sizeof(*syncs);
    }
  }
  *info = free_queries_.front();
  ++(info->bucket->used_query_count);
  info->sync->Reset();
  free_queries_.pop_front();
  return true;
}

void QuerySyncManager::Free(const QuerySyncManager::QueryInfo& info) {
  DCHECK_GT(info.bucket->used_query_count, 0u);
  --(info.bucket->used_query_count);
  free_queries_.push_back(info);
}

void QuerySyncManager::Shrink() {
  std::deque<QueryInfo> new_queue;
  while (!free_queries_.empty()) {
    if (free_queries_.front().bucket->used_query_count)
      new_queue.push_back(free_queries_.front());
    free_queries_.pop_front();
  }
  free_queries_.swap(new_queue);

  std::deque<Bucket*> new_buckets;
  while (!buckets_.empty()) {
    Bucket* bucket = buckets_.front();
    if (bucket->used_query_count) {
      new_buckets.push_back(bucket);
    } else {
      mapped_memory_->Free(bucket->syncs);
      delete bucket;
    }
    buckets_.pop_front();
  }
  buckets_.swap(new_buckets);
}

QueryTracker::Query::Query(GLuint id, GLenum target,
                           const QuerySyncManager::QueryInfo& info)
    : id_(id),
      target_(target),
      info_(info),
      state_(kUninitialized),
      submit_count_(0),
      token_(0),
      flush_count_(0),
      client_begin_time_us_(0),
      result_(0) {
    }


void QueryTracker::Query::Begin(GLES2Implementation* gl) {
  // init memory, inc count
  MarkAsActive();

  switch (target()) {
    case GL_GET_ERROR_QUERY_CHROMIUM:
      // To nothing on begin for error queries.
      break;
    case GL_LATENCY_QUERY_CHROMIUM:
      client_begin_time_us_ = MicrosecondsSinceOriginOfTime();
      // tell service about id, shared memory and count
      gl->helper()->BeginQueryEXT(target(), id(), shm_id(), shm_offset());
      break;
    case GL_ASYNC_PIXEL_UNPACK_COMPLETED_CHROMIUM:
    case GL_ASYNC_PIXEL_PACK_COMPLETED_CHROMIUM:
    default:
      // tell service about id, shared memory and count
      gl->helper()->BeginQueryEXT(target(), id(), shm_id(), shm_offset());
      break;
  }
}

void QueryTracker::Query::End(GLES2Implementation* gl) {
  switch (target()) {
    case GL_GET_ERROR_QUERY_CHROMIUM: {
      GLenum error = gl->GetClientSideGLError();
      if (error == GL_NO_ERROR) {
        // There was no error so start the query on the serivce.
        // it will end immediately.
        gl->helper()->BeginQueryEXT(target(), id(), shm_id(), shm_offset());
      } else {
        // There's an error on the client, no need to bother the service. just
        // set the query as completed and return the error.
        if (error != GL_NO_ERROR) {
          state_ = kComplete;
          result_ = error;
          return;
        }
      }
    }
  }
  flush_count_ = gl->helper()->flush_generation();
  gl->helper()->EndQueryEXT(target(), submit_count());
  MarkAsPending(gl->helper()->InsertToken());
}

bool QueryTracker::Query::CheckResultsAvailable(
    CommandBufferHelper* helper) {
  if (Pending()) {
    if (base::subtle::Acquire_Load(&info_.sync->process_count) ==
            submit_count_ ||
        helper->IsContextLost()) {
      switch (target()) {
        case GL_COMMANDS_ISSUED_CHROMIUM:
          result_ = std::min(info_.sync->result,
                             static_cast<uint64>(0xFFFFFFFFL));
          break;
        case GL_LATENCY_QUERY_CHROMIUM:
          DCHECK(info_.sync->result >= client_begin_time_us_);
          result_ = std::min(info_.sync->result - client_begin_time_us_,
                             static_cast<uint64>(0xFFFFFFFFL));
          break;
        case GL_ASYNC_PIXEL_UNPACK_COMPLETED_CHROMIUM:
        case GL_ASYNC_PIXEL_PACK_COMPLETED_CHROMIUM:
        default:
          result_ = info_.sync->result;
          break;
      }
      state_ = kComplete;
    } else {
      if ((helper->flush_generation() - flush_count_ - 1) >= 0x80000000) {
        helper->Flush();
      } else {
        // Insert no-ops so that eventually the GPU process will see more work.
        helper->Noop(1);
      }
    }
  }
  return state_ == kComplete;
}

uint32 QueryTracker::Query::GetResult() const {
  DCHECK(state_ == kComplete || state_ == kUninitialized);
  return result_;
}

QueryTracker::QueryTracker(MappedMemoryManager* manager)
    : query_sync_manager_(manager) {
}

QueryTracker::~QueryTracker() {
  while (!queries_.empty()) {
    delete queries_.begin()->second;
    queries_.erase(queries_.begin());
  }
  while (!removed_queries_.empty()) {
    delete removed_queries_.front();
    removed_queries_.pop_front();
  }
}

QueryTracker::Query* QueryTracker::CreateQuery(GLuint id, GLenum target) {
  DCHECK_NE(0u, id);
  FreeCompletedQueries();
  QuerySyncManager::QueryInfo info;
  if (!query_sync_manager_.Alloc(&info)) {
    return NULL;
  }
  Query* query = new Query(id, target, info);
  std::pair<QueryMap::iterator, bool> result =
      queries_.insert(std::make_pair(id, query));
  DCHECK(result.second);
  return query;
}

QueryTracker::Query* QueryTracker::GetQuery(
    GLuint client_id) {
  QueryMap::iterator it = queries_.find(client_id);
  return it != queries_.end() ? it->second : NULL;
}

void QueryTracker::RemoveQuery(GLuint client_id) {
  QueryMap::iterator it = queries_.find(client_id);
  if (it != queries_.end()) {
    Query* query = it->second;
    // When you delete a query you can't mark its memory as unused until it's
    // completed.
    // Note: If you don't do this you won't mess up the service but you will
    // mess up yourself.
    removed_queries_.push_back(query);
    queries_.erase(it);
    FreeCompletedQueries();
  }
}

void QueryTracker::Shrink() {
  FreeCompletedQueries();
  query_sync_manager_.Shrink();
}

void QueryTracker::FreeCompletedQueries() {
  QueryList::iterator it = removed_queries_.begin();
  while (it != removed_queries_.end()) {
    Query* query = *it;
    if (query->Pending() &&
        base::subtle::Acquire_Load(&query->info_.sync->process_count) !=
            query->submit_count()) {
      ++it;
      continue;
    }

    query_sync_manager_.Free(query->info_);
    it = removed_queries_.erase(it);
    delete query;
  }
}

}  // namespace gles2
}  // namespace gpu
