blob: 4dd4f680c9c29bd5dc627be74127e5a57fe758cd [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.
// This file contains the tests for the RingBuffer class.
#include "gpu/command_buffer/client/ring_buffer.h"
#include <stdint.h>
#include <memory>
#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/message_loop/message_loop.h"
#include "base/run_loop.h"
#include "gpu/command_buffer/client/cmd_buffer_helper.h"
#include "gpu/command_buffer/service/command_buffer_direct.h"
#include "gpu/command_buffer/service/mocks.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace gpu {
using testing::Return;
using testing::Mock;
using testing::Truly;
using testing::Sequence;
using testing::DoAll;
using testing::Invoke;
using testing::_;
class BaseRingBufferTest : public testing::Test {
protected:
static const unsigned int kBaseOffset = 128;
static const unsigned int kBufferSize = 1024;
static const unsigned int kAlignment = 4;
void RunPendingSetToken() {
for (std::vector<const volatile void*>::iterator it =
set_token_arguments_.begin();
it != set_token_arguments_.end(); ++it) {
api_mock_->SetToken(cmd::kSetToken, 1, *it);
}
set_token_arguments_.clear();
delay_set_token_ = false;
}
void SetToken(unsigned int command,
unsigned int arg_count,
const volatile void* _args) {
EXPECT_EQ(static_cast<unsigned int>(cmd::kSetToken), command);
EXPECT_EQ(1u, arg_count);
if (delay_set_token_)
set_token_arguments_.push_back(_args);
else
api_mock_->SetToken(cmd::kSetToken, 1, _args);
}
void SetUp() override {
delay_set_token_ = false;
command_buffer_.reset(new CommandBufferDirect());
api_mock_.reset(new AsyncAPIMock(true, command_buffer_->service()));
command_buffer_->set_handler(api_mock_.get());
// ignore noops in the mock - we don't want to inspect the internals of the
// helper.
EXPECT_CALL(*api_mock_, DoCommand(cmd::kNoop, 0, _))
.WillRepeatedly(Return(error::kNoError));
// Forward the SetToken calls to the engine
EXPECT_CALL(*api_mock_.get(), DoCommand(cmd::kSetToken, 1, _))
.WillRepeatedly(DoAll(Invoke(this, &BaseRingBufferTest::SetToken),
Return(error::kNoError)));
helper_.reset(new CommandBufferHelper(command_buffer_.get()));
helper_->Initialize(kBufferSize);
}
int32_t GetToken() { return command_buffer_->GetLastState().token; }
std::unique_ptr<CommandBufferDirect> command_buffer_;
std::unique_ptr<AsyncAPIMock> api_mock_;
std::unique_ptr<CommandBufferHelper> helper_;
std::vector<const volatile void*> set_token_arguments_;
bool delay_set_token_;
std::unique_ptr<int8_t[]> buffer_;
int8_t* buffer_start_;
base::MessageLoop message_loop_;
};
#ifndef _MSC_VER
const unsigned int BaseRingBufferTest::kBaseOffset;
const unsigned int BaseRingBufferTest::kBufferSize;
#endif
// Test fixture for RingBuffer test - Creates a RingBuffer, using a
// CommandBufferHelper with a mock AsyncAPIInterface for its interface (calling
// it directly, not through the RPC mechanism), making sure Noops are ignored
// and SetToken are properly forwarded to the engine.
class RingBufferTest : public BaseRingBufferTest {
protected:
void SetUp() override {
BaseRingBufferTest::SetUp();
buffer_.reset(new int8_t[kBufferSize + kBaseOffset]);
buffer_start_ = buffer_.get() + kBaseOffset;
allocator_.reset(new RingBuffer(kAlignment, kBaseOffset, kBufferSize,
helper_.get(), buffer_start_));
}
void TearDown() override {
// If the CommandExecutor posts any tasks, this forces them to run.
base::RunLoop().RunUntilIdle();
BaseRingBufferTest::TearDown();
}
std::unique_ptr<RingBuffer> allocator_;
};
// Checks basic alloc and free.
TEST_F(RingBufferTest, TestBasic) {
const unsigned int kSize = 16;
EXPECT_EQ(kBufferSize, allocator_->GetLargestFreeOrPendingSize());
EXPECT_EQ(kBufferSize, allocator_->GetLargestFreeSizeNoWaiting());
EXPECT_EQ(allocator_->NumUsedBlocks(), 0u);
void* pointer = allocator_->Alloc(kSize);
EXPECT_EQ(allocator_->NumUsedBlocks(), 1u);
EXPECT_GE(kBufferSize, allocator_->GetOffset(pointer) - kBaseOffset + kSize);
EXPECT_EQ(kBufferSize, allocator_->GetLargestFreeOrPendingSize());
EXPECT_EQ(kBufferSize - kSize, allocator_->GetLargestFreeSizeNoWaiting());
int32_t token = helper_->InsertToken();
allocator_->FreePendingToken(pointer, token);
EXPECT_EQ(allocator_->NumUsedBlocks(), 0u);
}
// Checks that the value returned by GetLargestFreeOrPendingSize can actually be
// allocated. This is a regression test for https://crbug.com/834444, where an
// unaligned buffer could cause an alloc using the value returned by
// GetLargestFreeOrPendingSize to try to allocate more memory than was allowed.
TEST_F(RingBufferTest, TestCanAllocGetLargestFreeOrPendingSize) {
// Make sure we aren't actually aligned
buffer_.reset(new int8_t[kBufferSize + 2 + kBaseOffset]);
buffer_start_ = buffer_.get() + kBaseOffset;
allocator_.reset(new RingBuffer(kAlignment, kBaseOffset, kBufferSize + 2,
helper_.get(), buffer_start_));
void* pointer = allocator_->Alloc(allocator_->GetLargestFreeOrPendingSize());
allocator_->FreePendingToken(pointer, helper_->InsertToken());
}
// Checks the free-pending-token mechanism.
TEST_F(RingBufferTest, TestFreePendingToken) {
const unsigned int kSize = 16;
const unsigned int kAllocCount = kBufferSize / kSize;
CHECK(kAllocCount * kSize == kBufferSize);
delay_set_token_ = true;
// Allocate several buffers to fill in the memory.
int32_t tokens[kAllocCount];
for (unsigned int ii = 0; ii < kAllocCount; ++ii) {
void* pointer = allocator_->Alloc(kSize);
EXPECT_GE(kBufferSize,
allocator_->GetOffset(pointer) - kBaseOffset + kSize);
tokens[ii] = helper_->InsertToken();
allocator_->FreePendingToken(pointer, tokens[ii]);
}
EXPECT_EQ(kBufferSize - (kSize * kAllocCount),
allocator_->GetLargestFreeSizeNoWaiting());
RunPendingSetToken();
// This allocation will need to reclaim the space freed above, so that should
// process the commands until a token is passed.
void* pointer1 = allocator_->Alloc(kSize);
EXPECT_EQ(kBaseOffset, allocator_->GetOffset(pointer1));
// Check that the token has indeed passed.
EXPECT_LE(tokens[0], GetToken());
allocator_->FreePendingToken(pointer1, helper_->InsertToken());
}
// Tests GetLargestFreeSizeNoWaiting
TEST_F(RingBufferTest, TestGetLargestFreeSizeNoWaiting) {
EXPECT_EQ(kBufferSize, allocator_->GetLargestFreeSizeNoWaiting());
void* pointer = allocator_->Alloc(kBufferSize);
EXPECT_EQ(0u, allocator_->GetLargestFreeSizeNoWaiting());
allocator_->FreePendingToken(pointer, helper_->InsertToken());
}
TEST_F(RingBufferTest, TestFreeBug) {
// The first and second allocations must not match.
const unsigned int kAlloc1 = 3*kAlignment;
const unsigned int kAlloc2 = 20;
void* pointer = allocator_->Alloc(kAlloc1);
EXPECT_EQ(kBufferSize - kAlloc1, allocator_->GetLargestFreeSizeNoWaiting());
allocator_->FreePendingToken(pointer, helper_.get()->InsertToken());
pointer = allocator_->Alloc(kAlloc2);
EXPECT_EQ(kBufferSize - kAlloc1 - kAlloc2,
allocator_->GetLargestFreeSizeNoWaiting());
allocator_->FreePendingToken(pointer, helper_.get()->InsertToken());
pointer = allocator_->Alloc(kBufferSize);
EXPECT_EQ(0u, allocator_->GetLargestFreeSizeNoWaiting());
allocator_->FreePendingToken(pointer, helper_.get()->InsertToken());
}
// Test that discarding a single allocation clears the block.
TEST_F(RingBufferTest, DiscardTest) {
const unsigned int kAlloc1 = 3*kAlignment;
void* ptr = allocator_->Alloc(kAlloc1);
EXPECT_EQ(kBufferSize - kAlloc1, allocator_->GetLargestFreeSizeNoWaiting());
EXPECT_EQ(allocator_->NumUsedBlocks(), 1u);
allocator_->DiscardBlock(ptr);
EXPECT_EQ(kBufferSize, allocator_->GetLargestFreeSizeNoWaiting());
EXPECT_EQ(allocator_->NumUsedBlocks(), 0u);
}
// Test that discarding front of the buffer effectively frees the block.
TEST_F(RingBufferTest, DiscardFrontTest) {
const unsigned int kAlloc1 = 3*kAlignment;
const unsigned int kAlloc2 = 2*kAlignment;
const unsigned int kAlloc3 = kBufferSize - kAlloc1 - kAlloc2;
void* ptr1 = allocator_->Alloc(kAlloc1);
EXPECT_EQ(kBufferSize - kAlloc1, allocator_->GetLargestFreeSizeNoWaiting());
allocator_->FreePendingToken(ptr1, helper_.get()->InsertToken());
EXPECT_EQ(allocator_->NumUsedBlocks(), 0u);
void* ptr2 = allocator_->Alloc(kAlloc2);
EXPECT_EQ(static_cast<uint8_t*>(ptr1) + kAlloc1,
static_cast<uint8_t*>(ptr2));
EXPECT_EQ(kBufferSize - kAlloc1 - kAlloc2,
allocator_->GetLargestFreeSizeNoWaiting());
allocator_->FreePendingToken(ptr2, helper_.get()->InsertToken());
EXPECT_EQ(allocator_->NumUsedBlocks(), 0u);
void* ptr3 = allocator_->Alloc(kAlloc3);
EXPECT_EQ(static_cast<uint8_t*>(ptr2) + kAlloc2,
static_cast<uint8_t*>(ptr3));
EXPECT_EQ(0u, allocator_->GetLargestFreeSizeNoWaiting());
EXPECT_EQ(allocator_->NumUsedBlocks(), 1u);
// Discard first block should free it up upon GetLargestFreeSizeNoWaiting().
allocator_->DiscardBlock(ptr1);
EXPECT_EQ(allocator_->NumUsedBlocks(), 1u);
EXPECT_EQ(kAlloc1, allocator_->GetLargestFreeSizeNoWaiting());
allocator_->FreePendingToken(ptr3, helper_.get()->InsertToken());
EXPECT_EQ(allocator_->NumUsedBlocks(), 0u);
}
// Test that discarding middle of the buffer merely marks it as padding.
TEST_F(RingBufferTest, DiscardMiddleTest) {
const unsigned int kAlloc1 = 3*kAlignment;
const unsigned int kAlloc2 = 2*kAlignment;
const unsigned int kAlloc3 = kBufferSize - kAlloc1 - kAlloc2;
void* ptr1 = allocator_->Alloc(kAlloc1);
EXPECT_EQ(kBufferSize - kAlloc1, allocator_->GetLargestFreeSizeNoWaiting());
allocator_->FreePendingToken(ptr1, helper_.get()->InsertToken());
void* ptr2 = allocator_->Alloc(kAlloc2);
EXPECT_EQ(static_cast<uint8_t*>(ptr1) + kAlloc1,
static_cast<uint8_t*>(ptr2));
EXPECT_EQ(kBufferSize - kAlloc1 - kAlloc2,
allocator_->GetLargestFreeSizeNoWaiting());
allocator_->FreePendingToken(ptr2, helper_.get()->InsertToken());
void* ptr3 = allocator_->Alloc(kAlloc3);
EXPECT_EQ(static_cast<uint8_t*>(ptr2) + kAlloc2,
static_cast<uint8_t*>(ptr3));
EXPECT_EQ(0u, allocator_->GetLargestFreeSizeNoWaiting());
EXPECT_EQ(allocator_->NumUsedBlocks(), 1u);
// Discard middle block should just set it as padding.
allocator_->DiscardBlock(ptr2);
EXPECT_EQ(allocator_->NumUsedBlocks(), 1u);
EXPECT_EQ(0u, allocator_->GetLargestFreeSizeNoWaiting());
allocator_->FreePendingToken(ptr3, helper_.get()->InsertToken());
EXPECT_EQ(allocator_->NumUsedBlocks(), 0u);
}
// Test that discarding end of the buffer frees it for no waiting.
TEST_F(RingBufferTest, DiscardEndTest) {
const unsigned int kAlloc1 = 3*kAlignment;
const unsigned int kAlloc2 = 2*kAlignment;
const unsigned int kAlloc3 = kBufferSize - kAlloc1 - kAlloc2;
void* ptr1 = allocator_->Alloc(kAlloc1);
EXPECT_EQ(kBufferSize - kAlloc1, allocator_->GetLargestFreeSizeNoWaiting());
allocator_->FreePendingToken(ptr1, helper_.get()->InsertToken());
void* ptr2 = allocator_->Alloc(kAlloc2);
EXPECT_EQ(static_cast<uint8_t*>(ptr1) + kAlloc1,
static_cast<uint8_t*>(ptr2));
EXPECT_EQ(kBufferSize - kAlloc1 - kAlloc2,
allocator_->GetLargestFreeSizeNoWaiting());
allocator_->FreePendingToken(ptr2, helper_.get()->InsertToken());
void* ptr3 = allocator_->Alloc(kAlloc3);
EXPECT_EQ(static_cast<uint8_t*>(ptr2) + kAlloc2,
static_cast<uint8_t*>(ptr3));
EXPECT_EQ(0u, allocator_->GetLargestFreeSizeNoWaiting());
EXPECT_EQ(allocator_->NumUsedBlocks(), 1u);
// Discard end block should discard it.
allocator_->DiscardBlock(ptr3);
EXPECT_EQ(allocator_->NumUsedBlocks(), 0u);
EXPECT_EQ(kAlloc3, allocator_->GetLargestFreeSizeNoWaiting());
}
// Test discard end of the buffer that has looped around.
TEST_F(RingBufferTest, DiscardLoopedEndTest) {
const unsigned int kAlloc1 = 3*kAlignment;
const unsigned int kAlloc2 = 2*kAlignment;
const unsigned int kAlloc3 = kBufferSize - kAlloc1 - kAlloc2;
void* ptr1 = allocator_->Alloc(kAlloc1);
EXPECT_EQ(kBufferSize - kAlloc1, allocator_->GetLargestFreeSizeNoWaiting());
allocator_->FreePendingToken(ptr1, helper_.get()->InsertToken());
void* ptr2 = allocator_->Alloc(kAlloc2);
EXPECT_EQ(static_cast<uint8_t*>(ptr1) + kAlloc1,
static_cast<uint8_t*>(ptr2));
EXPECT_EQ(kBufferSize - kAlloc1 - kAlloc2,
allocator_->GetLargestFreeSizeNoWaiting());
allocator_->FreePendingToken(ptr2, helper_.get()->InsertToken());
void* ptr3 = allocator_->Alloc(kAlloc3);
EXPECT_EQ(static_cast<uint8_t*>(ptr2) + kAlloc2,
static_cast<uint8_t*>(ptr3));
EXPECT_EQ(0u, allocator_->GetLargestFreeSizeNoWaiting());
allocator_->FreePendingToken(ptr3, helper_.get()->InsertToken());
// This allocation should be at the beginning again, we need to utilize
// DiscardBlock here to discard the first item so that we can allocate
// at the beginning without the FreeOldestBlock() getting called and freeing
// the whole ring buffer.
allocator_->DiscardBlock(ptr1);
void* ptr4 = allocator_->Alloc(kAlloc1);
EXPECT_EQ(ptr1, ptr4);
EXPECT_EQ(0u, allocator_->GetLargestFreeSizeNoWaiting());
EXPECT_EQ(allocator_->NumUsedBlocks(), 1u);
// Discard end block should work properly still.
allocator_->DiscardBlock(ptr4);
EXPECT_EQ(kAlloc1, allocator_->GetLargestFreeSizeNoWaiting());
EXPECT_EQ(allocator_->NumUsedBlocks(), 0u);
}
// Test discard end of the buffer that has looped around with padding.
TEST_F(RingBufferTest, DiscardEndWithPaddingTest) {
const unsigned int kAlloc1 = 3*kAlignment;
const unsigned int kAlloc2 = 2*kAlignment;
const unsigned int kPadding = kAlignment;
const unsigned int kAlloc3 = kBufferSize - kAlloc1 - kAlloc2 - kPadding;
void* ptr1 = allocator_->Alloc(kAlloc1);
EXPECT_EQ(kBufferSize - kAlloc1, allocator_->GetLargestFreeSizeNoWaiting());
allocator_->FreePendingToken(ptr1, helper_.get()->InsertToken());
void* ptr2 = allocator_->Alloc(kAlloc2);
EXPECT_EQ(static_cast<uint8_t*>(ptr1) + kAlloc1,
static_cast<uint8_t*>(ptr2));
EXPECT_EQ(kBufferSize - kAlloc1 - kAlloc2,
allocator_->GetLargestFreeSizeNoWaiting());
allocator_->FreePendingToken(ptr2, helper_.get()->InsertToken());
void* ptr3 = allocator_->Alloc(kAlloc3);
EXPECT_EQ(static_cast<uint8_t*>(ptr2) + kAlloc2,
static_cast<uint8_t*>(ptr3));
EXPECT_EQ(kPadding, allocator_->GetLargestFreeSizeNoWaiting());
allocator_->FreePendingToken(ptr3, helper_.get()->InsertToken());
// Cause it to loop around with padding at the end of ptr3.
allocator_->DiscardBlock(ptr1);
EXPECT_EQ(allocator_->NumUsedBlocks(), 0u);
void* ptr4 = allocator_->Alloc(kAlloc1);
EXPECT_EQ(ptr1, ptr4);
EXPECT_EQ(0u, allocator_->GetLargestFreeSizeNoWaiting());
EXPECT_EQ(allocator_->NumUsedBlocks(), 1u);
// Discard end block should also discard the padding.
allocator_->DiscardBlock(ptr4);
EXPECT_EQ(kAlloc1, allocator_->GetLargestFreeSizeNoWaiting());
EXPECT_EQ(allocator_->NumUsedBlocks(), 0u);
// We can test that there is padding by attempting to allocate the padding.
void* padding = allocator_->Alloc(kPadding);
EXPECT_EQ(kAlloc1, allocator_->GetLargestFreeSizeNoWaiting());
allocator_->FreePendingToken(padding, helper_.get()->InsertToken());
EXPECT_EQ(allocator_->NumUsedBlocks(), 0u);
}
// Test that discard will effectively remove all padding at the end.
TEST_F(RingBufferTest, DiscardAllPaddingFromEndTest) {
const unsigned int kAlloc1 = 3*kAlignment;
const unsigned int kAlloc2 = 2*kAlignment;
const unsigned int kAlloc3 = kBufferSize - kAlloc1 - kAlloc2;
void* ptr1 = allocator_->Alloc(kAlloc1);
EXPECT_EQ(kBufferSize - kAlloc1, allocator_->GetLargestFreeSizeNoWaiting());
allocator_->FreePendingToken(ptr1, helper_.get()->InsertToken());
void* ptr2 = allocator_->Alloc(kAlloc2);
EXPECT_EQ(static_cast<uint8_t*>(ptr1) + kAlloc1,
static_cast<uint8_t*>(ptr2));
EXPECT_EQ(kBufferSize - kAlloc1 - kAlloc2,
allocator_->GetLargestFreeSizeNoWaiting());
allocator_->FreePendingToken(ptr2, helper_.get()->InsertToken());
void* ptr3 = allocator_->Alloc(kAlloc3);
EXPECT_EQ(static_cast<uint8_t*>(ptr2) + kAlloc2,
static_cast<uint8_t*>(ptr3));
EXPECT_EQ(0u, allocator_->GetLargestFreeSizeNoWaiting());
// Discarding the middle allocation should turn it into padding.
allocator_->DiscardBlock(ptr2);
EXPECT_EQ(allocator_->NumUsedBlocks(), 1u);
EXPECT_EQ(0u, allocator_->GetLargestFreeSizeNoWaiting());
// Discarding the last allocation should discard the middle padding as well.
allocator_->DiscardBlock(ptr3);
EXPECT_EQ(allocator_->NumUsedBlocks(), 0u);
EXPECT_EQ(kAlloc2 + kAlloc3, allocator_->GetLargestFreeSizeNoWaiting());
}
// Test that discard will effectively remove all padding from the beginning.
TEST_F(RingBufferTest, DiscardAllPaddingFromBeginningTest) {
const unsigned int kAlloc1 = 3*kAlignment;
const unsigned int kAlloc2 = 2*kAlignment;
const unsigned int kAlloc3 = kBufferSize - kAlloc1 - kAlloc2;
void* ptr1 = allocator_->Alloc(kAlloc1);
EXPECT_EQ(kBufferSize - kAlloc1, allocator_->GetLargestFreeSizeNoWaiting());
allocator_->FreePendingToken(ptr1, helper_.get()->InsertToken());
void* ptr2 = allocator_->Alloc(kAlloc2);
EXPECT_EQ(static_cast<uint8_t*>(ptr1) + kAlloc1,
static_cast<uint8_t*>(ptr2));
EXPECT_EQ(kBufferSize - kAlloc1 - kAlloc2,
allocator_->GetLargestFreeSizeNoWaiting());
allocator_->FreePendingToken(ptr2, helper_.get()->InsertToken());
void* ptr3 = allocator_->Alloc(kAlloc3);
EXPECT_EQ(static_cast<uint8_t*>(ptr2) + kAlloc2,
static_cast<uint8_t*>(ptr3));
EXPECT_EQ(0u, allocator_->GetLargestFreeSizeNoWaiting());
allocator_->FreePendingToken(ptr3, helper_.get()->InsertToken());
EXPECT_EQ(allocator_->NumUsedBlocks(), 0u);
// Discarding the middle allocation should turn it into padding.
allocator_->DiscardBlock(ptr2);
EXPECT_EQ(0u, allocator_->GetLargestFreeSizeNoWaiting());
// Discarding the first allocation should discard the middle padding as well.
allocator_->DiscardBlock(ptr1);
EXPECT_EQ(kAlloc1 + kAlloc2, allocator_->GetLargestFreeSizeNoWaiting());
EXPECT_EQ(allocator_->NumUsedBlocks(), 0u);
}
TEST_F(RingBufferTest, LargestFreeSizeNoWaiting) {
// GetLargestFreeSizeNoWaiting should return the largest free aligned size.
void* ptr = allocator_->Alloc(kBufferSize);
EXPECT_EQ(0u, allocator_->GetLargestFreeSizeNoWaiting());
allocator_->ShrinkLastBlock(kBufferSize - 2 * kAlignment - 1);
EXPECT_EQ(2 * kAlignment, allocator_->GetLargestFreeSizeNoWaiting());
allocator_->FreePendingToken(ptr, helper_.get()->InsertToken());
}
} // namespace gpu