// 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/service/query_manager.h"
#include "base/atomicops.h"
#include "base/bind.h"
#include "base/logging.h"
#include "base/memory/shared_memory.h"
#include "base/numerics/safe_math.h"
#include "base/synchronization/lock.h"
#include "base/time/time.h"
#include "gpu/command_buffer/common/gles2_cmd_format.h"
#include "gpu/command_buffer/service/async_pixel_transfer_manager.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_fence.h"
namespace gpu {
namespace gles2 {
namespace {
class AsyncPixelTransferCompletionObserverImpl
: public AsyncPixelTransferCompletionObserver {
public:
AsyncPixelTransferCompletionObserverImpl(base::subtle::Atomic32 submit_count)
: submit_count_(submit_count), cancelled_(false) {}
void Cancel() {
base::AutoLock locked(lock_);
cancelled_ = true;
}
virtual void DidComplete(const AsyncMemoryParams& mem_params) OVERRIDE {
base::AutoLock locked(lock_);
if (!cancelled_) {
DCHECK(mem_params.buffer());
void* data = mem_params.GetDataAddress();
QuerySync* sync = static_cast<QuerySync*>(data);
base::subtle::Release_Store(&sync->process_count, submit_count_);
}
}
private:
virtual ~AsyncPixelTransferCompletionObserverImpl() {}
base::subtle::Atomic32 submit_count_;
base::Lock lock_;
bool cancelled_;
DISALLOW_COPY_AND_ASSIGN(AsyncPixelTransferCompletionObserverImpl);
};
class AsyncPixelTransfersCompletedQuery
: public QueryManager::Query,
public base::SupportsWeakPtr<AsyncPixelTransfersCompletedQuery> {
public:
AsyncPixelTransfersCompletedQuery(
QueryManager* manager, GLenum target, int32 shm_id, uint32 shm_offset);
virtual bool Begin() OVERRIDE;
virtual bool End(base::subtle::Atomic32 submit_count) OVERRIDE;
virtual bool Process() OVERRIDE;
virtual void Destroy(bool have_context) OVERRIDE;
protected:
virtual ~AsyncPixelTransfersCompletedQuery();
scoped_refptr<AsyncPixelTransferCompletionObserverImpl> observer_;
};
AsyncPixelTransfersCompletedQuery::AsyncPixelTransfersCompletedQuery(
QueryManager* manager, GLenum target, int32 shm_id, uint32 shm_offset)
: Query(manager, target, shm_id, shm_offset) {
}
bool AsyncPixelTransfersCompletedQuery::Begin() {
return true;
}
bool AsyncPixelTransfersCompletedQuery::End(
base::subtle::Atomic32 submit_count) {
// Get the real shared memory since it might need to be duped to prevent
// use-after-free of the memory.
scoped_refptr<Buffer> buffer =
manager()->decoder()->GetSharedMemoryBuffer(shm_id());
if (!buffer)
return false;
AsyncMemoryParams mem_params(buffer, shm_offset(), sizeof(QuerySync));
if (!mem_params.GetDataAddress())
return false;
observer_ = new AsyncPixelTransferCompletionObserverImpl(submit_count);
// Ask AsyncPixelTransferDelegate to run completion callback after all
// previous async transfers are done. No guarantee that callback is run
// on the current thread.
manager()->decoder()->GetAsyncPixelTransferManager()
->AsyncNotifyCompletion(mem_params, observer_);
return AddToPendingTransferQueue(submit_count);
}
bool AsyncPixelTransfersCompletedQuery::Process() {
QuerySync* sync = manager()->decoder()->GetSharedMemoryAs<QuerySync*>(
shm_id(), shm_offset(), sizeof(*sync));
if (!sync)
return false;
// Check if completion callback has been run. sync->process_count atomicity
// is guaranteed as this is already used to notify client of a completed
// query.
if (base::subtle::Acquire_Load(&sync->process_count) != submit_count())
return true;
UnmarkAsPending();
return true;
}
void AsyncPixelTransfersCompletedQuery::Destroy(bool /* have_context */) {
if (!IsDeleted()) {
MarkAsDeleted();
}
}
AsyncPixelTransfersCompletedQuery::~AsyncPixelTransfersCompletedQuery() {
if (observer_)
observer_->Cancel();
}
} // namespace
class AllSamplesPassedQuery : public QueryManager::Query {
public:
AllSamplesPassedQuery(
QueryManager* manager, GLenum target, int32 shm_id, uint32 shm_offset,
GLuint service_id);
virtual bool Begin() OVERRIDE;
virtual bool End(base::subtle::Atomic32 submit_count) OVERRIDE;
virtual bool Process() OVERRIDE;
virtual void Destroy(bool have_context) OVERRIDE;
protected:
virtual ~AllSamplesPassedQuery();
private:
// Service side query id.
GLuint service_id_;
};
AllSamplesPassedQuery::AllSamplesPassedQuery(
QueryManager* manager, GLenum target, int32 shm_id, uint32 shm_offset,
GLuint service_id)
: Query(manager, target, shm_id, shm_offset),
service_id_(service_id) {
}
bool AllSamplesPassedQuery::Begin() {
BeginQueryHelper(target(), service_id_);
return true;
}
bool AllSamplesPassedQuery::End(base::subtle::Atomic32 submit_count) {
EndQueryHelper(target());
return AddToPendingQueue(submit_count);
}
bool AllSamplesPassedQuery::Process() {
GLuint available = 0;
glGetQueryObjectuivARB(
service_id_, GL_QUERY_RESULT_AVAILABLE_EXT, &available);
if (!available) {
return true;
}
GLuint result = 0;
glGetQueryObjectuivARB(
service_id_, GL_QUERY_RESULT_EXT, &result);
return MarkAsCompleted(result != 0);
}
void AllSamplesPassedQuery::Destroy(bool have_context) {
if (have_context && !IsDeleted()) {
glDeleteQueriesARB(1, &service_id_);
MarkAsDeleted();
}
}
AllSamplesPassedQuery::~AllSamplesPassedQuery() {
}
class CommandsIssuedQuery : public QueryManager::Query {
public:
CommandsIssuedQuery(
QueryManager* manager, GLenum target, int32 shm_id, uint32 shm_offset);
virtual bool Begin() OVERRIDE;
virtual bool End(base::subtle::Atomic32 submit_count) OVERRIDE;
virtual bool Process() OVERRIDE;
virtual void Destroy(bool have_context) OVERRIDE;
protected:
virtual ~CommandsIssuedQuery();
private:
base::TimeTicks begin_time_;
};
CommandsIssuedQuery::CommandsIssuedQuery(
QueryManager* manager, GLenum target, int32 shm_id, uint32 shm_offset)
: Query(manager, target, shm_id, shm_offset) {
}
bool CommandsIssuedQuery::Begin() {
begin_time_ = base::TimeTicks::HighResNow();
return true;
}
bool CommandsIssuedQuery::End(base::subtle::Atomic32 submit_count) {
base::TimeDelta elapsed = base::TimeTicks::HighResNow() - begin_time_;
MarkAsPending(submit_count);
return MarkAsCompleted(elapsed.InMicroseconds());
}
bool CommandsIssuedQuery::Process() {
NOTREACHED();
return true;
}
void CommandsIssuedQuery::Destroy(bool /* have_context */) {
if (!IsDeleted()) {
MarkAsDeleted();
}
}
CommandsIssuedQuery::~CommandsIssuedQuery() {
}
class CommandLatencyQuery : public QueryManager::Query {
public:
CommandLatencyQuery(
QueryManager* manager, GLenum target, int32 shm_id, uint32 shm_offset);
virtual bool Begin() OVERRIDE;
virtual bool End(base::subtle::Atomic32 submit_count) OVERRIDE;
virtual bool Process() OVERRIDE;
virtual void Destroy(bool have_context) OVERRIDE;
protected:
virtual ~CommandLatencyQuery();
};
CommandLatencyQuery::CommandLatencyQuery(
QueryManager* manager, GLenum target, int32 shm_id, uint32 shm_offset)
: Query(manager, target, shm_id, shm_offset) {
}
bool CommandLatencyQuery::Begin() {
return true;
}
bool CommandLatencyQuery::End(base::subtle::Atomic32 submit_count) {
base::TimeDelta now = base::TimeTicks::HighResNow() - base::TimeTicks();
MarkAsPending(submit_count);
return MarkAsCompleted(now.InMicroseconds());
}
bool CommandLatencyQuery::Process() {
NOTREACHED();
return true;
}
void CommandLatencyQuery::Destroy(bool /* have_context */) {
if (!IsDeleted()) {
MarkAsDeleted();
}
}
CommandLatencyQuery::~CommandLatencyQuery() {
}
class AsyncReadPixelsCompletedQuery
: public QueryManager::Query,
public base::SupportsWeakPtr<AsyncReadPixelsCompletedQuery> {
public:
AsyncReadPixelsCompletedQuery(
QueryManager* manager, GLenum target, int32 shm_id, uint32 shm_offset);
virtual bool Begin() OVERRIDE;
virtual bool End(base::subtle::Atomic32 submit_count) OVERRIDE;
virtual bool Process() OVERRIDE;
virtual void Destroy(bool have_context) OVERRIDE;
protected:
void Complete();
virtual ~AsyncReadPixelsCompletedQuery();
private:
bool completed_;
bool complete_result_;
};
AsyncReadPixelsCompletedQuery::AsyncReadPixelsCompletedQuery(
QueryManager* manager, GLenum target, int32 shm_id, uint32 shm_offset)
: Query(manager, target, shm_id, shm_offset),
completed_(false),
complete_result_(false) {
}
bool AsyncReadPixelsCompletedQuery::Begin() {
return true;
}
bool AsyncReadPixelsCompletedQuery::End(base::subtle::Atomic32 submit_count) {
if (!AddToPendingQueue(submit_count)) {
return false;
}
manager()->decoder()->WaitForReadPixels(
base::Bind(&AsyncReadPixelsCompletedQuery::Complete,
AsWeakPtr()));
return Process();
}
void AsyncReadPixelsCompletedQuery::Complete() {
completed_ = true;
complete_result_ = MarkAsCompleted(1);
}
bool AsyncReadPixelsCompletedQuery::Process() {
return !completed_ || complete_result_;
}
void AsyncReadPixelsCompletedQuery::Destroy(bool /* have_context */) {
if (!IsDeleted()) {
MarkAsDeleted();
}
}
AsyncReadPixelsCompletedQuery::~AsyncReadPixelsCompletedQuery() {
}
class GetErrorQuery : public QueryManager::Query {
public:
GetErrorQuery(
QueryManager* manager, GLenum target, int32 shm_id, uint32 shm_offset);
virtual bool Begin() OVERRIDE;
virtual bool End(base::subtle::Atomic32 submit_count) OVERRIDE;
virtual bool Process() OVERRIDE;
virtual void Destroy(bool have_context) OVERRIDE;
protected:
virtual ~GetErrorQuery();
private:
};
GetErrorQuery::GetErrorQuery(
QueryManager* manager, GLenum target, int32 shm_id, uint32 shm_offset)
: Query(manager, target, shm_id, shm_offset) {
}
bool GetErrorQuery::Begin() {
return true;
}
bool GetErrorQuery::End(base::subtle::Atomic32 submit_count) {
MarkAsPending(submit_count);
return MarkAsCompleted(manager()->decoder()->GetErrorState()->GetGLError());
}
bool GetErrorQuery::Process() {
NOTREACHED();
return true;
}
void GetErrorQuery::Destroy(bool /* have_context */) {
if (!IsDeleted()) {
MarkAsDeleted();
}
}
GetErrorQuery::~GetErrorQuery() {
}
class CommandsCompletedQuery : public QueryManager::Query {
public:
CommandsCompletedQuery(QueryManager* manager,
GLenum target,
int32 shm_id,
uint32 shm_offset);
// Overridden from QueryManager::Query:
virtual bool Begin() OVERRIDE;
virtual bool End(base::subtle::Atomic32 submit_count) OVERRIDE;
virtual bool Process() OVERRIDE;
virtual void Destroy(bool have_context) OVERRIDE;
protected:
virtual ~CommandsCompletedQuery();
private:
scoped_ptr<gfx::GLFence> fence_;
};
CommandsCompletedQuery::CommandsCompletedQuery(QueryManager* manager,
GLenum target,
int32 shm_id,
uint32 shm_offset)
: Query(manager, target, shm_id, shm_offset) {}
bool CommandsCompletedQuery::Begin() { return true; }
bool CommandsCompletedQuery::End(base::subtle::Atomic32 submit_count) {
fence_.reset(gfx::GLFence::Create());
DCHECK(fence_);
return AddToPendingQueue(submit_count);
}
bool CommandsCompletedQuery::Process() {
if (fence_ && !fence_->HasCompleted())
return true;
return MarkAsCompleted(0);
}
void CommandsCompletedQuery::Destroy(bool have_context) {
if (have_context && !IsDeleted()) {
fence_.reset();
MarkAsDeleted();
}
}
CommandsCompletedQuery::~CommandsCompletedQuery() {}
QueryManager::QueryManager(
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),
query_count_(0) {
DCHECK(!(use_arb_occlusion_query_for_occlusion_query_boolean_ &&
use_arb_occlusion_query2_for_occlusion_query_boolean_));
}
QueryManager::~QueryManager() {
DCHECK(queries_.empty());
// If this triggers, that means something is keeping a reference to
// a Query belonging to this.
CHECK_EQ(query_count_, 0u);
}
void QueryManager::Destroy(bool have_context) {
pending_queries_.clear();
pending_transfer_queries_.clear();
while (!queries_.empty()) {
Query* query = queries_.begin()->second.get();
query->Destroy(have_context);
queries_.erase(queries_.begin());
}
}
QueryManager::Query* QueryManager::CreateQuery(
GLenum target, GLuint client_id, int32 shm_id, uint32 shm_offset) {
scoped_refptr<Query> query;
switch (target) {
case GL_COMMANDS_ISSUED_CHROMIUM:
query = new CommandsIssuedQuery(this, target, shm_id, shm_offset);
break;
case GL_LATENCY_QUERY_CHROMIUM:
query = new CommandLatencyQuery(this, target, shm_id, shm_offset);
break;
case GL_ASYNC_PIXEL_UNPACK_COMPLETED_CHROMIUM:
// Currently async pixel transfer delegates only support uploads.
query = new AsyncPixelTransfersCompletedQuery(
this, target, shm_id, shm_offset);
break;
case GL_ASYNC_PIXEL_PACK_COMPLETED_CHROMIUM:
query = new AsyncReadPixelsCompletedQuery(
this, target, shm_id, shm_offset);
break;
case GL_GET_ERROR_QUERY_CHROMIUM:
query = new GetErrorQuery(this, target, shm_id, shm_offset);
break;
case GL_COMMANDS_COMPLETED_CHROMIUM:
query = new CommandsCompletedQuery(this, target, shm_id, shm_offset);
break;
default: {
GLuint service_id = 0;
glGenQueriesARB(1, &service_id);
DCHECK_NE(0u, service_id);
query = new AllSamplesPassedQuery(
this, target, shm_id, shm_offset, service_id);
break;
}
}
std::pair<QueryMap::iterator, bool> result =
queries_.insert(std::make_pair(client_id, query));
DCHECK(result.second);
return query.get();
}
void QueryManager::GenQueries(GLsizei n, const GLuint* queries) {
DCHECK_GE(n, 0);
for (GLsizei i = 0; i < n; ++i) {
generated_query_ids_.insert(queries[i]);
}
}
bool QueryManager::IsValidQuery(GLuint id) {
GeneratedQueryIds::iterator it = generated_query_ids_.find(id);
return it != generated_query_ids_.end();
}
QueryManager::Query* QueryManager::GetQuery(
GLuint client_id) {
QueryMap::iterator it = queries_.find(client_id);
return it != queries_.end() ? it->second.get() : NULL;
}
void QueryManager::RemoveQuery(GLuint client_id) {
QueryMap::iterator it = queries_.find(client_id);
if (it != queries_.end()) {
Query* query = it->second.get();
RemovePendingQuery(query);
query->MarkAsDeleted();
queries_.erase(it);
}
generated_query_ids_.erase(client_id);
}
void QueryManager::StartTracking(QueryManager::Query* /* query */) {
++query_count_;
}
void QueryManager::StopTracking(QueryManager::Query* /* query */) {
--query_count_;
}
GLenum QueryManager::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 QueryManager::BeginQueryHelper(GLenum target, GLuint id) {
target = AdjustTargetForEmulation(target);
glBeginQueryARB(target, id);
}
void QueryManager::EndQueryHelper(GLenum target) {
target = AdjustTargetForEmulation(target);
glEndQueryARB(target);
}
QueryManager::Query::Query(
QueryManager* manager, GLenum target, int32 shm_id, uint32 shm_offset)
: manager_(manager),
target_(target),
shm_id_(shm_id),
shm_offset_(shm_offset),
submit_count_(0),
pending_(false),
deleted_(false) {
DCHECK(manager);
manager_->StartTracking(this);
}
void QueryManager::Query::RunCallbacks() {
for (size_t i = 0; i < callbacks_.size(); i++) {
callbacks_[i].Run();
}
callbacks_.clear();
}
void QueryManager::Query::AddCallback(base::Closure callback) {
if (pending_) {
callbacks_.push_back(callback);
} else {
callback.Run();
}
}
QueryManager::Query::~Query() {
// The query is getting deleted, either by the client or
// because the context was lost. Call any outstanding
// callbacks to avoid leaks.
RunCallbacks();
if (manager_) {
manager_->StopTracking(this);
manager_ = NULL;
}
}
bool QueryManager::Query::MarkAsCompleted(uint64 result) {
DCHECK(pending_);
QuerySync* sync = manager_->decoder_->GetSharedMemoryAs<QuerySync*>(
shm_id_, shm_offset_, sizeof(*sync));
if (!sync) {
return false;
}
pending_ = false;
sync->result = result;
base::subtle::Release_Store(&sync->process_count, submit_count_);
return true;
}
bool QueryManager::ProcessPendingQueries() {
while (!pending_queries_.empty()) {
Query* query = pending_queries_.front().get();
if (!query->Process()) {
return false;
}
if (query->pending()) {
break;
}
query->RunCallbacks();
pending_queries_.pop_front();
}
return true;
}
bool QueryManager::HavePendingQueries() {
return !pending_queries_.empty();
}
bool QueryManager::ProcessPendingTransferQueries() {
while (!pending_transfer_queries_.empty()) {
Query* query = pending_transfer_queries_.front().get();
if (!query->Process()) {
return false;
}
if (query->pending()) {
break;
}
query->RunCallbacks();
pending_transfer_queries_.pop_front();
}
return true;
}
bool QueryManager::HavePendingTransferQueries() {
return !pending_transfer_queries_.empty();
}
bool QueryManager::AddPendingQuery(Query* query,
base::subtle::Atomic32 submit_count) {
DCHECK(query);
DCHECK(!query->IsDeleted());
if (!RemovePendingQuery(query)) {
return false;
}
query->MarkAsPending(submit_count);
pending_queries_.push_back(query);
return true;
}
bool QueryManager::AddPendingTransferQuery(
Query* query,
base::subtle::Atomic32 submit_count) {
DCHECK(query);
DCHECK(!query->IsDeleted());
if (!RemovePendingQuery(query)) {
return false;
}
query->MarkAsPending(submit_count);
pending_transfer_queries_.push_back(query);
return true;
}
bool QueryManager::RemovePendingQuery(Query* query) {
DCHECK(query);
if (query->pending()) {
// TODO(gman): Speed this up if this is a common operation. This would only
// happen if you do being/end begin/end on the same query without waiting
// for the first one to finish.
for (QueryQueue::iterator it = pending_queries_.begin();
it != pending_queries_.end(); ++it) {
if (it->get() == query) {
pending_queries_.erase(it);
break;
}
}
for (QueryQueue::iterator it = pending_transfer_queries_.begin();
it != pending_transfer_queries_.end(); ++it) {
if (it->get() == query) {
pending_transfer_queries_.erase(it);
break;
}
}
if (!query->MarkAsCompleted(0)) {
return false;
}
}
return true;
}
bool QueryManager::BeginQuery(Query* query) {
DCHECK(query);
if (!RemovePendingQuery(query)) {
return false;
}
return query->Begin();
}
bool QueryManager::EndQuery(Query* query, base::subtle::Atomic32 submit_count) {
DCHECK(query);
if (!RemovePendingQuery(query)) {
return false;
}
return query->End(submit_count);
}
} // namespace gles2
} // namespace gpu