blob: 515e52dbc3ca97a1a14d4852edc1ebed95e782d6 [file] [log] [blame]
// 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.
// Tests for the QueryTracker.
#include "gpu/command_buffer/client/query_tracker.h"
#include <GLES2/gl2ext.h>
#include <stddef.h>
#include <stdint.h>
#include <memory>
#include <vector>
#include "base/stl_util.h"
#include "gpu/command_buffer/client/client_test_helper.h"
#include "gpu/command_buffer/client/gles2_cmd_helper.h"
#include "gpu/command_buffer/client/mapped_memory.h"
#include "gpu/command_buffer/common/command_buffer.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
using testing::_;
using testing::AnyNumber;
namespace gpu {
namespace gles2 {
class QuerySyncManagerTest : public testing::Test {
protected:
static const int32_t kNumCommandEntries = 400;
static const int32_t kCommandBufferSizeBytes =
kNumCommandEntries * sizeof(CommandBufferEntry);
void SetUp() override {
command_buffer_.reset(new MockClientCommandBuffer());
helper_.reset(new GLES2CmdHelper(command_buffer_.get()));
helper_->Initialize(kCommandBufferSizeBytes);
mapped_memory_.reset(
new MappedMemoryManager(helper_.get(), MappedMemoryManager::kNoLimit));
sync_manager_.reset(new QuerySyncManager(mapped_memory_.get()));
}
void TearDown() override {
EXPECT_CALL(*command_buffer_, DestroyTransferBuffer(_)).Times(AnyNumber());
sync_manager_.reset();
mapped_memory_.reset();
helper_.reset();
command_buffer_.reset();
}
std::unique_ptr<MockClientCommandBuffer> command_buffer_;
std::unique_ptr<GLES2CmdHelper> helper_;
std::unique_ptr<MappedMemoryManager> mapped_memory_;
std::unique_ptr<QuerySyncManager> sync_manager_;
};
TEST_F(QuerySyncManagerTest, Basic) {
QuerySyncManager::QueryInfo infos[4];
memset(&infos, 0xBD, sizeof(infos));
for (size_t ii = 0; ii < base::size(infos); ++ii) {
EXPECT_TRUE(sync_manager_->Alloc(&infos[ii]));
ASSERT_TRUE(infos[ii].sync != nullptr);
EXPECT_EQ(0, infos[ii].sync->process_count);
EXPECT_EQ(0u, infos[ii].sync->result);
EXPECT_EQ(0, infos[ii].submit_count);
}
for (size_t ii = 0; ii < base::size(infos); ++ii) {
sync_manager_->Free(infos[ii]);
}
}
TEST_F(QuerySyncManagerTest, DontFree) {
QuerySyncManager::QueryInfo infos[4];
memset(&infos, 0xBD, sizeof(infos));
for (size_t ii = 0; ii < base::size(infos); ++ii) {
EXPECT_TRUE(sync_manager_->Alloc(&infos[ii]));
}
}
TEST_F(QuerySyncManagerTest, FreePendingSyncs) {
QuerySyncManager::QueryInfo info;
EXPECT_TRUE(sync_manager_->Alloc(&info));
QuerySyncManager::Bucket* bucket = info.bucket;
// Mark the query as in-use.
++info.submit_count;
// Freeing the QueryInfo should keep the QuerySync busy as it's still in-use,
// but should be tracked in pending_syncs.
sync_manager_->Free(info);
EXPECT_FALSE(bucket->pending_syncs.empty());
EXPECT_TRUE(bucket->in_use_query_syncs.any());
// FreePendingSyncs should not free in-use QuerySync.
bucket->FreePendingSyncs();
EXPECT_FALSE(bucket->pending_syncs.empty());
EXPECT_TRUE(bucket->in_use_query_syncs.any());
// Mark the query as completed.
info.sync->process_count = info.submit_count;
// FreePendingSyncs should free the QuerySync.
bucket->FreePendingSyncs();
EXPECT_TRUE(bucket->pending_syncs.empty());
EXPECT_FALSE(bucket->in_use_query_syncs.any());
// Allocate a new Query, mark it in-use
EXPECT_TRUE(sync_manager_->Alloc(&info));
bucket = info.bucket;
++info.submit_count;
// Mark the query as completed
info.sync->process_count = info.submit_count;
// FreePendingSyncs should not free the QuerySync. Even though the query is
// completed, is has not been deleted yet.
bucket->FreePendingSyncs();
EXPECT_TRUE(bucket->in_use_query_syncs.any());
// Free the QueryInfo, it should be immediately freed.
sync_manager_->Free(info);
EXPECT_TRUE(bucket->pending_syncs.empty());
EXPECT_FALSE(bucket->in_use_query_syncs.any());
}
TEST_F(QuerySyncManagerTest, Shrink) {
QuerySyncManager::QueryInfo info;
EXPECT_TRUE(sync_manager_->Alloc(&info));
QuerySyncManager::Bucket* bucket = info.bucket;
QuerySync* syncs = bucket->syncs;
FencedAllocator::State state =
mapped_memory_->GetPointerStatusForTest(syncs, nullptr);
EXPECT_EQ(FencedAllocator::IN_USE, state);
// Shrink while a query is allocated - should not release anything.
sync_manager_->Shrink(helper_.get());
state = mapped_memory_->GetPointerStatusForTest(syncs, nullptr);
EXPECT_EQ(FencedAllocator::IN_USE, state);
// Free query that was never submitted.
sync_manager_->Free(info);
EXPECT_TRUE(bucket->pending_syncs.empty());
EXPECT_FALSE(bucket->in_use_query_syncs.any());
// Shrink should release the memory immediately.
sync_manager_->Shrink(helper_.get());
EXPECT_TRUE(sync_manager_->buckets_.empty());
state = mapped_memory_->GetPointerStatusForTest(syncs, nullptr);
EXPECT_EQ(FencedAllocator::FREE, state);
EXPECT_TRUE(sync_manager_->Alloc(&info));
bucket = info.bucket;
syncs = bucket->syncs;
state = mapped_memory_->GetPointerStatusForTest(syncs, nullptr);
EXPECT_EQ(FencedAllocator::IN_USE, state);
// Free a query that was submitted, but not completed.
++info.submit_count;
sync_manager_->Free(info);
EXPECT_FALSE(bucket->pending_syncs.empty());
EXPECT_TRUE(bucket->in_use_query_syncs.any());
int32_t last_token = helper_->InsertToken();
// Shrink should release the memory, pending a new token.
sync_manager_->Shrink(helper_.get());
EXPECT_TRUE(sync_manager_->buckets_.empty());
int32_t token = 0;
state = mapped_memory_->GetPointerStatusForTest(syncs, &token);
EXPECT_EQ(FencedAllocator::FREE_PENDING_TOKEN, state);
EXPECT_EQ(last_token + 1, token);
EXPECT_TRUE(sync_manager_->Alloc(&info));
bucket = info.bucket;
syncs = bucket->syncs;
state = mapped_memory_->GetPointerStatusForTest(syncs, nullptr);
EXPECT_EQ(FencedAllocator::IN_USE, state);
// Free a query that was submitted, but not completed yet.
++info.submit_count;
int32_t submit_count = info.submit_count;
QuerySync* sync = info.sync;
sync_manager_->Free(info);
EXPECT_FALSE(bucket->pending_syncs.empty());
EXPECT_TRUE(bucket->in_use_query_syncs.any());
// Complete the query after Free.
sync->process_count = submit_count;
// Shrink should free the memory immediately since the query is completed.
sync_manager_->Shrink(helper_.get());
EXPECT_TRUE(sync_manager_->buckets_.empty());
state = mapped_memory_->GetPointerStatusForTest(syncs, nullptr);
EXPECT_EQ(FencedAllocator::FREE, state);
}
class QueryTrackerTest : public testing::Test {
protected:
static const int32_t kNumCommandEntries = 400;
static const int32_t kCommandBufferSizeBytes =
kNumCommandEntries * sizeof(CommandBufferEntry);
void SetUp() override {
command_buffer_.reset(new MockClientCommandBuffer());
helper_.reset(new GLES2CmdHelper(command_buffer_.get()));
helper_->Initialize(kCommandBufferSizeBytes);
mapped_memory_.reset(
new MappedMemoryManager(helper_.get(), MappedMemoryManager::kNoLimit));
query_tracker_.reset(new QueryTracker(mapped_memory_.get()));
}
void TearDown() override {
helper_->CommandBufferHelper::Flush();
EXPECT_CALL(*command_buffer_, DestroyTransferBuffer(_)).Times(AnyNumber());
query_tracker_.reset();
mapped_memory_.reset();
helper_.reset();
command_buffer_.reset();
}
QuerySync* GetSync(QueryTracker::Query* query) {
return query->info_.sync;
}
QuerySyncManager::Bucket* GetBucket(QueryTracker::Query* query) {
return query->info_.bucket;
}
uint32_t GetBucketUsedCount(QuerySyncManager::Bucket* bucket) {
return bucket->in_use_query_syncs.count();
}
uint32_t GetFlushGeneration() { return helper_->flush_generation(); }
std::unique_ptr<MockClientCommandBuffer> command_buffer_;
std::unique_ptr<GLES2CmdHelper> helper_;
std::unique_ptr<MappedMemoryManager> mapped_memory_;
std::unique_ptr<QueryTracker> query_tracker_;
};
TEST_F(QueryTrackerTest, Basic) {
const GLuint kId1 = 123;
const GLuint kId2 = 124;
// Check we can create a Query.
QueryTracker::Query* query = query_tracker_->CreateQuery(
kId1, GL_ANY_SAMPLES_PASSED_EXT);
ASSERT_TRUE(query != nullptr);
// Check we can get the same Query.
EXPECT_EQ(query, query_tracker_->GetQuery(kId1));
// Check we get nothing for a non-existent query.
EXPECT_TRUE(query_tracker_->GetQuery(kId2) == nullptr);
// Check we can delete the query.
query_tracker_->RemoveQuery(kId1);
// Check we get nothing for a non-existent query.
EXPECT_TRUE(query_tracker_->GetQuery(kId1) == nullptr);
}
TEST_F(QueryTrackerTest, Query) {
const GLuint kId1 = 123;
const int32_t kToken = 46;
const uint32_t kResult = 456;
// Create a Query.
QueryTracker::Query* query = query_tracker_->CreateQuery(
kId1, GL_ANY_SAMPLES_PASSED_EXT);
ASSERT_TRUE(query != nullptr);
EXPECT_TRUE(query->NeverUsed());
EXPECT_FALSE(query->Pending());
EXPECT_EQ(0, query->token());
EXPECT_EQ(0, query->submit_count());
// Check MarkAsActive.
query->MarkAsActive();
EXPECT_FALSE(query->NeverUsed());
EXPECT_FALSE(query->Pending());
EXPECT_EQ(0, query->token());
EXPECT_EQ(0, query->submit_count());
EXPECT_EQ(1, query->NextSubmitCount());
// Check MarkAsPending.
query->MarkAsPending(kToken, query->NextSubmitCount());
EXPECT_FALSE(query->NeverUsed());
EXPECT_TRUE(query->Pending());
EXPECT_EQ(kToken, query->token());
EXPECT_EQ(1, query->submit_count());
// Flush only once if no more flushes happened between a call to
// EndQuery command and CheckResultsAvailable
// Advance put_ so flush calls in CheckResultsAvailable go through
// and updates flush_generation count
helper_->Noop(1);
// Store FlushGeneration count after EndQuery is called
uint32_t gen1 = GetFlushGeneration();
bool flush_if_pending = false;
EXPECT_FALSE(query->CheckResultsAvailable(helper_.get(), flush_if_pending));
EXPECT_FALSE(query->NeverUsed());
EXPECT_TRUE(query->Pending());
// No flush should happen if |flush_if_pending| is false.
uint32_t gen2 = GetFlushGeneration();
EXPECT_EQ(gen1, gen2);
flush_if_pending = true;
// Check CheckResultsAvailable.
EXPECT_FALSE(query->CheckResultsAvailable(helper_.get(), flush_if_pending));
EXPECT_FALSE(query->NeverUsed());
EXPECT_TRUE(query->Pending());
gen2 = GetFlushGeneration();
EXPECT_NE(gen1, gen2);
// Repeated calls to CheckResultsAvailable should not flush unnecessarily
EXPECT_FALSE(query->CheckResultsAvailable(helper_.get(), flush_if_pending));
gen1 = GetFlushGeneration();
EXPECT_EQ(gen1, gen2);
EXPECT_FALSE(query->CheckResultsAvailable(helper_.get(), flush_if_pending));
gen1 = GetFlushGeneration();
EXPECT_EQ(gen1, gen2);
// Simulate GPU process marking it as available.
QuerySync* sync = GetSync(query);
sync->process_count = query->submit_count();
sync->result = kResult;
// Check CheckResultsAvailable.
EXPECT_TRUE(query->CheckResultsAvailable(helper_.get(), flush_if_pending));
EXPECT_EQ(kResult, query->GetResult());
EXPECT_FALSE(query->NeverUsed());
EXPECT_FALSE(query->Pending());
}
TEST_F(QueryTrackerTest, Remove) {
const GLuint kId1 = 123;
const int32_t kToken = 46;
const uint32_t kResult = 456;
// Create a Query.
QueryTracker::Query* query = query_tracker_->CreateQuery(
kId1, GL_ANY_SAMPLES_PASSED_EXT);
ASSERT_TRUE(query != nullptr);
QuerySyncManager::Bucket* bucket = GetBucket(query);
EXPECT_EQ(1u, GetBucketUsedCount(bucket));
query->MarkAsActive();
int32_t submit_count = query->NextSubmitCount();
query->MarkAsPending(kToken, submit_count);
QuerySync* sync = GetSync(query);
query_tracker_->RemoveQuery(kId1);
// Check we get nothing for a non-existent query.
EXPECT_TRUE(query_tracker_->GetQuery(kId1) == nullptr);
// Check that memory was not freed.
EXPECT_EQ(1u, GetBucketUsedCount(bucket));
EXPECT_EQ(1u, bucket->pending_syncs.size());
// Simulate GPU process marking it as available.
sync->result = kResult;
sync->process_count = submit_count;
// Check FreePendingSyncs.
bucket->FreePendingSyncs();
EXPECT_EQ(0u, GetBucketUsedCount(bucket));
}
TEST_F(QueryTrackerTest, RemoveActive) {
const GLuint kId1 = 123;
// Create a Query.
QueryTracker::Query* query =
query_tracker_->CreateQuery(kId1, GL_ANY_SAMPLES_PASSED_EXT);
ASSERT_TRUE(query != nullptr);
QuerySyncManager::Bucket* bucket = GetBucket(query);
EXPECT_EQ(1u, GetBucketUsedCount(bucket));
query->MarkAsActive();
query_tracker_->RemoveQuery(kId1);
// Check we get nothing for a non-existent query.
EXPECT_TRUE(query_tracker_->GetQuery(kId1) == nullptr);
// Check that memory was freed.
EXPECT_EQ(0u, GetBucketUsedCount(bucket));
EXPECT_EQ(0u, bucket->pending_syncs.size());
}
TEST_F(QueryTrackerTest, ManyQueries) {
const GLuint kId1 = 123;
const int32_t kToken = 46;
const uint32_t kResult = 456;
const uint32_t kTestSize = 4000;
static_assert(kTestSize > QuerySyncManager::kSyncsPerBucket,
"We want to use more than one bucket");
// Create lots of queries.
std::vector<QueryTracker::Query*> queries;
for (size_t i = 0; i < kTestSize; i++) {
QueryTracker::Query* query =
query_tracker_->CreateQuery(kId1 + i, GL_ANY_SAMPLES_PASSED_EXT);
ASSERT_TRUE(query != nullptr);
queries.push_back(query);
QuerySyncManager::Bucket* bucket = GetBucket(query);
EXPECT_LE(1u, GetBucketUsedCount(bucket));
}
QuerySyncManager::Bucket* query_0_bucket = GetBucket(queries[0]);
uint32_t expected_use_count = QuerySyncManager::kSyncsPerBucket;
EXPECT_EQ(expected_use_count, GetBucketUsedCount(query_0_bucket));
while (!queries.empty()) {
QueryTracker::Query* query = queries.back();
queries.pop_back();
GLuint query_id = kId1 + queries.size();
EXPECT_EQ(query_id, query->id());
query->MarkAsActive();
int32_t submit_count = query->NextSubmitCount();
query->MarkAsPending(kToken, submit_count);
QuerySync* sync = GetSync(query);
QuerySyncManager::Bucket* bucket = GetBucket(query);
uint32_t use_count_before_remove = GetBucketUsedCount(bucket);
bucket->FreePendingSyncs();
EXPECT_EQ(use_count_before_remove, GetBucketUsedCount(bucket));
query_tracker_->RemoveQuery(query_id);
// Check we get nothing for a non-existent query.
EXPECT_TRUE(query_tracker_->GetQuery(query_id) == nullptr);
// Check that memory was not freed since it was not completed.
EXPECT_EQ(use_count_before_remove, GetBucketUsedCount(bucket));
// Simulate GPU process marking it as available.
sync->process_count = submit_count;
sync->result = kResult;
// Check FreeCompletedQueries.
bucket->FreePendingSyncs();
EXPECT_EQ(use_count_before_remove - 1, GetBucketUsedCount(bucket));
}
}
} // namespace gles2
} // namespace gpu