// 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 "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),
flushed_(false),
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;
}
}
}
}
gl->helper()->EndQueryEXT(target(), submit_count());
MarkAsPending(gl->helper()->InsertToken());
}
bool QueryTracker::Query::CheckResultsAvailable(
CommandBufferHelper* helper) {
if (Pending()) {
if (info_.sync->process_count == submit_count_ ||
helper->IsContextLost()) {
// Need a MemoryBarrier here so that sync->result read after
// sync->process_count.
base::subtle::MemoryBarrier();
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 (!flushed_) {
// TODO(gman): We could reduce the number of flushes by having a
// flush count, recording that count at the time we insert the
// EndQuery command and then only flushing here if we've have not
// passed that count yet.
flushed_ = true;
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() &&
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