// 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