| // 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 <limits.h> |
| #include <stddef.h> |
| #include <stdint.h> |
| |
| #include "base/atomicops.h" |
| #include "base/numerics/safe_conversions.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::Bucket::Bucket(QuerySync* sync_mem, |
| int32_t shm_id, |
| unsigned int shm_offset) |
| : syncs(sync_mem), shm_id(shm_id), base_shm_offset(shm_offset) {} |
| |
| QuerySyncManager::Bucket::~Bucket() = default; |
| |
| 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); |
| Bucket* bucket = nullptr; |
| for (Bucket* bucket_candidate : buckets_) { |
| // In C++11 STL this could be replaced with |
| // if (!bucket_candidate->in_use_queries.all()) { ... } |
| if (bucket_candidate->in_use_queries.count() != kSyncsPerBucket) { |
| bucket = bucket_candidate; |
| break; |
| } |
| } |
| if (!bucket) { |
| int32_t 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 = new Bucket(syncs, shm_id, shm_offset); |
| buckets_.push_back(bucket); |
| } |
| |
| size_t index_in_bucket = 0; |
| for (size_t i = 0; i < kSyncsPerBucket; i++) { |
| if (!bucket->in_use_queries[i]) { |
| index_in_bucket = i; |
| break; |
| } |
| } |
| |
| uint32_t shm_offset = |
| bucket->base_shm_offset + index_in_bucket * sizeof(QuerySync); |
| QuerySync* sync = bucket->syncs + index_in_bucket; |
| *info = QueryInfo(bucket, bucket->shm_id, shm_offset, sync); |
| info->sync->Reset(); |
| bucket->in_use_queries[index_in_bucket] = true; |
| return true; |
| } |
| |
| void QuerySyncManager::Free(const QuerySyncManager::QueryInfo& info) { |
| DCHECK_NE(info.bucket->in_use_queries.count(), 0u); |
| unsigned short index_in_bucket = info.sync - info.bucket->syncs; |
| DCHECK(info.bucket->in_use_queries[index_in_bucket]); |
| info.bucket->in_use_queries[index_in_bucket] = false; |
| } |
| |
| void QuerySyncManager::Shrink() { |
| std::deque<Bucket*> new_buckets; |
| while (!buckets_.empty()) { |
| Bucket* bucket = buckets_.front(); |
| if (bucket->in_use_queries.any()) { |
| 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_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 service. |
| // 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()); |
| } |
| |
| void QueryTracker::Query::QueryCounter(GLES2Implementation* gl) { |
| MarkAsActive(); |
| flush_count_ = gl->helper()->flush_generation(); |
| gl->helper()->QueryCounterEXT(id(), target(), shm_id(), shm_offset(), |
| submit_count()); |
| MarkAsPending(gl->helper()->InsertToken()); |
| } |
| |
| bool QueryTracker::Query::CheckResultsAvailable( |
| CommandBufferHelper* helper) { |
| if (Pending()) { |
| bool processed_all = |
| base::subtle::Acquire_Load(&info_.sync->process_count) == submit_count_; |
| // We check lost on the command buffer itself here instead of checking the |
| // GLES2Implementation because the GLES2Implementation will not hear about |
| // the loss until we exit out of this call stack (to avoid re-entrancy), and |
| // we need be able to enter kComplete state on context loss. |
| // TODO(danakj): If GLES2Implementation can handle being notified of loss |
| // re-entrantly (without calling its clients re-entrantly), then we could |
| // call GLES2Implementation::GetGraphicsResetStatusKHR() here and remove |
| // this method from CommandBufferHelper. |
| if (processed_all || helper->IsContextLost()) { |
| switch (target()) { |
| case GL_COMMANDS_ISSUED_CHROMIUM: |
| result_ = info_.sync->result; |
| break; |
| case GL_LATENCY_QUERY_CHROMIUM: |
| // Disabled DCHECK because of http://crbug.com/419236. |
| //DCHECK(info_.sync->result >= client_begin_time_us_); |
| result_ = info_.sync->result - client_begin_time_us_; |
| break; |
| 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; |
| } |
| |
| uint64_t QueryTracker::Query::GetResult() const { |
| DCHECK(state_ == kComplete || state_ == kUninitialized); |
| return result_; |
| } |
| |
| QueryTracker::QueryTracker(MappedMemoryManager* manager) |
| : query_sync_manager_(manager), |
| mapped_memory_(manager), |
| disjoint_count_sync_shm_id_(-1), |
| disjoint_count_sync_shm_offset_(0), |
| disjoint_count_sync_(nullptr), |
| local_disjoint_count_(0) { |
| } |
| |
| 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(); |
| } |
| if (disjoint_count_sync_) { |
| mapped_memory_->Free(disjoint_count_sync_); |
| disjoint_count_sync_ = nullptr; |
| } |
| } |
| |
| QueryTracker::Query* QueryTracker::CreateQuery(GLuint id, GLenum target) { |
| DCHECK_NE(0u, id); |
| FreeCompletedQueries(); |
| QuerySyncManager::QueryInfo info; |
| if (!query_sync_manager_.Alloc(&info)) { |
| return nullptr; |
| } |
| Query* query = new Query(id, target, info); |
| std::pair<QueryIdMap::iterator, bool> result = |
| queries_.insert(std::make_pair(id, query)); |
| DCHECK(result.second); |
| return query; |
| } |
| |
| QueryTracker::Query* QueryTracker::GetQuery(GLuint client_id) { |
| QueryIdMap::iterator it = queries_.find(client_id); |
| return it != queries_.end() ? it->second : nullptr; |
| } |
| |
| QueryTracker::Query* QueryTracker::GetCurrentQuery(GLenum target) { |
| QueryTargetMap::iterator it = current_queries_.find(target); |
| return it != current_queries_.end() ? it->second : nullptr; |
| } |
| |
| void QueryTracker::RemoveQuery(GLuint client_id) { |
| QueryIdMap::iterator it = queries_.find(client_id); |
| if (it != queries_.end()) { |
| Query* query = it->second; |
| |
| // Erase from current targets map if it is the current target. |
| const GLenum target = query->target(); |
| QueryTargetMap::iterator target_it = current_queries_.find(target); |
| if (target_it != current_queries_.end() && target_it->second == query) { |
| current_queries_.erase(target_it); |
| } |
| |
| // 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; |
| } |
| } |
| |
| bool QueryTracker::BeginQuery(GLuint id, GLenum target, |
| GLES2Implementation* gl) { |
| QueryTracker::Query* query = GetQuery(id); |
| if (!query) { |
| query = CreateQuery(id, target); |
| if (!query) { |
| gl->SetGLError(GL_OUT_OF_MEMORY, |
| "glBeginQueryEXT", |
| "transfer buffer allocation failed"); |
| return false; |
| } |
| } else if (query->target() != target) { |
| gl->SetGLError(GL_INVALID_OPERATION, |
| "glBeginQueryEXT", |
| "target does not match"); |
| return false; |
| } |
| |
| current_queries_[query->target()] = query; |
| query->Begin(gl); |
| return true; |
| } |
| |
| bool QueryTracker::EndQuery(GLenum target, GLES2Implementation* gl) { |
| QueryTargetMap::iterator target_it = current_queries_.find(target); |
| if (target_it == current_queries_.end()) { |
| gl->SetGLError(GL_INVALID_OPERATION, |
| "glEndQueryEXT", "no active query"); |
| return false; |
| } |
| |
| target_it->second->End(gl); |
| current_queries_.erase(target_it); |
| return true; |
| } |
| |
| bool QueryTracker::QueryCounter(GLuint id, GLenum target, |
| GLES2Implementation* gl) { |
| QueryTracker::Query* query = GetQuery(id); |
| if (!query) { |
| query = CreateQuery(id, target); |
| if (!query) { |
| gl->SetGLError(GL_OUT_OF_MEMORY, |
| "glQueryCounterEXT", |
| "transfer buffer allocation failed"); |
| return false; |
| } |
| } else if (query->target() != target) { |
| gl->SetGLError(GL_INVALID_OPERATION, |
| "glQueryCounterEXT", |
| "target does not match"); |
| return false; |
| } |
| |
| query->QueryCounter(gl); |
| return true; |
| } |
| |
| bool QueryTracker::SetDisjointSync(GLES2Implementation* gl) { |
| if (!disjoint_count_sync_) { |
| // Allocate memory for disjoint value sync. |
| int32_t shm_id = -1; |
| uint32_t shm_offset; |
| void* mem = mapped_memory_->Alloc(sizeof(*disjoint_count_sync_), |
| &shm_id, |
| &shm_offset); |
| if (mem) { |
| disjoint_count_sync_shm_id_ = shm_id; |
| disjoint_count_sync_shm_offset_ = shm_offset; |
| disjoint_count_sync_ = static_cast<DisjointValueSync*>(mem); |
| disjoint_count_sync_->Reset(); |
| gl->helper()->SetDisjointValueSyncCHROMIUM(shm_id, shm_offset); |
| } |
| } |
| return disjoint_count_sync_ != nullptr; |
| } |
| |
| bool QueryTracker::CheckAndResetDisjoint() { |
| if (disjoint_count_sync_) { |
| const uint32_t disjoint_count = disjoint_count_sync_->GetDisjointCount(); |
| if (local_disjoint_count_ != disjoint_count) { |
| local_disjoint_count_ = disjoint_count; |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| } // namespace gles2 |
| } // namespace gpu |