blob: c71c92e1e8a55a63d0c66ec57fd8a7c7a645e9b7 [file] [log] [blame]
// Copyright (c) 2009 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/service/common_decoder.h"
#include "gpu/command_buffer/service/cmd_buffer_engine.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace gpu {
TEST(CommonDecoderBucket, Basic) {
CommonDecoder::Bucket bucket;
EXPECT_EQ(0u, bucket.size());
EXPECT_TRUE(NULL == bucket.GetData(0, 0));
}
TEST(CommonDecoderBucket, Size) {
CommonDecoder::Bucket bucket;
bucket.SetSize(24);
EXPECT_EQ(24u, bucket.size());
bucket.SetSize(12);
EXPECT_EQ(12u, bucket.size());
}
TEST(CommonDecoderBucket, GetData) {
CommonDecoder::Bucket bucket;
bucket.SetSize(24);
EXPECT_TRUE(NULL != bucket.GetData(0, 0));
EXPECT_TRUE(NULL != bucket.GetData(24, 0));
EXPECT_TRUE(NULL == bucket.GetData(25, 0));
EXPECT_TRUE(NULL != bucket.GetData(0, 24));
EXPECT_TRUE(NULL == bucket.GetData(0, 25));
bucket.SetSize(23);
EXPECT_TRUE(NULL == bucket.GetData(0, 24));
}
TEST(CommonDecoderBucket, SetData) {
CommonDecoder::Bucket bucket;
static const char data[] = "testing";
bucket.SetSize(10);
EXPECT_TRUE(bucket.SetData(data, 0, sizeof(data)));
EXPECT_EQ(0, memcmp(data, bucket.GetData(0, sizeof(data)), sizeof(data)));
EXPECT_TRUE(bucket.SetData(data, 2, sizeof(data)));
EXPECT_EQ(0, memcmp(data, bucket.GetData(2, sizeof(data)), sizeof(data)));
EXPECT_FALSE(bucket.SetData(data, 0, sizeof(data) * 2));
EXPECT_FALSE(bucket.SetData(data, 5, sizeof(data)));
}
class TestCommonDecoder : public CommonDecoder {
public:
// Overridden from AsyncAPIInterface
const char* GetCommandName(unsigned int command_id) const {
return GetCommonCommandName(static_cast<cmd::CommandId>(command_id));
}
// Overridden from AsyncAPIInterface
error::Error DoCommand(
unsigned int command,
unsigned int arg_count,
const void* cmd_data) {
return DoCommonCommand(command, arg_count, cmd_data);
}
CommonDecoder::Bucket* GetBucket(uint32 id) const {
return CommonDecoder::GetBucket(id);
}
};
class MockCommandBufferEngine : public CommandBufferEngine {
public:
static const int32 kStartValidShmId = 1;
static const int32 kValidShmId = 2;
static const int32 kInvalidShmId = 3;
static const size_t kBufferSize = 1024;
static const int32 kValidOffset = kBufferSize / 2;
static const int32 kInvalidOffset = kBufferSize;
MockCommandBufferEngine()
: CommandBufferEngine(),
token_(),
get_offset_(0) {
}
// Overridden from CommandBufferEngine.
virtual Buffer GetSharedMemoryBuffer(int32 shm_id) {
Buffer buffer;
if (IsValidSharedMemoryId(shm_id)) {
buffer.ptr = buffer_;
buffer.size = kBufferSize;
}
return buffer;
}
template <typename T>
T GetSharedMemoryAs(uint32 offset) {
DCHECK(offset < kBufferSize);
return reinterpret_cast<T>(&buffer_[offset]);
}
int32 GetSharedMemoryOffset(const void* memory) {
ptrdiff_t offset = reinterpret_cast<const int8*>(memory) - &buffer_[0];
DCHECK_GE(offset, 0);
DCHECK(static_cast<size_t>(offset) < kBufferSize);
return static_cast<int32>(offset);
}
// Overridden from CommandBufferEngine.
virtual void set_token(int32 token) {
token_ = token;
}
int32 token() const {
return token_;
}
// Overridden from CommandBufferEngine.
virtual bool SetGetOffset(int32 offset) {
if (static_cast<size_t>(offset) < kBufferSize) {
get_offset_ = offset;
return true;
}
return false;
}
// Overridden from CommandBufferEngine.
virtual int32 GetGetOffset() {
return get_offset_;
}
private:
bool IsValidSharedMemoryId(int32 shm_id) {
return shm_id == kValidShmId || shm_id == kStartValidShmId;
}
int8 buffer_[kBufferSize];
int32 token_;
int32 get_offset_;
};
const int32 MockCommandBufferEngine::kStartValidShmId;
const int32 MockCommandBufferEngine::kValidShmId;
const int32 MockCommandBufferEngine::kInvalidShmId;
const size_t MockCommandBufferEngine::kBufferSize;
const int32 MockCommandBufferEngine::kValidOffset;
const int32 MockCommandBufferEngine::kInvalidOffset;
class CommonDecoderTest : public testing::Test {
protected:
virtual void SetUp() {
decoder_.set_engine(&engine_);
}
virtual void TearDown() {
}
template <typename T>
error::Error ExecuteCmd(const T& cmd) {
COMPILE_ASSERT(T::kArgFlags == cmd::kFixed, Cmd_kArgFlags_not_kFixed);
return decoder_.DoCommand(cmd.kCmdId,
ComputeNumEntries(sizeof(cmd)) - 1,
&cmd);
}
template <typename T>
error::Error ExecuteImmediateCmd(const T& cmd, size_t data_size) {
COMPILE_ASSERT(T::kArgFlags == cmd::kAtLeastN, Cmd_kArgFlags_not_kAtLeastN);
return decoder_.DoCommand(cmd.kCmdId,
ComputeNumEntries(sizeof(cmd) + data_size) - 1,
&cmd);
}
MockCommandBufferEngine engine_;
TestCommonDecoder decoder_;
};
TEST_F(CommonDecoderTest, Initialize) {
EXPECT_EQ(0, engine_.GetGetOffset());
}
TEST_F(CommonDecoderTest, HandleNoop) {
cmd::Noop cmd;
const uint32 kSkipCount = 5;
cmd.Init(kSkipCount);
EXPECT_EQ(error::kNoError,
ExecuteImmediateCmd(
cmd, kSkipCount * kCommandBufferEntrySize));
}
TEST_F(CommonDecoderTest, SetToken) {
cmd::SetToken cmd;
const int32 kTokenId = 123;
EXPECT_EQ(0, engine_.token());
cmd.Init(kTokenId);
EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
EXPECT_EQ(kTokenId, engine_.token());
}
TEST_F(CommonDecoderTest, Jump) {
cmd::Jump cmd;
// Check valid args succeed.
cmd.Init(MockCommandBufferEngine::kValidOffset);
EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
EXPECT_EQ(MockCommandBufferEngine::kValidOffset,
engine_.GetGetOffset());
// Check invalid offset fails.
cmd.Init(MockCommandBufferEngine::kInvalidOffset);
EXPECT_NE(error::kNoError, ExecuteCmd(cmd));
EXPECT_EQ(MockCommandBufferEngine::kValidOffset,
engine_.GetGetOffset());
// Check negative offset fails
cmd.Init(-1);
EXPECT_NE(error::kNoError, ExecuteCmd(cmd));
}
// NOTE: The read_pointer checks for relative commands do not take into account
// that the actual implementation of CommandBufferEngine uses the parse
// which will advance the read pointer to the start of the next command.
TEST_F(CommonDecoderTest, JumpRelative) {
cmd::JumpRelative cmd;
// Check valid positive offset succeeds.
const int32 kPositiveOffset = 16;
cmd.Init(kPositiveOffset);
int32 read_pointer = engine_.GetGetOffset();
EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
// See note above.
EXPECT_EQ(read_pointer + kPositiveOffset, engine_.GetGetOffset());
// Check valid negative offset succeeds.
const int32 kNegativeOffset = -8;
read_pointer = engine_.GetGetOffset();
cmd.Init(kNegativeOffset);
EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
// See note above.
EXPECT_EQ(read_pointer + kNegativeOffset, engine_.GetGetOffset());
// Check invalid offset fails.
cmd.Init(MockCommandBufferEngine::kInvalidOffset);
EXPECT_NE(error::kNoError, ExecuteCmd(cmd));
// See note above.
EXPECT_EQ(read_pointer + kNegativeOffset, engine_.GetGetOffset());
// Check invalid negative offset fails.
const int32 kInvalidNegativeOffset = -kPositiveOffset + kNegativeOffset - 1;
cmd.Init(kInvalidNegativeOffset);
EXPECT_NE(error::kNoError, ExecuteCmd(cmd));
}
TEST_F(CommonDecoderTest, Call) {
cmd::Call cmd;
// Check valid args succeed.
cmd.Init(MockCommandBufferEngine::kValidOffset);
EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
EXPECT_EQ(MockCommandBufferEngine::kValidOffset,
engine_.GetGetOffset());
// Check invalid offset fails.
cmd.Init(MockCommandBufferEngine::kInvalidOffset);
EXPECT_NE(error::kNoError, ExecuteCmd(cmd));
EXPECT_EQ(MockCommandBufferEngine::kValidOffset,
engine_.GetGetOffset());
// Check negative offset fails
cmd.Init(-1);
EXPECT_NE(error::kNoError, ExecuteCmd(cmd));
// Check that the call values are on the stack.
cmd::Return return_cmd;
return_cmd.Init();
EXPECT_EQ(error::kNoError, ExecuteCmd(return_cmd));
EXPECT_EQ(0, engine_.GetGetOffset());
// Check that stack overflow fails.
cmd.Init(MockCommandBufferEngine::kValidOffset);
for (unsigned int ii = 0; ii < CommonDecoder::kMaxStackDepth; ++ii) {
EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
}
EXPECT_NE(error::kNoError, ExecuteCmd(cmd));
}
TEST_F(CommonDecoderTest, CallRelative) {
cmd::CallRelative cmd;
// Check valid positive offset succeeds.
const int32 kPositiveOffset = 16;
cmd.Init(kPositiveOffset);
int32 read_pointer_1 = engine_.GetGetOffset();
EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
// See note above.
EXPECT_EQ(read_pointer_1 + kPositiveOffset, engine_.GetGetOffset());
// Check valid negative offset succeeds.
const int32 kNegativeOffset = -8;
int32 read_pointer_2 = engine_.GetGetOffset();
cmd.Init(kNegativeOffset);
EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
// See note above.
EXPECT_EQ(read_pointer_2 + kNegativeOffset, engine_.GetGetOffset());
// Check invalid offset fails.
cmd.Init(MockCommandBufferEngine::kInvalidOffset);
EXPECT_NE(error::kNoError, ExecuteCmd(cmd));
// See note above.
EXPECT_EQ(read_pointer_2 + kNegativeOffset, engine_.GetGetOffset());
// Check invalid negative offset fails.
const int32 kInvalidNegativeOffset = -kPositiveOffset + kNegativeOffset - 1;
cmd.Init(kInvalidNegativeOffset);
EXPECT_NE(error::kNoError, ExecuteCmd(cmd));
// Check that the call values are on the stack.
cmd::Return return_cmd;
return_cmd.Init();
EXPECT_EQ(error::kNoError, ExecuteCmd(return_cmd));
// See note above.
EXPECT_EQ(read_pointer_1 + kPositiveOffset, engine_.GetGetOffset());
EXPECT_EQ(error::kNoError, ExecuteCmd(return_cmd));
// See note above.
EXPECT_EQ(0, engine_.GetGetOffset());
// Check that stack overflow fails.
cmd.Init(kPositiveOffset);
for (unsigned int ii = 0; ii < CommonDecoder::kMaxStackDepth; ++ii) {
EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
}
EXPECT_NE(error::kNoError, ExecuteCmd(cmd));
}
TEST_F(CommonDecoderTest, Return) {
// Success is tested by Call and CallRelative
// Test that an empty stack fails.
cmd::Return cmd;
cmd.Init();
EXPECT_NE(error::kNoError, ExecuteCmd(cmd));
}
TEST_F(CommonDecoderTest, SetBucketSize) {
cmd::SetBucketSize cmd;
const uint32 kBucketId = 123;
const uint32 kBucketLength1 = 1234;
const uint32 kBucketLength2 = 78;
// Check the bucket does not exist.
EXPECT_TRUE(NULL == decoder_.GetBucket(kBucketId));
// Check we can create one.
cmd.Init(kBucketId, kBucketLength1);
EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
CommonDecoder::Bucket* bucket;
bucket = decoder_.GetBucket(kBucketId);
EXPECT_TRUE(NULL != bucket);
EXPECT_EQ(kBucketLength1, bucket->size());
// Check we can change it.
cmd.Init(kBucketId, kBucketLength2);
EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
bucket = decoder_.GetBucket(kBucketId);
EXPECT_TRUE(NULL != bucket);
EXPECT_EQ(kBucketLength2, bucket->size());
// Check we can delete it.
cmd.Init(kBucketId, 0);
EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
bucket = decoder_.GetBucket(kBucketId);
EXPECT_EQ(0u, bucket->size());
}
TEST_F(CommonDecoderTest, SetBucketData) {
cmd::SetBucketSize size_cmd;
cmd::SetBucketData cmd;
static const char kData[] = "1234567890123456789";
const uint32 kBucketId = 123;
const uint32 kInvalidBucketId = 124;
size_cmd.Init(kBucketId, sizeof(kData));
EXPECT_EQ(error::kNoError, ExecuteCmd(size_cmd));
CommonDecoder::Bucket* bucket = decoder_.GetBucket(kBucketId);
// Check the data is not there.
EXPECT_NE(0, memcmp(bucket->GetData(0, sizeof(kData)), kData, sizeof(kData)));
// Check we can set it.
const uint32 kSomeOffsetInSharedMemory = 50;
void* memory = engine_.GetSharedMemoryAs<void*>(kSomeOffsetInSharedMemory);
memcpy(memory, kData, sizeof(kData));
cmd.Init(kBucketId, 0, sizeof(kData),
MockCommandBufferEngine::kValidShmId, kSomeOffsetInSharedMemory);
EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
EXPECT_EQ(0, memcmp(bucket->GetData(0, sizeof(kData)), kData, sizeof(kData)));
// Check we can set it partially.
static const char kData2[] = "ABCEDFG";
const uint32 kSomeOffsetInBucket = 5;
memcpy(memory, kData2, sizeof(kData2));
cmd.Init(kBucketId, kSomeOffsetInBucket, sizeof(kData2),
MockCommandBufferEngine::kValidShmId, kSomeOffsetInSharedMemory);
EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
EXPECT_EQ(0, memcmp(bucket->GetData(kSomeOffsetInBucket, sizeof(kData2)),
kData2, sizeof(kData2)));
const char* bucket_data = bucket->GetDataAs<const char*>(0, sizeof(kData));
// Check that nothing was affected outside of updated area.
EXPECT_EQ(kData[kSomeOffsetInBucket - 1],
bucket_data[kSomeOffsetInBucket - 1]);
EXPECT_EQ(kData[kSomeOffsetInBucket + sizeof(kData2)],
bucket_data[kSomeOffsetInBucket + sizeof(kData2)]);
// Check that it fails if the bucket_id is invalid
cmd.Init(kInvalidBucketId, kSomeOffsetInBucket, sizeof(kData2),
MockCommandBufferEngine::kValidShmId, kSomeOffsetInSharedMemory);
EXPECT_NE(error::kNoError, ExecuteCmd(cmd));
// Check that it fails if the offset is out of range.
cmd.Init(kBucketId, bucket->size(), 1,
MockCommandBufferEngine::kValidShmId, kSomeOffsetInSharedMemory);
EXPECT_NE(error::kNoError, ExecuteCmd(cmd));
// Check that it fails if the size is out of range.
cmd.Init(kBucketId, 0, bucket->size() + 1,
MockCommandBufferEngine::kValidShmId, kSomeOffsetInSharedMemory);
EXPECT_NE(error::kNoError, ExecuteCmd(cmd));
}
TEST_F(CommonDecoderTest, SetBucketDataImmediate) {
cmd::SetBucketSize size_cmd;
int8 buffer[1024];
cmd::SetBucketDataImmediate& cmd =
*reinterpret_cast<cmd::SetBucketDataImmediate*>(&buffer);
static const char kData[] = "1234567890123456789";
const uint32 kBucketId = 123;
const uint32 kInvalidBucketId = 124;
size_cmd.Init(kBucketId, sizeof(kData));
EXPECT_EQ(error::kNoError, ExecuteCmd(size_cmd));
CommonDecoder::Bucket* bucket = decoder_.GetBucket(kBucketId);
// Check the data is not there.
EXPECT_NE(0, memcmp(bucket->GetData(0, sizeof(kData)), kData, sizeof(kData)));
// Check we can set it.
void* memory = &buffer[0] + sizeof(cmd);
memcpy(memory, kData, sizeof(kData));
cmd.Init(kBucketId, 0, sizeof(kData));
EXPECT_EQ(error::kNoError,
ExecuteImmediateCmd(cmd, sizeof(kData)));
EXPECT_EQ(0, memcmp(bucket->GetData(0, sizeof(kData)), kData, sizeof(kData)));
// Check we can set it partially.
static const char kData2[] = "ABCEDFG";
const uint32 kSomeOffsetInBucket = 5;
memcpy(memory, kData2, sizeof(kData2));
cmd.Init(kBucketId, kSomeOffsetInBucket, sizeof(kData2));
EXPECT_EQ(error::kNoError,
ExecuteImmediateCmd(cmd, sizeof(kData2)));
EXPECT_EQ(0, memcmp(bucket->GetData(kSomeOffsetInBucket, sizeof(kData2)),
kData2, sizeof(kData2)));
const char* bucket_data = bucket->GetDataAs<const char*>(0, sizeof(kData));
// Check that nothing was affected outside of updated area.
EXPECT_EQ(kData[kSomeOffsetInBucket - 1],
bucket_data[kSomeOffsetInBucket - 1]);
EXPECT_EQ(kData[kSomeOffsetInBucket + sizeof(kData2)],
bucket_data[kSomeOffsetInBucket + sizeof(kData2)]);
// Check that it fails if the bucket_id is invalid
cmd.Init(kInvalidBucketId, kSomeOffsetInBucket, sizeof(kData2));
EXPECT_NE(error::kNoError,
ExecuteImmediateCmd(cmd, sizeof(kData2)));
// Check that it fails if the offset is out of range.
cmd.Init(kBucketId, bucket->size(), 1);
EXPECT_NE(error::kNoError,
ExecuteImmediateCmd(cmd, sizeof(kData2)));
// Check that it fails if the size is out of range.
cmd.Init(kBucketId, 0, bucket->size() + 1);
EXPECT_NE(error::kNoError,
ExecuteImmediateCmd(cmd, sizeof(kData2)));
}
TEST_F(CommonDecoderTest, GetBucketSize) {
cmd::SetBucketSize size_cmd;
cmd::GetBucketSize cmd;
const uint32 kBucketSize = 456;
const uint32 kBucketId = 123;
const uint32 kInvalidBucketId = 124;
size_cmd.Init(kBucketId, kBucketSize);
EXPECT_EQ(error::kNoError, ExecuteCmd(size_cmd));
// Check that the size is correct.
const uint32 kSomeOffsetInSharedMemory = 50;
uint32* memory =
engine_.GetSharedMemoryAs<uint32*>(kSomeOffsetInSharedMemory);
*memory = 0x0;
cmd.Init(kBucketId,
MockCommandBufferEngine::kValidShmId, kSomeOffsetInSharedMemory);
EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
EXPECT_EQ(kBucketSize, *memory);
// Check that it fails if the bucket_id is invalid
cmd.Init(kInvalidBucketId,
MockCommandBufferEngine::kValidShmId, kSomeOffsetInSharedMemory);
EXPECT_NE(error::kNoError, ExecuteCmd(cmd));
// Check that it fails if the result size is not set to zero
*memory = 0x1;
cmd.Init(kBucketId,
MockCommandBufferEngine::kValidShmId, kSomeOffsetInSharedMemory);
EXPECT_NE(error::kNoError, ExecuteCmd(cmd));
}
TEST_F(CommonDecoderTest, GetBucketData) {
cmd::SetBucketSize size_cmd;
cmd::SetBucketData set_cmd;
cmd::GetBucketData cmd;
static const char kData[] = "1234567890123456789";
static const char zero[sizeof(kData)] = { 0, };
const uint32 kBucketId = 123;
const uint32 kInvalidBucketId = 124;
size_cmd.Init(kBucketId, sizeof(kData));
EXPECT_EQ(error::kNoError, ExecuteCmd(size_cmd));
const uint32 kSomeOffsetInSharedMemory = 50;
uint8* memory = engine_.GetSharedMemoryAs<uint8*>(kSomeOffsetInSharedMemory);
memcpy(memory, kData, sizeof(kData));
set_cmd.Init(kBucketId, 0, sizeof(kData),
MockCommandBufferEngine::kValidShmId, kSomeOffsetInSharedMemory);
EXPECT_EQ(error::kNoError, ExecuteCmd(set_cmd));
// Check we can get the whole thing.
memset(memory, 0, sizeof(kData));
cmd.Init(kBucketId, 0, sizeof(kData),
MockCommandBufferEngine::kValidShmId, kSomeOffsetInSharedMemory);
EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
EXPECT_EQ(0, memcmp(memory, kData, sizeof(kData)));
// Check we can get a piece.
const uint32 kSomeOffsetInBucket = 5;
const uint32 kLengthOfPiece = 6;
const uint8 kSentinel = 0xff;
memset(memory, 0, sizeof(kData));
memory[-1] = kSentinel;
cmd.Init(kBucketId, kSomeOffsetInBucket, kLengthOfPiece,
MockCommandBufferEngine::kValidShmId, kSomeOffsetInSharedMemory);
EXPECT_EQ(error::kNoError, ExecuteCmd(cmd));
EXPECT_EQ(0, memcmp(memory, kData + kSomeOffsetInBucket, kLengthOfPiece));
EXPECT_EQ(0, memcmp(memory + kLengthOfPiece, zero,
sizeof(kData) - kLengthOfPiece));
EXPECT_EQ(kSentinel, memory[-1]);
// Check that it fails if the bucket_id is invalid
cmd.Init(kInvalidBucketId, kSomeOffsetInBucket, sizeof(kData),
MockCommandBufferEngine::kValidShmId, kSomeOffsetInSharedMemory);
EXPECT_NE(error::kNoError, ExecuteCmd(cmd));
// Check that it fails if the offset is invalid
cmd.Init(kBucketId, sizeof(kData) + 1, 1,
MockCommandBufferEngine::kValidShmId, kSomeOffsetInSharedMemory);
EXPECT_NE(error::kNoError, ExecuteCmd(cmd));
// Check that it fails if the size is invalid
cmd.Init(kBucketId, 0, sizeof(kData) + 1,
MockCommandBufferEngine::kValidShmId, kSomeOffsetInSharedMemory);
EXPECT_NE(error::kNoError, ExecuteCmd(cmd));
}
} // namespace gpu