blob: 002bda4f0ed67f454a3082e40a0db874daeca37f [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 Command Buffer Helper.
#include "gpu/command_buffer/client/transfer_buffer.h"
#include <stddef.h>
#include <stdint.h>
#include <memory>
#include "base/compiler_specific.h"
#include "gpu/command_buffer/client/client_test_helper.h"
#include "gpu/command_buffer/client/cmd_buffer_helper.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::AtMost;
using ::testing::Invoke;
using ::testing::Return;
using ::testing::SetArgPointee;
using ::testing::StrictMock;
namespace gpu {
class TransferBufferTest : public testing::Test {
protected:
static const int32_t kNumCommandEntries = 400;
static const int32_t kCommandBufferSizeBytes =
kNumCommandEntries * sizeof(CommandBufferEntry);
static const uint32_t kStartingOffset = 64;
static const uint32_t kAlignment = 4;
static const uint32_t kTransferBufferSize = 256;
TransferBufferTest()
: transfer_buffer_id_(0) {
}
void SetUp() override;
void TearDown() override;
virtual void Initialize() {
ASSERT_TRUE(transfer_buffer_->Initialize(
kTransferBufferSize, kStartingOffset, kTransferBufferSize,
kTransferBufferSize, kAlignment));
}
MockClientCommandBufferMockFlush* command_buffer() const {
return command_buffer_.get();
}
std::unique_ptr<MockClientCommandBufferMockFlush> command_buffer_;
std::unique_ptr<CommandBufferHelper> helper_;
std::unique_ptr<TransferBuffer> transfer_buffer_;
int32_t transfer_buffer_id_;
};
void TransferBufferTest::SetUp() {
command_buffer_.reset(new StrictMock<MockClientCommandBufferMockFlush>());
helper_.reset(new CommandBufferHelper(command_buffer()));
ASSERT_EQ(helper_->Initialize(kCommandBufferSizeBytes),
gpu::ContextResult::kSuccess);
transfer_buffer_id_ = command_buffer()->GetNextFreeTransferBufferId();
transfer_buffer_.reset(new TransferBuffer(helper_.get()));
}
void TransferBufferTest::TearDown() {
if (transfer_buffer_->HaveBuffer()) {
EXPECT_CALL(*command_buffer(), DestroyTransferBuffer(_))
.Times(1)
.RetiresOnSaturation();
}
// For command buffer.
EXPECT_CALL(*command_buffer(), DestroyTransferBuffer(_))
.Times(1)
.RetiresOnSaturation();
EXPECT_CALL(*command_buffer(), Flush(_)).Times(AtMost(1));
EXPECT_CALL(*command_buffer(), OrderingBarrier(_)).Times(AtMost(2));
transfer_buffer_.reset();
}
// GCC requires these declarations, but MSVC requires they not be present
#ifndef _MSC_VER
const int32_t TransferBufferTest::kNumCommandEntries;
const int32_t TransferBufferTest::kCommandBufferSizeBytes;
const uint32_t TransferBufferTest::kStartingOffset;
const uint32_t TransferBufferTest::kAlignment;
const uint32_t TransferBufferTest::kTransferBufferSize;
#endif
TEST_F(TransferBufferTest, Basic) {
Initialize();
EXPECT_TRUE(transfer_buffer_->HaveBuffer());
EXPECT_EQ(transfer_buffer_id_, transfer_buffer_->GetShmId());
EXPECT_EQ(
kTransferBufferSize - kStartingOffset,
transfer_buffer_->GetCurrentMaxAllocationWithoutRealloc());
EXPECT_NE(base::UnguessableToken(), transfer_buffer_->shared_memory_guid());
}
TEST_F(TransferBufferTest, Free) {
Initialize();
EXPECT_TRUE(transfer_buffer_->HaveBuffer());
EXPECT_EQ(transfer_buffer_id_, transfer_buffer_->GetShmId());
EXPECT_NE(base::UnguessableToken(), transfer_buffer_->shared_memory_guid());
// Free buffer.
EXPECT_CALL(*command_buffer(), DestroyTransferBuffer(_))
.Times(1)
.RetiresOnSaturation();
EXPECT_CALL(*command_buffer(), OrderingBarrier(_))
.Times(1)
.RetiresOnSaturation();
transfer_buffer_->Free();
// See it's freed.
EXPECT_FALSE(transfer_buffer_->HaveBuffer());
EXPECT_EQ(base::UnguessableToken(), transfer_buffer_->shared_memory_guid());
// See that it gets reallocated.
EXPECT_EQ(transfer_buffer_id_, transfer_buffer_->GetShmId());
EXPECT_TRUE(transfer_buffer_->HaveBuffer());
EXPECT_NE(base::UnguessableToken(), transfer_buffer_->shared_memory_guid());
// Free buffer.
EXPECT_CALL(*command_buffer(), DestroyTransferBuffer(_))
.Times(1)
.RetiresOnSaturation();
EXPECT_CALL(*command_buffer(), OrderingBarrier(_))
.Times(1)
.RetiresOnSaturation();
transfer_buffer_->Free();
// See it's freed.
EXPECT_FALSE(transfer_buffer_->HaveBuffer());
EXPECT_EQ(base::UnguessableToken(), transfer_buffer_->shared_memory_guid());
// See that it gets reallocated.
EXPECT_TRUE(transfer_buffer_->AcquireResultBuffer() != nullptr);
transfer_buffer_->ReleaseResultBuffer();
EXPECT_TRUE(transfer_buffer_->HaveBuffer());
EXPECT_NE(base::UnguessableToken(), transfer_buffer_->shared_memory_guid());
// Free buffer.
EXPECT_CALL(*command_buffer(), DestroyTransferBuffer(_))
.Times(1)
.RetiresOnSaturation();
EXPECT_CALL(*command_buffer(), OrderingBarrier(_))
.Times(1)
.RetiresOnSaturation();
transfer_buffer_->Free();
// See it's freed.
EXPECT_FALSE(transfer_buffer_->HaveBuffer());
EXPECT_EQ(base::UnguessableToken(), transfer_buffer_->shared_memory_guid());
// See that it gets reallocated.
uint32_t size = 0;
void* data = transfer_buffer_->AllocUpTo(1, &size);
EXPECT_TRUE(data != nullptr);
EXPECT_TRUE(transfer_buffer_->HaveBuffer());
EXPECT_NE(base::UnguessableToken(), transfer_buffer_->shared_memory_guid());
int32_t token = helper_->InsertToken();
int32_t put_offset = helper_->GetPutOffsetForTest();
transfer_buffer_->FreePendingToken(data, token);
// Free buffer. Should cause an ordering barrier.
EXPECT_CALL(*command_buffer(), OrderingBarrier(_)).Times(AtMost(1));
EXPECT_CALL(*command_buffer(), DestroyTransferBuffer(_))
.Times(1)
.RetiresOnSaturation();
transfer_buffer_->Free();
// See it's freed.
EXPECT_FALSE(transfer_buffer_->HaveBuffer());
EXPECT_EQ(base::UnguessableToken(), transfer_buffer_->shared_memory_guid());
// Free should not have caused a finish.
EXPECT_LT(command_buffer_->GetState().get_offset, put_offset);
// See that it gets reallocated.
transfer_buffer_->GetShmId();
EXPECT_TRUE(transfer_buffer_->HaveBuffer());
EXPECT_NE(base::UnguessableToken(), transfer_buffer_->shared_memory_guid());
EXPECT_EQ(
kTransferBufferSize - kStartingOffset,
transfer_buffer_->GetCurrentMaxAllocationWithoutRealloc());
// Test freeing twice.
EXPECT_CALL(*command_buffer(), DestroyTransferBuffer(_))
.Times(1)
.RetiresOnSaturation();
EXPECT_CALL(*command_buffer(), OrderingBarrier(_))
.Times(1)
.RetiresOnSaturation();
transfer_buffer_->Free();
transfer_buffer_->Free();
}
TEST_F(TransferBufferTest, TooLargeAllocation) {
Initialize();
// Check that we can't allocate large than max size.
void* ptr = transfer_buffer_->Alloc(kTransferBufferSize + 1);
EXPECT_TRUE(ptr == nullptr);
// Check we if we try to allocate larger than max we get max.
uint32_t size_allocated = 0;
ptr = transfer_buffer_->AllocUpTo(
kTransferBufferSize + 1, &size_allocated);
ASSERT_TRUE(ptr != nullptr);
EXPECT_EQ(kTransferBufferSize - kStartingOffset, size_allocated);
transfer_buffer_->FreePendingToken(ptr, 1);
}
TEST_F(TransferBufferTest, MemoryAlignmentAfterZeroAllocation) {
Initialize();
void* ptr = transfer_buffer_->Alloc(0);
EXPECT_EQ((reinterpret_cast<uintptr_t>(ptr) & (kAlignment - 1)), 0u);
transfer_buffer_->FreePendingToken(ptr, helper_->InsertToken());
// Check that the pointer is aligned on the following allocation.
ptr = transfer_buffer_->Alloc(4);
EXPECT_EQ((reinterpret_cast<uintptr_t>(ptr) & (kAlignment - 1)), 0u);
transfer_buffer_->FreePendingToken(ptr, helper_->InsertToken());
}
class MockClientCommandBufferCanFail : public MockClientCommandBufferMockFlush {
public:
MockClientCommandBufferCanFail() = default;
~MockClientCommandBufferCanFail() override = default;
MOCK_METHOD2(CreateTransferBuffer,
scoped_refptr<Buffer>(uint32_t size, int32_t* id));
scoped_refptr<gpu::Buffer> RealCreateTransferBuffer(uint32_t size,
int32_t* id) {
return MockClientCommandBufferMockFlush::CreateTransferBuffer(size, id);
}
};
class TransferBufferExpandContractTest : public testing::Test {
protected:
static const int32_t kNumCommandEntries = 400;
static const int32_t kCommandBufferSizeBytes =
kNumCommandEntries * sizeof(CommandBufferEntry);
static const uint32_t kStartingOffset = 64;
static const uint32_t kAlignment = 4;
static const uint32_t kStartTransferBufferSize = 256;
static const uint32_t kMaxTransferBufferSize = 1024;
static const uint32_t kMinTransferBufferSize = 128;
TransferBufferExpandContractTest()
: transfer_buffer_id_(0) {
}
void SetUp() override;
void TearDown() override;
MockClientCommandBufferCanFail* command_buffer() const {
return command_buffer_.get();
}
std::unique_ptr<MockClientCommandBufferCanFail> command_buffer_;
std::unique_ptr<CommandBufferHelper> helper_;
std::unique_ptr<TransferBuffer> transfer_buffer_;
int32_t transfer_buffer_id_;
};
void TransferBufferExpandContractTest::SetUp() {
command_buffer_.reset(new StrictMock<MockClientCommandBufferCanFail>());
command_buffer_->SetTokenForSetGetBuffer(0);
EXPECT_CALL(*command_buffer(),
CreateTransferBuffer(kCommandBufferSizeBytes, _))
.WillOnce(Invoke(
command_buffer(),
&MockClientCommandBufferCanFail::RealCreateTransferBuffer))
.RetiresOnSaturation();
helper_.reset(new CommandBufferHelper(command_buffer()));
ASSERT_EQ(helper_->Initialize(kCommandBufferSizeBytes),
gpu::ContextResult::kSuccess);
transfer_buffer_id_ = command_buffer()->GetNextFreeTransferBufferId();
EXPECT_CALL(*command_buffer(),
CreateTransferBuffer(kStartTransferBufferSize, _))
.WillOnce(Invoke(
command_buffer(),
&MockClientCommandBufferCanFail::RealCreateTransferBuffer))
.RetiresOnSaturation();
transfer_buffer_.reset(new TransferBuffer(helper_.get()));
ASSERT_TRUE(transfer_buffer_->Initialize(
kStartTransferBufferSize, kStartingOffset, kMinTransferBufferSize,
kMaxTransferBufferSize, kAlignment));
}
void TransferBufferExpandContractTest::TearDown() {
if (transfer_buffer_->HaveBuffer()) {
EXPECT_CALL(*command_buffer(), DestroyTransferBuffer(_))
.Times(1)
.RetiresOnSaturation();
EXPECT_CALL(*command_buffer(), OrderingBarrier(_))
.Times(1)
.RetiresOnSaturation();
}
// For command buffer.
EXPECT_CALL(*command_buffer(), DestroyTransferBuffer(_))
.Times(1)
.RetiresOnSaturation();
EXPECT_CALL(*command_buffer(), OrderingBarrier(_))
.Times(1)
.RetiresOnSaturation();
EXPECT_CALL(*command_buffer(), Flush(_)).Times(1).RetiresOnSaturation();
transfer_buffer_.reset();
}
// GCC requires these declarations, but MSVC requires they not be present
#ifndef _MSC_VER
const int32_t TransferBufferExpandContractTest::kNumCommandEntries;
const int32_t TransferBufferExpandContractTest::kCommandBufferSizeBytes;
const uint32_t TransferBufferExpandContractTest::kStartingOffset;
const uint32_t TransferBufferExpandContractTest::kAlignment;
const uint32_t TransferBufferExpandContractTest::kStartTransferBufferSize;
const uint32_t TransferBufferExpandContractTest::kMaxTransferBufferSize;
const uint32_t TransferBufferExpandContractTest::kMinTransferBufferSize;
#endif
TEST_F(TransferBufferExpandContractTest, ExpandWithSmallAllocations) {
int32_t token = helper_->InsertToken();
EXPECT_FALSE(helper_->HasTokenPassed(token));
auto ExpectCreateTransferBuffer = [&](int size) {
EXPECT_CALL(*command_buffer(), DestroyTransferBuffer(_))
.Times(1)
.RetiresOnSaturation();
EXPECT_CALL(*command_buffer(), OrderingBarrier(_))
.Times(1)
.RetiresOnSaturation();
EXPECT_CALL(*command_buffer(), CreateTransferBuffer(size, _))
.WillOnce(
Invoke(command_buffer(),
&MockClientCommandBufferCanFail::RealCreateTransferBuffer))
.RetiresOnSaturation();
};
// Check it starts at starting size.
EXPECT_EQ(
kStartTransferBufferSize - kStartingOffset,
transfer_buffer_->GetCurrentMaxAllocationWithoutRealloc());
// Fill the free space.
uint32_t size_allocated = 0;
void* ptr = transfer_buffer_->AllocUpTo(transfer_buffer_->GetFreeSize(),
&size_allocated);
transfer_buffer_->FreePendingToken(ptr, token);
// Allocate one more byte to force expansion.
ExpectCreateTransferBuffer(kStartTransferBufferSize * 2);
ptr = transfer_buffer_->AllocUpTo(1, &size_allocated);
ASSERT_TRUE(ptr != nullptr);
EXPECT_EQ(1u, size_allocated);
transfer_buffer_->FreePendingToken(ptr, token);
// Fill free space and expand again.
ptr = transfer_buffer_->AllocUpTo(transfer_buffer_->GetFreeSize(),
&size_allocated);
transfer_buffer_->FreePendingToken(ptr, token);
ExpectCreateTransferBuffer(kStartTransferBufferSize * 4);
ptr = transfer_buffer_->AllocUpTo(1, &size_allocated);
ASSERT_TRUE(ptr != nullptr);
EXPECT_EQ(1u, size_allocated);
transfer_buffer_->FreePendingToken(ptr, token);
// Try to expand again, no expansion should occur because we are at max.
ptr = transfer_buffer_->AllocUpTo(transfer_buffer_->GetFreeSize(),
&size_allocated);
transfer_buffer_->FreePendingToken(ptr, token);
EXPECT_CALL(*command_buffer(), Flush(_)).Times(1).RetiresOnSaturation();
ptr = transfer_buffer_->AllocUpTo(1, &size_allocated);
ASSERT_TRUE(ptr != nullptr);
EXPECT_EQ(1u, size_allocated);
transfer_buffer_->FreePendingToken(ptr, token);
EXPECT_EQ(kMaxTransferBufferSize - kStartingOffset,
transfer_buffer_->GetCurrentMaxAllocationWithoutRealloc());
}
// Verify that expansion does not happen when there are blocks in use.
TEST_F(TransferBufferExpandContractTest, NoExpandWithInUseAllocation) {
EXPECT_CALL(*command_buffer(), Flush(_)).Times(1).RetiresOnSaturation();
int32_t token = helper_->InsertToken();
EXPECT_FALSE(helper_->HasTokenPassed(token));
// Check it starts at starting size.
EXPECT_EQ(kStartTransferBufferSize - kStartingOffset,
transfer_buffer_->GetCurrentMaxAllocationWithoutRealloc());
// Fill the free space in two blocks.
uint32_t block_size_1 = transfer_buffer_->GetFreeSize() / 2;
uint32_t block_size_2 = transfer_buffer_->GetFreeSize() - block_size_1;
uint32_t size_allocated = 0;
void* block1 = transfer_buffer_->AllocUpTo(block_size_1, &size_allocated);
EXPECT_EQ(block_size_1, size_allocated);
void* block2 = transfer_buffer_->AllocUpTo(block_size_2, &size_allocated);
EXPECT_EQ(block_size_2, size_allocated);
transfer_buffer_->FreePendingToken(block1, token);
// Expansion tries to happens when GetFreeSize() is not enough for the
// allocation.
EXPECT_EQ(0u, transfer_buffer_->GetFreeSize());
// Allocate one more byte to try to force expansion, however there are
// blocks in use, so this should not expand.
void* block3 = transfer_buffer_->AllocUpTo(1, &size_allocated);
ASSERT_TRUE(block3 != nullptr);
EXPECT_EQ(1u, size_allocated);
transfer_buffer_->FreePendingToken(block3, token);
transfer_buffer_->FreePendingToken(block2, token);
// No reallocs should have occurred.
EXPECT_EQ(kStartTransferBufferSize - kStartingOffset,
transfer_buffer_->GetCurrentMaxAllocationWithoutRealloc());
}
TEST_F(TransferBufferExpandContractTest, ExpandWithLargeAllocations) {
int32_t token = helper_->InsertToken();
EXPECT_FALSE(helper_->HasTokenPassed(token));
auto ExpectCreateTransferBuffer = [&](int size) {
EXPECT_CALL(*command_buffer(), DestroyTransferBuffer(_))
.Times(1)
.RetiresOnSaturation();
EXPECT_CALL(*command_buffer(), OrderingBarrier(_))
.Times(1)
.RetiresOnSaturation();
EXPECT_CALL(*command_buffer(), CreateTransferBuffer(size, _))
.WillOnce(
Invoke(command_buffer(),
&MockClientCommandBufferCanFail::RealCreateTransferBuffer))
.RetiresOnSaturation();
};
// Check it starts at starting size.
EXPECT_EQ(kStartTransferBufferSize - kStartingOffset,
transfer_buffer_->GetCurrentMaxAllocationWithoutRealloc());
// Allocate one byte more than the free space to force expansion.
uint32_t size_allocated = 0;
ExpectCreateTransferBuffer(kStartTransferBufferSize * 2);
void* ptr = transfer_buffer_->AllocUpTo(transfer_buffer_->GetFreeSize() + 1,
&size_allocated);
transfer_buffer_->FreePendingToken(ptr, token);
// Expand again.
ExpectCreateTransferBuffer(kStartTransferBufferSize * 4);
uint32_t size_requested = transfer_buffer_->GetFreeSize() + 1;
ptr = transfer_buffer_->AllocUpTo(size_requested, &size_allocated);
ASSERT_TRUE(ptr != nullptr);
EXPECT_EQ(size_requested, size_allocated);
transfer_buffer_->FreePendingToken(ptr, token);
// Try to expand again, no expansion should occur because we are at max.
EXPECT_CALL(*command_buffer(), Flush(_)).Times(1).RetiresOnSaturation();
size_requested =
transfer_buffer_->GetCurrentMaxAllocationWithoutRealloc() + 1;
ptr = transfer_buffer_->AllocUpTo(size_requested, &size_allocated);
EXPECT_LT(size_allocated, size_requested);
transfer_buffer_->FreePendingToken(ptr, token);
EXPECT_EQ(kMaxTransferBufferSize - kStartingOffset,
transfer_buffer_->GetCurrentMaxAllocationWithoutRealloc());
}
TEST_F(TransferBufferExpandContractTest, ShrinkRingBuffer) {
int32_t token = helper_->InsertToken();
// For this test we want all allocations to be freed immediately.
command_buffer_->SetToken(token);
EXPECT_TRUE(helper_->HasTokenPassed(token));
auto ExpectCreateTransferBuffer = [&](int size) {
EXPECT_CALL(*command_buffer(), DestroyTransferBuffer(_))
.Times(1)
.RetiresOnSaturation();
EXPECT_CALL(*command_buffer(), OrderingBarrier(_))
.Times(1)
.RetiresOnSaturation();
EXPECT_CALL(*command_buffer(), CreateTransferBuffer(size, _))
.WillOnce(
Invoke(command_buffer(),
&MockClientCommandBufferCanFail::RealCreateTransferBuffer))
.RetiresOnSaturation();
};
// Expand the ring buffer to the maximum size.
ExpectCreateTransferBuffer(kMaxTransferBufferSize);
void* ptr = transfer_buffer_->Alloc(kMaxTransferBufferSize - kStartingOffset);
EXPECT_TRUE(ptr != nullptr);
transfer_buffer_->FreePendingToken(ptr, token);
// We shouldn't shrink before we reach the allocation threshold.
for (uint32_t allocated = kMaxTransferBufferSize - kStartingOffset;
allocated < (kStartTransferBufferSize + kStartingOffset) *
(TransferBuffer::kShrinkThreshold);) {
ptr = transfer_buffer_->Alloc(kStartTransferBufferSize);
EXPECT_TRUE(ptr != nullptr);
transfer_buffer_->FreePendingToken(ptr, token);
allocated += kStartTransferBufferSize;
}
// The next allocation should trip the threshold and shrink.
ExpectCreateTransferBuffer(kStartTransferBufferSize * 2);
ptr = transfer_buffer_->Alloc(1);
EXPECT_TRUE(ptr != nullptr);
transfer_buffer_->FreePendingToken(ptr, token);
}
TEST_F(TransferBufferExpandContractTest, Contract) {
// Check it starts at starting size.
EXPECT_EQ(
kStartTransferBufferSize - kStartingOffset,
transfer_buffer_->GetCurrentMaxAllocationWithoutRealloc());
// Free buffer.
EXPECT_CALL(*command_buffer(), DestroyTransferBuffer(_))
.Times(1)
.RetiresOnSaturation();
EXPECT_CALL(*command_buffer(), OrderingBarrier(_))
.Times(1)
.RetiresOnSaturation();
transfer_buffer_->Free();
// See it's freed.
EXPECT_FALSE(transfer_buffer_->HaveBuffer());
// Try to allocate again, fail first request
EXPECT_CALL(*command_buffer(),
CreateTransferBuffer(kStartTransferBufferSize, _))
.WillOnce(
DoAll(SetArgPointee<1>(-1), Return(scoped_refptr<gpu::Buffer>())))
.RetiresOnSaturation();
EXPECT_CALL(*command_buffer(),
CreateTransferBuffer(kMinTransferBufferSize, _))
.WillOnce(Invoke(
command_buffer(),
&MockClientCommandBufferCanFail::RealCreateTransferBuffer))
.RetiresOnSaturation();
const uint32_t kSize1 = 256 - kStartingOffset;
const uint32_t kSize2 = 128 - kStartingOffset;
uint32_t size_allocated = 0;
void* ptr = transfer_buffer_->AllocUpTo(kSize1, &size_allocated);
ASSERT_TRUE(ptr != nullptr);
EXPECT_EQ(kSize2, size_allocated);
EXPECT_EQ(kSize2, transfer_buffer_->GetCurrentMaxAllocationWithoutRealloc());
transfer_buffer_->FreePendingToken(ptr, 1);
// Free buffer.
EXPECT_CALL(*command_buffer(), DestroyTransferBuffer(_))
.Times(1)
.RetiresOnSaturation();
EXPECT_CALL(*command_buffer(), OrderingBarrier(_))
.Times(1)
.RetiresOnSaturation();
transfer_buffer_->Free();
// See it's freed.
EXPECT_FALSE(transfer_buffer_->HaveBuffer());
// Try to allocate again,
EXPECT_CALL(*command_buffer(),
CreateTransferBuffer(kMinTransferBufferSize, _))
.WillOnce(Invoke(
command_buffer(),
&MockClientCommandBufferCanFail::RealCreateTransferBuffer))
.RetiresOnSaturation();
ptr = transfer_buffer_->AllocUpTo(kSize1, &size_allocated);
ASSERT_TRUE(ptr != nullptr);
EXPECT_EQ(kSize2, size_allocated);
EXPECT_EQ(kSize2, transfer_buffer_->GetCurrentMaxAllocationWithoutRealloc());
transfer_buffer_->FreePendingToken(ptr, 1);
}
TEST_F(TransferBufferExpandContractTest, OutOfMemory) {
// Free buffer.
EXPECT_CALL(*command_buffer(), DestroyTransferBuffer(_))
.Times(1)
.RetiresOnSaturation();
EXPECT_CALL(*command_buffer(), OrderingBarrier(_))
.Times(1)
.RetiresOnSaturation();
transfer_buffer_->Free();
// See it's freed.
EXPECT_FALSE(transfer_buffer_->HaveBuffer());
// Try to allocate again, fail both requests.
EXPECT_CALL(*command_buffer(), CreateTransferBuffer(_, _))
.WillOnce(
DoAll(SetArgPointee<1>(-1), Return(scoped_refptr<gpu::Buffer>())))
.WillOnce(
DoAll(SetArgPointee<1>(-1), Return(scoped_refptr<gpu::Buffer>())))
.WillOnce(
DoAll(SetArgPointee<1>(-1), Return(scoped_refptr<gpu::Buffer>())))
.RetiresOnSaturation();
const uint32_t kSize1 = 512 - kStartingOffset;
uint32_t size_allocated = 0;
void* ptr = transfer_buffer_->AllocUpTo(kSize1, &size_allocated);
ASSERT_TRUE(ptr == nullptr);
EXPECT_FALSE(transfer_buffer_->HaveBuffer());
}
TEST_F(TransferBufferExpandContractTest, ReallocsToDefault) {
// Free buffer.
EXPECT_CALL(*command_buffer(), DestroyTransferBuffer(_))
.Times(1)
.RetiresOnSaturation();
EXPECT_CALL(*command_buffer(), OrderingBarrier(_))
.Times(1)
.RetiresOnSaturation();
transfer_buffer_->Free();
// See it's freed.
EXPECT_FALSE(transfer_buffer_->HaveBuffer());
// See that it gets reallocated.
EXPECT_CALL(*command_buffer(),
CreateTransferBuffer(kStartTransferBufferSize, _))
.WillOnce(Invoke(
command_buffer(),
&MockClientCommandBufferCanFail::RealCreateTransferBuffer))
.RetiresOnSaturation();
EXPECT_EQ(transfer_buffer_id_, transfer_buffer_->GetShmId());
EXPECT_TRUE(transfer_buffer_->HaveBuffer());
// Check it's the default size.
EXPECT_EQ(
kStartTransferBufferSize - kStartingOffset,
transfer_buffer_->GetCurrentMaxAllocationWithoutRealloc());
}
TEST_F(TransferBufferExpandContractTest, Shrink) {
uint32_t alloc_size = transfer_buffer_->GetFreeSize();
EXPECT_EQ(kStartTransferBufferSize - kStartingOffset, alloc_size);
uint32_t size_allocated = 0;
void* ptr = transfer_buffer_->AllocUpTo(alloc_size, &size_allocated);
ASSERT_NE(ptr, nullptr);
EXPECT_EQ(alloc_size, size_allocated);
EXPECT_GT(alloc_size, 0u);
EXPECT_EQ(0u, transfer_buffer_->GetFreeSize());
// Shrink once.
const uint32_t shrink_size1 = 64;
EXPECT_LT(shrink_size1, alloc_size);
transfer_buffer_->ShrinkLastBlock(shrink_size1 - kAlignment + 1);
EXPECT_EQ(alloc_size - shrink_size1, transfer_buffer_->GetFreeSize());
// Shrink again.
const uint32_t shrink_size2 = 32;
EXPECT_LT(shrink_size2, shrink_size1);
transfer_buffer_->ShrinkLastBlock(shrink_size2);
EXPECT_EQ(alloc_size - shrink_size2, transfer_buffer_->GetFreeSize());
// Shrink to zero (minimum size is kAlignment).
transfer_buffer_->ShrinkLastBlock(0);
EXPECT_EQ(alloc_size - kAlignment, transfer_buffer_->GetFreeSize());
transfer_buffer_->FreePendingToken(ptr, 1);
}
TEST_F(TransferBufferTest, MultipleAllocsAndFrees) {
// An arbitrary size, but is aligned so no padding needed.
constexpr uint32_t kArbitrarySize = 16;
Initialize();
uint32_t original_free_size = transfer_buffer_->GetFreeSize();
EXPECT_EQ(transfer_buffer_->GetSize(), original_free_size);
EXPECT_EQ(transfer_buffer_->GetFragmentedFreeSize(), original_free_size);
void* ptr1 = transfer_buffer_->Alloc(kArbitrarySize);
EXPECT_NE(ptr1, nullptr);
EXPECT_EQ(transfer_buffer_->GetSize(), original_free_size);
EXPECT_EQ(transfer_buffer_->GetFreeSize(),
original_free_size - kArbitrarySize);
EXPECT_EQ(transfer_buffer_->GetFragmentedFreeSize(),
original_free_size - kArbitrarySize);
void* ptr2 = transfer_buffer_->Alloc(kArbitrarySize);
EXPECT_NE(ptr2, nullptr);
EXPECT_EQ(transfer_buffer_->GetSize(), original_free_size);
EXPECT_EQ(transfer_buffer_->GetFreeSize(),
original_free_size - kArbitrarySize * 2);
EXPECT_EQ(transfer_buffer_->GetFragmentedFreeSize(),
original_free_size - kArbitrarySize * 2);
void* ptr3 = transfer_buffer_->Alloc(kArbitrarySize);
EXPECT_NE(ptr3, nullptr);
EXPECT_EQ(transfer_buffer_->GetSize(), original_free_size);
EXPECT_EQ(transfer_buffer_->GetFreeSize(),
original_free_size - kArbitrarySize * 3);
EXPECT_EQ(transfer_buffer_->GetFragmentedFreeSize(),
original_free_size - kArbitrarySize * 3);
// Generate tokens in order, but submit out of order.
auto token1 = helper_->InsertToken();
auto token2 = helper_->InsertToken();
auto token3 = helper_->InsertToken();
auto token4 = helper_->InsertToken();
// Freeing the final block here, is not perceivable because it's a hole.
transfer_buffer_->FreePendingToken(ptr3, token3);
EXPECT_EQ(transfer_buffer_->GetSize(), original_free_size);
EXPECT_EQ(transfer_buffer_->GetFreeSize(),
original_free_size - kArbitrarySize * 3);
EXPECT_EQ(transfer_buffer_->GetFragmentedFreeSize(),
original_free_size - kArbitrarySize * 3);
// Freeing the first block here leaves the second plus a hole after, so
// perceived two blocks not free yet. The free size (no waiting) has not
// changed because the free_offset_ has not moved, but the fragmented free
// size gets bigger because in_use_offset_ has moved past the first block.
transfer_buffer_->FreePendingToken(ptr1, token1);
EXPECT_EQ(transfer_buffer_->GetSize(), original_free_size);
EXPECT_EQ(transfer_buffer_->GetFreeSize(),
original_free_size - kArbitrarySize * 3);
EXPECT_EQ(transfer_buffer_->GetFragmentedFreeSize(),
original_free_size - kArbitrarySize * 2);
// Allocate a 4th block. This leaves the state as: Freed Used Freed Used
void* ptr4 = transfer_buffer_->Alloc(kArbitrarySize);
EXPECT_EQ(transfer_buffer_->GetSize(), original_free_size);
EXPECT_EQ(transfer_buffer_->GetFreeSize(),
original_free_size - kArbitrarySize * 4);
EXPECT_EQ(transfer_buffer_->GetFragmentedFreeSize(),
original_free_size - kArbitrarySize * 3);
// Freeing the second and fourth block makes everything free, so back to
// original size.
transfer_buffer_->FreePendingToken(ptr4, token4);
transfer_buffer_->FreePendingToken(ptr2, token2);
EXPECT_EQ(transfer_buffer_->GetSize(), original_free_size);
EXPECT_EQ(transfer_buffer_->GetFreeSize(), original_free_size);
EXPECT_EQ(transfer_buffer_->GetFragmentedFreeSize(), original_free_size);
}
#if defined(GTEST_HAS_DEATH_TEST) && DCHECK_IS_ON()
TEST_F(TransferBufferTest, ResizeDuringScopedResultPtr) {
Initialize();
ScopedResultPtr<int> ptr(transfer_buffer_.get());
// If an attempt is made to resize the transfer buffer while a result
// pointer exists, we should hit a CHECK. Allocate just enough to force a
// resize.
uint32_t size_allocated;
ASSERT_DEATH(transfer_buffer_->AllocUpTo(transfer_buffer_->GetFreeSize() + 1,
&size_allocated),
"outstanding_result_pointer_");
}
TEST_F(TransferBufferTest, AllocDuringScopedResultPtr) {
Initialize();
ScopedResultPtr<int> ptr(transfer_buffer_.get());
// If an attempt is made to allocate any amount in the transfer buffer while a
// result pointer exists, we should hit a DCHECK.
uint32_t size_allocated;
ASSERT_DEATH(transfer_buffer_->AllocUpTo(transfer_buffer_->GetFreeSize() + 1,
&size_allocated),
"outstanding_result_pointer_");
}
TEST_F(TransferBufferTest, TwoScopedResultPtrs) {
Initialize();
// Attempting to create two ScopedResultPtrs at the same time should DCHECK.
ScopedResultPtr<int> ptr(transfer_buffer_.get());
ASSERT_DEATH(ScopedResultPtr<int>(transfer_buffer_.get()),
"outstanding_result_pointer_");
}
#endif // defined(GTEST_HAS_DEATH_TEST) && DCHECK_IS_ON()
} // namespace gpu