| // Copyright 2018 The Chromium Authors | |
| // 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/gles2_query_manager.h" | |
| #include <stddef.h> | |
| #include <stdint.h> | |
| #include "base/atomicops.h" | |
| #include "base/check_op.h" | |
| #include "base/functional/bind.h" | |
| #include "base/notreached.h" | |
| #include "base/time/time.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_bindings.h" | |
| #include "ui/gl/gl_context.h" | |
| #include "ui/gl/gl_fence.h" | |
| #include "ui/gl/gpu_timing.h" | |
| namespace gpu { | |
| namespace gles2 { | |
| namespace { | |
| class AbstractIntegerQuery : public QueryManager::Query { | |
| public: | |
| AbstractIntegerQuery(QueryManager* manager, | |
| GLenum target, | |
| scoped_refptr<gpu::Buffer> buffer, | |
| QuerySync* sync); | |
| void Begin() override; | |
| void End(base::subtle::Atomic32 submit_count) override; | |
| void QueryCounter(base::subtle::Atomic32 submit_count) override; | |
| void Pause() override; | |
| void Resume() override; | |
| void Destroy(bool have_context) override; | |
| protected: | |
| ~AbstractIntegerQuery() override; | |
| bool AreAllResultsAvailable(); | |
| // Service side query ids. | |
| std::vector<GLuint> service_ids_; | |
| }; | |
| AbstractIntegerQuery::AbstractIntegerQuery(QueryManager* manager, | |
| GLenum target, | |
| scoped_refptr<gpu::Buffer> buffer, | |
| QuerySync* sync) | |
| : Query(manager, target, std::move(buffer), sync) { | |
| GLuint service_id = 0; | |
| glGenQueries(1, &service_id); | |
| DCHECK_NE(0u, service_id); | |
| service_ids_.push_back(service_id); | |
| } | |
| void AbstractIntegerQuery::Begin() { | |
| MarkAsActive(); | |
| // Delete all but the first one when beginning a new query. | |
| if (service_ids_.size() > 1) { | |
| glDeleteQueries(service_ids_.size() - 1, &service_ids_[1]); | |
| service_ids_.resize(1); | |
| } | |
| BeginQueryHelper(target(), service_ids_.back()); | |
| } | |
| void AbstractIntegerQuery::End(base::subtle::Atomic32 submit_count) { | |
| EndQueryHelper(target()); | |
| AddToPendingQueue(submit_count); | |
| } | |
| void AbstractIntegerQuery::QueryCounter(base::subtle::Atomic32 submit_count) { | |
| NOTREACHED(); | |
| } | |
| void AbstractIntegerQuery::Pause() { | |
| MarkAsPaused(); | |
| EndQueryHelper(target()); | |
| } | |
| void AbstractIntegerQuery::Resume() { | |
| MarkAsActive(); | |
| GLuint service_id = 0; | |
| glGenQueries(1, &service_id); | |
| DCHECK_NE(0u, service_id); | |
| service_ids_.push_back(service_id); | |
| BeginQueryHelper(target(), service_ids_.back()); | |
| } | |
| void AbstractIntegerQuery::Destroy(bool have_context) { | |
| if (have_context && !IsDeleted()) { | |
| glDeleteQueries(service_ids_.size(), &service_ids_[0]); | |
| service_ids_.clear(); | |
| MarkAsDeleted(); | |
| } | |
| } | |
| AbstractIntegerQuery::~AbstractIntegerQuery() = default; | |
| bool AbstractIntegerQuery::AreAllResultsAvailable() { | |
| GLuint available = 0; | |
| glGetQueryObjectuiv(service_ids_.back(), GL_QUERY_RESULT_AVAILABLE_EXT, | |
| &available); | |
| return !!available; | |
| } | |
| class BooleanQuery : public AbstractIntegerQuery { | |
| public: | |
| BooleanQuery(QueryManager* manager, | |
| GLenum target, | |
| scoped_refptr<gpu::Buffer> buffer, | |
| QuerySync* sync); | |
| void Process(bool did_finish) override; | |
| protected: | |
| ~BooleanQuery() override; | |
| }; | |
| BooleanQuery::BooleanQuery(QueryManager* manager, | |
| GLenum target, | |
| scoped_refptr<gpu::Buffer> buffer, | |
| QuerySync* sync) | |
| : AbstractIntegerQuery(manager, target, std::move(buffer), sync) {} | |
| BooleanQuery::~BooleanQuery() = default; | |
| void BooleanQuery::Process(bool did_finish) { | |
| if (!AreAllResultsAvailable()) | |
| return; | |
| for (const GLuint& service_id : service_ids_) { | |
| GLuint result = 0; | |
| glGetQueryObjectuiv(service_id, GL_QUERY_RESULT_EXT, &result); | |
| if (result != 0) { | |
| MarkAsCompleted(1); | |
| return; | |
| } | |
| } | |
| MarkAsCompleted(0); | |
| } | |
| class SummedIntegerQuery : public AbstractIntegerQuery { | |
| public: | |
| SummedIntegerQuery(QueryManager* manager, | |
| GLenum target, | |
| scoped_refptr<gpu::Buffer> buffer, | |
| QuerySync* sync); | |
| void Process(bool did_finish) override; | |
| protected: | |
| ~SummedIntegerQuery() override; | |
| }; | |
| SummedIntegerQuery::SummedIntegerQuery(QueryManager* manager, | |
| GLenum target, | |
| scoped_refptr<gpu::Buffer> buffer, | |
| QuerySync* sync) | |
| : AbstractIntegerQuery(manager, target, std::move(buffer), sync) {} | |
| SummedIntegerQuery::~SummedIntegerQuery() = default; | |
| void SummedIntegerQuery::Process(bool did_finish) { | |
| if (!AreAllResultsAvailable()) | |
| return; | |
| GLuint summed_result = 0; | |
| for (const GLuint& service_id : service_ids_) { | |
| GLuint result = 0; | |
| glGetQueryObjectuiv(service_id, GL_QUERY_RESULT_EXT, &result); | |
| summed_result += result; | |
| } | |
| MarkAsCompleted(summed_result); | |
| } | |
| class AsyncReadPixelsCompletedQuery final | |
| : public GLES2QueryManager::GLES2Query { | |
| public: | |
| AsyncReadPixelsCompletedQuery(GLES2QueryManager* manager, | |
| GLenum target, | |
| scoped_refptr<gpu::Buffer> buffer, | |
| QuerySync* sync); | |
| void Begin() override; | |
| void End(base::subtle::Atomic32 submit_count) override; | |
| void QueryCounter(base::subtle::Atomic32 submit_count) override; | |
| void Pause() override; | |
| void Resume() override; | |
| void Process(bool did_finish) override; | |
| void Destroy(bool have_context) override; | |
| protected: | |
| void Complete(); | |
| ~AsyncReadPixelsCompletedQuery() override; | |
| base::WeakPtrFactory<AsyncReadPixelsCompletedQuery> weak_ptr_factory_{this}; | |
| }; | |
| AsyncReadPixelsCompletedQuery::AsyncReadPixelsCompletedQuery( | |
| GLES2QueryManager* manager, | |
| GLenum target, | |
| scoped_refptr<gpu::Buffer> buffer, | |
| QuerySync* sync) | |
| : GLES2Query(manager, target, std::move(buffer), sync) {} | |
| void AsyncReadPixelsCompletedQuery::Begin() { | |
| MarkAsActive(); | |
| } | |
| void AsyncReadPixelsCompletedQuery::Pause() { | |
| MarkAsPaused(); | |
| } | |
| void AsyncReadPixelsCompletedQuery::Resume() { | |
| MarkAsActive(); | |
| } | |
| void AsyncReadPixelsCompletedQuery::End(base::subtle::Atomic32 submit_count) { | |
| MarkAsPending(submit_count); | |
| gles2_query_manager()->decoder()->WaitForReadPixels( | |
| base::BindOnce(&AsyncReadPixelsCompletedQuery::Complete, | |
| weak_ptr_factory_.GetWeakPtr())); | |
| } | |
| void AsyncReadPixelsCompletedQuery::QueryCounter( | |
| base::subtle::Atomic32 submit_count) { | |
| NOTREACHED(); | |
| } | |
| void AsyncReadPixelsCompletedQuery::Complete() { | |
| MarkAsCompleted(1); | |
| } | |
| void AsyncReadPixelsCompletedQuery::Process(bool did_finish) { | |
| NOTREACHED(); | |
| } | |
| void AsyncReadPixelsCompletedQuery::Destroy(bool /* have_context */) { | |
| if (!IsDeleted()) { | |
| MarkAsDeleted(); | |
| } | |
| } | |
| AsyncReadPixelsCompletedQuery::~AsyncReadPixelsCompletedQuery() = default; | |
| class GetErrorQuery : public GLES2QueryManager::GLES2Query { | |
| public: | |
| GetErrorQuery(GLES2QueryManager* manager, | |
| GLenum target, | |
| scoped_refptr<gpu::Buffer> buffer, | |
| QuerySync* sync); | |
| void Begin() override; | |
| void End(base::subtle::Atomic32 submit_count) override; | |
| void QueryCounter(base::subtle::Atomic32 submit_count) override; | |
| void Pause() override; | |
| void Resume() override; | |
| void Process(bool did_finish) override; | |
| void Destroy(bool have_context) override; | |
| protected: | |
| ~GetErrorQuery() override; | |
| }; | |
| GetErrorQuery::GetErrorQuery(GLES2QueryManager* manager, | |
| GLenum target, | |
| scoped_refptr<gpu::Buffer> buffer, | |
| QuerySync* sync) | |
| : GLES2Query(manager, target, std::move(buffer), sync) {} | |
| void GetErrorQuery::Begin() { | |
| MarkAsActive(); | |
| } | |
| void GetErrorQuery::Pause() { | |
| MarkAsPaused(); | |
| } | |
| void GetErrorQuery::Resume() { | |
| MarkAsActive(); | |
| } | |
| void GetErrorQuery::End(base::subtle::Atomic32 submit_count) { | |
| MarkAsPending(submit_count); | |
| MarkAsCompleted( | |
| gles2_query_manager()->decoder()->GetErrorState()->GetGLError()); | |
| } | |
| void GetErrorQuery::QueryCounter(base::subtle::Atomic32 submit_count) { | |
| NOTREACHED(); | |
| } | |
| void GetErrorQuery::Process(bool did_finish) { | |
| NOTREACHED(); | |
| } | |
| void GetErrorQuery::Destroy(bool /* have_context */) { | |
| if (!IsDeleted()) { | |
| MarkAsDeleted(); | |
| } | |
| } | |
| GetErrorQuery::~GetErrorQuery() = default; | |
| class TimeElapsedQuery : public GLES2QueryManager::GLES2Query { | |
| public: | |
| TimeElapsedQuery(GLES2QueryManager* manager, | |
| GLenum target, | |
| scoped_refptr<gpu::Buffer> buffer, | |
| QuerySync* sync); | |
| // Overridden from QueryManager::Query: | |
| void Begin() override; | |
| void End(base::subtle::Atomic32 submit_count) override; | |
| void QueryCounter(base::subtle::Atomic32 submit_count) override; | |
| void Pause() override; | |
| void Resume() override; | |
| void Process(bool did_finish) override; | |
| void Destroy(bool have_context) override; | |
| protected: | |
| ~TimeElapsedQuery() override; | |
| private: | |
| std::unique_ptr<gl::GPUTimer> gpu_timer_; | |
| }; | |
| TimeElapsedQuery::TimeElapsedQuery(GLES2QueryManager* manager, | |
| GLenum target, | |
| scoped_refptr<gpu::Buffer> buffer, | |
| QuerySync* sync) | |
| : GLES2Query(manager, target, std::move(buffer), sync), | |
| gpu_timer_(manager->CreateGPUTimer(true)) {} | |
| void TimeElapsedQuery::Begin() { | |
| // Reset the disjoint value before the query begins if it is safe. | |
| SafelyResetDisjointValue(); | |
| MarkAsActive(); | |
| gpu_timer_->Start(); | |
| } | |
| void TimeElapsedQuery::End(base::subtle::Atomic32 submit_count) { | |
| gpu_timer_->End(); | |
| AddToPendingQueue(submit_count); | |
| } | |
| void TimeElapsedQuery::QueryCounter(base::subtle::Atomic32 submit_count) { | |
| NOTREACHED(); | |
| } | |
| void TimeElapsedQuery::Pause() { | |
| MarkAsPaused(); | |
| } | |
| void TimeElapsedQuery::Resume() { | |
| MarkAsActive(); | |
| } | |
| void TimeElapsedQuery::Process(bool did_finish) { | |
| if (!gpu_timer_->IsAvailable()) | |
| return; | |
| // Make sure disjoint value is up to date. This disjoint check is the only one | |
| // that needs to be done to validate that this query is valid. If a disjoint | |
| // occurs before the client checks the query value we will just hide the | |
| // disjoint state since it did not affect this query. | |
| UpdateDisjointValue(); | |
| const uint64_t nano_seconds = | |
| gpu_timer_->GetDeltaElapsed() * base::Time::kNanosecondsPerMicrosecond; | |
| MarkAsCompleted(nano_seconds); | |
| } | |
| void TimeElapsedQuery::Destroy(bool have_context) { | |
| gpu_timer_->Destroy(have_context); | |
| } | |
| TimeElapsedQuery::~TimeElapsedQuery() = default; | |
| class TimeStampQuery : public GLES2QueryManager::GLES2Query { | |
| public: | |
| TimeStampQuery(GLES2QueryManager* manager, | |
| GLenum target, | |
| scoped_refptr<gpu::Buffer> buffer, | |
| QuerySync* sync); | |
| // Overridden from QueryManager::Query: | |
| void Begin() override; | |
| void End(base::subtle::Atomic32 submit_count) override; | |
| void QueryCounter(base::subtle::Atomic32 submit_count) override; | |
| void Pause() override; | |
| void Resume() override; | |
| void Process(bool did_finish) override; | |
| void Destroy(bool have_context) override; | |
| protected: | |
| ~TimeStampQuery() override; | |
| private: | |
| std::unique_ptr<gl::GPUTimer> gpu_timer_; | |
| }; | |
| TimeStampQuery::TimeStampQuery(GLES2QueryManager* manager, | |
| GLenum target, | |
| scoped_refptr<gpu::Buffer> buffer, | |
| QuerySync* sync) | |
| : GLES2Query(manager, target, std::move(buffer), sync), | |
| gpu_timer_(manager->CreateGPUTimer(false)) {} | |
| void TimeStampQuery::Begin() { | |
| NOTREACHED(); | |
| } | |
| void TimeStampQuery::End(base::subtle::Atomic32 submit_count) { | |
| NOTREACHED(); | |
| } | |
| void TimeStampQuery::Pause() { | |
| MarkAsPaused(); | |
| } | |
| void TimeStampQuery::Resume() { | |
| MarkAsActive(); | |
| } | |
| void TimeStampQuery::QueryCounter(base::subtle::Atomic32 submit_count) { | |
| // Reset the disjoint value before the query begins if it is safe. | |
| SafelyResetDisjointValue(); | |
| MarkAsActive(); | |
| // After a timestamp has begun, we will want to continually detect | |
| // the disjoint value every frame until the context is destroyed. | |
| BeginContinualDisjointUpdate(); | |
| gpu_timer_->QueryTimeStamp(); | |
| AddToPendingQueue(submit_count); | |
| } | |
| void TimeStampQuery::Process(bool did_finish) { | |
| if (!gpu_timer_->IsAvailable()) | |
| return; | |
| // Make sure disjoint value is up to date. This disjoint check is the only one | |
| // that needs to be done to validate that this query is valid. If a disjoint | |
| // occurs before the client checks the query value we will just hide the | |
| // disjoint state since it did not affect this query. | |
| UpdateDisjointValue(); | |
| int64_t start = 0; | |
| int64_t end = 0; | |
| gpu_timer_->GetStartEndTimestamps(&start, &end); | |
| DCHECK(start == end); | |
| const uint64_t nano_seconds = start * base::Time::kNanosecondsPerMicrosecond; | |
| MarkAsCompleted(nano_seconds); | |
| } | |
| void TimeStampQuery::Destroy(bool have_context) { | |
| if (gpu_timer_.get()) { | |
| gpu_timer_->Destroy(have_context); | |
| gpu_timer_.reset(); | |
| } | |
| } | |
| TimeStampQuery::~TimeStampQuery() = default; | |
| } // namespace | |
| GLES2QueryManager::GLES2Query::GLES2Query(GLES2QueryManager* manager, | |
| GLenum target, | |
| scoped_refptr<gpu::Buffer> buffer, | |
| QuerySync* sync) | |
| : QueryManager::Query(manager, target, buffer, sync), | |
| gles2_query_manager_(manager) {} | |
| GLES2QueryManager::GLES2Query::~GLES2Query() = default; | |
| GLES2QueryManager::GLES2QueryManager(GLES2Decoder* decoder, | |
| FeatureInfo* feature_info) | |
| : decoder_(decoder), | |
| flush_queries_before_deleting_or_unbinding_fbo_( | |
| feature_info && | |
| feature_info->workarounds() | |
| .flush_queries_before_deleting_or_unbinding_fbo), | |
| update_disjoints_continually_(false), | |
| disjoint_notify_shm_id_(-1), | |
| disjoint_notify_shm_offset_(0), | |
| disjoints_notified_(0) { | |
| DCHECK(decoder); | |
| gl::GLContext* context = decoder_->GetGLContext(); | |
| if (context) { | |
| gpu_timing_client_ = context->CreateGPUTimingClient(); | |
| } else { | |
| gpu_timing_client_ = new gl::GPUTimingClient(); | |
| } | |
| } | |
| GLES2QueryManager::~GLES2QueryManager() = default; | |
| void GLES2QueryManager::PauseQueries() { | |
| const bool had_unflushed_queries = HaveActiveQueries() || HavePendingQueries(); | |
| QueryManager::PauseQueries(); | |
| // Driver-bug workaround flush_queries_before_deleting_or_unbinding_fbo | |
| // (ARM Mali Valhall/Avalon, crbug.com/534468209): a user FBO must not be | |
| // unbound or deleted while GPU jobs/queries that reference it are still | |
| // unflushed. GLES2DecoderImpl guards its own glBindFramebuffer / | |
| // glDeleteFramebuffers, but with virtualized GL contexts every other | |
| // virtual context on the same real context (the raster SharedContextState | |
| // and its Skia Ganesh-GL GrGLGpu, other decoders) issues FBO binds and | |
| // deletes without any knowledge of this decoder's queries -- Skia's own | |
| // copy of the workaround is keyed on Skia-issued timer queries only. So | |
| // flush this decoder's query job chain at the point where the invariant | |
| // "every query on this real context is mine" stops holding: the virtual | |
| // context switch. | |
| if (had_unflushed_queries && | |
| flush_queries_before_deleting_or_unbinding_fbo_) { | |
| gl::GLApi* api = gl::g_current_gl_context; | |
| GLsync sync = api->glFenceSyncFn(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); | |
| if (sync) { | |
| api->glClientWaitSyncFn(sync, GL_SYNC_FLUSH_COMMANDS_BIT, 0); | |
| api->glDeleteSyncFn(sync); | |
| } else { | |
| api->glFinishFn(); | |
| } | |
| } | |
| } | |
| QueryManager::Query* GLES2QueryManager::CreateQuery( | |
| GLenum target, | |
| GLuint client_id, | |
| scoped_refptr<gpu::Buffer> buffer, | |
| QuerySync* sync) { | |
| scoped_refptr<Query> query; | |
| switch (target) { | |
| case GL_ASYNC_PIXEL_PACK_COMPLETED_CHROMIUM: | |
| query = new AsyncReadPixelsCompletedQuery(this, target, std::move(buffer), | |
| sync); | |
| break; | |
| case GL_GET_ERROR_QUERY_CHROMIUM: | |
| query = new GetErrorQuery(this, target, std::move(buffer), sync); | |
| break; | |
| case GL_TIME_ELAPSED_EXT: | |
| query = new TimeElapsedQuery(this, target, std::move(buffer), sync); | |
| break; | |
| case GL_TIMESTAMP_EXT: | |
| query = new TimeStampQuery(this, target, std::move(buffer), sync); | |
| break; | |
| case GL_ANY_SAMPLES_PASSED: | |
| case GL_ANY_SAMPLES_PASSED_CONSERVATIVE: | |
| query = new BooleanQuery(this, target, std::move(buffer), sync); | |
| break; | |
| case GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN: | |
| query = new SummedIntegerQuery(this, target, std::move(buffer), sync); | |
| break; | |
| case GL_SAMPLES_PASSED_ARB: | |
| query = new SummedIntegerQuery(this, target, std::move(buffer), sync); | |
| break; | |
| default: | |
| return QueryManager::CreateQuery(target, client_id, buffer, sync); | |
| } | |
| std::pair<QueryMap::iterator, bool> result = | |
| queries_.insert(std::make_pair(client_id, query)); | |
| DCHECK(result.second); | |
| return query.get(); | |
| } | |
| void GLES2QueryManager::ProcessFrameBeginUpdates() { | |
| if (update_disjoints_continually_) | |
| UpdateDisjointValue(); | |
| } | |
| error::Error GLES2QueryManager::SetDisjointSync(int32_t shm_id, | |
| uint32_t shm_offset) { | |
| if (disjoint_notify_shm_id_ != -1 || shm_id == -1) | |
| return error::kInvalidArguments; | |
| DisjointValueSync* sync = decoder_->GetSharedMemoryAs<DisjointValueSync*>( | |
| shm_id, shm_offset, sizeof(*sync)); | |
| if (!sync) | |
| return error::kOutOfBounds; | |
| sync->Reset(); | |
| disjoints_notified_ = 0; | |
| disjoint_notify_shm_id_ = shm_id; | |
| disjoint_notify_shm_offset_ = shm_offset; | |
| return error::kNoError; | |
| } | |
| std::unique_ptr<gl::GPUTimer> GLES2QueryManager::CreateGPUTimer( | |
| bool elapsed_time) { | |
| return gpu_timing_client_->CreateGPUTimer(elapsed_time); | |
| } | |
| bool GLES2QueryManager::GPUTimingAvailable() { | |
| return gpu_timing_client_->IsAvailable(); | |
| } | |
| void GLES2QueryManager::UpdateDisjointValue() { | |
| if (disjoint_notify_shm_id_ != -1) { | |
| if (gpu_timing_client_->CheckAndResetTimerErrors()) { | |
| disjoints_notified_++; | |
| DisjointValueSync* sync = decoder_->GetSharedMemoryAs<DisjointValueSync*>( | |
| disjoint_notify_shm_id_, disjoint_notify_shm_offset_, sizeof(*sync)); | |
| if (!sync) { | |
| // Shared memory does not seem to be valid, ignore the shm id/offset. | |
| disjoint_notify_shm_id_ = -1; | |
| disjoint_notify_shm_offset_ = 0; | |
| } else { | |
| sync->SetDisjointCount(disjoints_notified_); | |
| } | |
| } | |
| } | |
| } | |
| void GLES2QueryManager::SafelyResetDisjointValue() { | |
| // It is only safe to reset the disjoint value is there is no active | |
| // elapsed timer and we are not continually updating the disjoint value. | |
| if (!update_disjoints_continually_ && !GetActiveQuery(GL_TIME_ELAPSED_EXT)) { | |
| // Reset the error state without storing the result. | |
| gpu_timing_client_->CheckAndResetTimerErrors(); | |
| } | |
| } | |
| } // namespace gles2 | |
| } // namespace gpu |