#include "gpu/command_buffer/client/cmd_buffer_helper.h"
#include <stddef.h>
#include <stdint.h>
#include <memory>
#include <vector>
#include "base/compiler_specific.h"
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/run_loop.h"
#include "base/test/task_environment.h"
#include "gpu/command_buffer/client/command_buffer_direct_locked.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::_;
const int32_t kTotalNumCommandEntries = 32;
const int32_t kCommandBufferSizeBytes =
kTotalNumCommandEntries * sizeof(CommandBufferEntry);
const int32_t kUnusedCommandId = 5;
class CommandBufferHelperTest : public testing::Test {
protected:
void SetUp() override {
command_buffer_ = std::make_unique<CommandBufferDirectLocked>();
api_mock_ = std::make_unique<AsyncAPIMock>(true, command_buffer_.get(),
command_buffer_->service());
EXPECT_CALL(*api_mock_, DoCommand(cmd::kNoop, _, _))
.WillRepeatedly(Return(error::kNoError));
helper_ = std::make_unique<CommandBufferHelper>(command_buffer_.get());
helper_->Initialize(kCommandBufferSizeBytes);
test_command_next_id_ = kUnusedCommandId;
}
void TearDown() override {
base::RunLoop().RunUntilIdle();
test_command_args_.clear();
}
int32_t ImmediateEntryCount() const {
return helper_->immediate_entry_count_;
}
void AddCommandWithExpect(error::Error _return,
unsigned int command,
int arg_count,
CommandBufferEntry *args) {
CommandHeader header;
header.size = arg_count + 1;
header.command = command;
CommandBufferEntry* cmds =
static_cast<CommandBufferEntry*>(helper_->GetSpace(arg_count + 1));
CommandBufferOffset put = 0;
UNSAFE_TODO(cmds[put++]).value_header = header;
for (int ii = 0; ii < arg_count; ++ii) {
UNSAFE_TODO(cmds[put++]) = UNSAFE_TODO(args[ii]);
}
EXPECT_CALL(*api_mock_, DoCommand(command, arg_count,
Truly(AsyncAPIMock::IsArgs(arg_count, args))))
.InSequence(sequence_)
.WillOnce(Return(_return));
}
void AddUniqueCommandWithExpect(error::Error _return, int cmd_size) {
EXPECT_GE(cmd_size, 1);
EXPECT_LT(cmd_size, kTotalNumCommandEntries);
int arg_count = cmd_size - 1;
auto args_ptr =
std::make_unique<CommandBufferEntry[]>(arg_count ? arg_count : 1);
for (int32_t ii = 0; ii < arg_count; ++ii) {
UNSAFE_TODO(args_ptr[ii]).value_uint32 = 0xF00DF00D + ii;
}
AddCommandWithExpect(
_return, test_command_next_id_++, arg_count, args_ptr.get());
test_command_args_.push_back(std::move(args_ptr));
}
void TestCommandWrappingFull(int32_t cmd_size, int32_t start_commands) {
const int32_t num_args = cmd_size - 1;
EXPECT_EQ(kTotalNumCommandEntries % cmd_size, 0);
std::vector<CommandBufferEntry> args(num_args);
for (int32_t ii = 0; ii < num_args; ++ii) {
args[ii].value_uint32 = ii + 1;
}
for (int32_t ii = 0; ii < start_commands; ++ii) {
AddCommandWithExpect(
error::kNoError, ii + kUnusedCommandId, num_args, &args[0]);
}
helper_->Finish();
EXPECT_EQ(GetPutOffset(),
(start_commands * cmd_size) % kTotalNumCommandEntries);
EXPECT_EQ(GetGetOffset(),
(start_commands * cmd_size) % kTotalNumCommandEntries);
command_buffer_->LockFlush();
for (int32_t ii = 0; ii < kTotalNumCommandEntries / cmd_size + 2; ++ii) {
AddCommandWithExpect(error::kNoError,
start_commands + ii + kUnusedCommandId,
num_args,
&args[0]);
}
command_buffer_->UnlockFlush();
helper_->Finish();
Mock::VerifyAndClearExpectations(api_mock_.get());
EXPECT_EQ(error::kNoError, GetError());
}
void CheckFreeSpace(CommandBufferOffset put, unsigned int size) {
CommandBufferOffset parser_put = GetPutOffset();
CommandBufferOffset parser_get = GetGetOffset();
CommandBufferOffset limit = put + size;
if (parser_get > parser_put) {
EXPECT_LE(parser_put, put);
EXPECT_GT(parser_get, limit);
} else {
if (put >= parser_put) {
EXPECT_GE(kTotalNumCommandEntries, limit);
} else {
EXPECT_GT(parser_get, limit);
}
}
}
int32_t GetGetOffset() {
return command_buffer_->service()->GetState().get_offset;
}
int32_t GetPutOffset() { return command_buffer_->GetServicePutOffset(); }
int32_t GetHelperGetOffset() { return helper_->cached_get_offset_; }
int32_t GetHelperPutOffset() { return helper_->put_; }
uint32_t GetHelperFlushGeneration() { return helper_->flush_generation(); }
error::Error GetError() {
return command_buffer_->GetLastState().error;
}
CommandBufferOffset get_helper_put() { return helper_->put_; }
void WaitForGetOffsetInRange(int32_t start, int32_t end) {
helper_->WaitForGetOffsetInRange(start, end);
}
std::unique_ptr<CommandBufferDirectLocked> command_buffer_;
std::unique_ptr<AsyncAPIMock> api_mock_;
std::unique_ptr<CommandBufferHelper> helper_;
std::vector<std::unique_ptr<CommandBufferEntry[]>> test_command_args_;
unsigned int test_command_next_id_;
Sequence sequence_;
base::test::SingleThreadTaskEnvironment task_environment_;
};
TEST_F(CommandBufferHelperTest, TestCalcImmediateEntriesNoRingBuffer) {
helper_->SetAutomaticFlushes(false);
EXPECT_EQ(ImmediateEntryCount(), kTotalNumCommandEntries - 1);
helper_->FreeRingBuffer();
EXPECT_TRUE(helper_->usable());
EXPECT_EQ(ImmediateEntryCount(), 0);
command_buffer_->set_fail_create_transfer_buffer(true);
helper_->WaitForAvailableEntries(1);
EXPECT_FALSE(helper_->usable());
EXPECT_EQ(ImmediateEntryCount(), 0);
}
TEST_F(CommandBufferHelperTest, TestCalcImmediateEntriesGetAtZero) {
helper_->SetAutomaticFlushes(false);
command_buffer_->LockFlush();
EXPECT_EQ(GetHelperPutOffset(), 0);
EXPECT_EQ(GetHelperGetOffset(), 0);
EXPECT_EQ(ImmediateEntryCount(), kTotalNumCommandEntries - 1);
AddUniqueCommandWithExpect(error::kNoError, 2);
EXPECT_EQ(ImmediateEntryCount(), kTotalNumCommandEntries - 3);
helper_->Finish();
Mock::VerifyAndClearExpectations(api_mock_.get());
EXPECT_EQ(error::kNoError, GetError());
}
TEST_F(CommandBufferHelperTest, TestCalcImmediateEntriesGetInMiddle) {
helper_->SetAutomaticFlushes(false);
command_buffer_->LockFlush();
AddUniqueCommandWithExpect(error::kNoError, 2);
helper_->Finish();
EXPECT_EQ(GetHelperPutOffset(), 2);
EXPECT_EQ(GetHelperGetOffset(), 2);
EXPECT_EQ(ImmediateEntryCount(), kTotalNumCommandEntries - 2);
AddUniqueCommandWithExpect(error::kNoError, 2);
EXPECT_EQ(ImmediateEntryCount(), kTotalNumCommandEntries - 4);
helper_->Finish();
Mock::VerifyAndClearExpectations(api_mock_.get());
EXPECT_EQ(error::kNoError, GetError());
}
TEST_F(CommandBufferHelperTest, TestCalcImmediateEntriesGetBeforePut) {
const int kInitGetOffset = kTotalNumCommandEntries / 4;
helper_->SetAutomaticFlushes(false);
command_buffer_->LockFlush();
AddUniqueCommandWithExpect(error::kNoError, kInitGetOffset);
helper_->Finish();
AddUniqueCommandWithExpect(error::kNoError,
kTotalNumCommandEntries - kInitGetOffset);
helper_->Flush();
EXPECT_EQ(GetHelperGetOffset(), kInitGetOffset);
EXPECT_EQ(GetHelperPutOffset(), 0);
EXPECT_EQ(ImmediateEntryCount(), kInitGetOffset - 1);
AddUniqueCommandWithExpect(error::kNoError, 2);
EXPECT_EQ(ImmediateEntryCount(), kInitGetOffset - 3);
helper_->Finish();
Mock::VerifyAndClearExpectations(api_mock_.get());
EXPECT_EQ(error::kNoError, GetError());
}
TEST_F(CommandBufferHelperTest, TestCalcImmediateEntriesAutoFlushing) {
command_buffer_->LockFlush();
EXPECT_EQ(GetHelperPutOffset(), 0);
EXPECT_EQ(GetHelperGetOffset(), 0);
helper_->SetAutomaticFlushes(false);
EXPECT_EQ(ImmediateEntryCount(), kTotalNumCommandEntries - 1);
helper_->SetAutomaticFlushes(true);
EXPECT_EQ(ImmediateEntryCount(), kTotalNumCommandEntries / kAutoFlushSmall);
AddUniqueCommandWithExpect(error::kNoError, 2);
helper_->Flush();
EXPECT_EQ(ImmediateEntryCount(), kTotalNumCommandEntries / kAutoFlushBig);
helper_->Finish();
Mock::VerifyAndClearExpectations(api_mock_.get());
EXPECT_EQ(error::kNoError, GetError());
}
TEST_F(CommandBufferHelperTest,
TestCalcImmediateEntriesAutoFlushingOrderingBarrier) {
AddUniqueCommandWithExpect(error::kNoError, kAutoFlushSmall - 1);
EXPECT_EQ(0, command_buffer_->FlushCount());
AddUniqueCommandWithExpect(error::kNoError, 1);
EXPECT_EQ(1, command_buffer_->FlushCount());
helper_->Finish();
EXPECT_EQ(2, command_buffer_->FlushCount());
AddUniqueCommandWithExpect(error::kNoError, kAutoFlushSmall - 1);
EXPECT_EQ(2, command_buffer_->FlushCount());
helper_->OrderingBarrier();
EXPECT_EQ(3, command_buffer_->FlushCount());
AddUniqueCommandWithExpect(error::kNoError, 1);
EXPECT_EQ(3, command_buffer_->FlushCount());
helper_->Finish();
Mock::VerifyAndClearExpectations(api_mock_.get());
EXPECT_EQ(error::kNoError, GetError());
}
TEST_F(CommandBufferHelperTest, TestCalcImmediateEntriesOverFlushLimit) {
command_buffer_->LockFlush();
EXPECT_EQ(GetHelperPutOffset(), 0);
EXPECT_EQ(GetHelperGetOffset(), 0);
helper_->SetAutomaticFlushes(true);
EXPECT_EQ(ImmediateEntryCount(), kTotalNumCommandEntries / kAutoFlushSmall);
AddUniqueCommandWithExpect(error::kNoError, ImmediateEntryCount() + 1);
EXPECT_EQ(ImmediateEntryCount(), 0);
AddUniqueCommandWithExpect(error::kNoError, ImmediateEntryCount() + 1);
helper_->Finish();
Mock::VerifyAndClearExpectations(api_mock_.get());
EXPECT_EQ(error::kNoError, GetError());
}
TEST_F(CommandBufferHelperTest, TestCommandProcessing) {
EXPECT_EQ(error::kNoError, GetError());
EXPECT_EQ(0, GetGetOffset());
AddCommandWithExpect(error::kNoError, kUnusedCommandId, 0, nullptr);
CommandBufferEntry args1[2];
args1[0].value_uint32 = 3;
args1[1].value_float = 4.f;
AddCommandWithExpect(error::kNoError, kUnusedCommandId, 2, args1);
CommandBufferEntry args2[2];
args2[0].value_uint32 = 5;
args2[1].value_float = 6.f;
AddCommandWithExpect(error::kNoError, kUnusedCommandId, 2, args2);
helper_->Finish();
EXPECT_EQ(GetGetOffset(), GetPutOffset());
Mock::VerifyAndClearExpectations(api_mock_.get());
EXPECT_EQ(error::kNoError, GetError());
}
TEST_F(CommandBufferHelperTest, TestCommandWrapping) {
static_assert(kTotalNumCommandEntries % 3 != 0,
"kTotalNumCommandEntries must not be a multiple of 3");
const int kNumCommands = (kTotalNumCommandEntries / 3) * 2;
CommandBufferEntry args1[2];
args1[0].value_uint32 = 5;
args1[1].value_float = 4.f;
for (int i = 0; i < kNumCommands; ++i) {
AddCommandWithExpect(error::kNoError, kUnusedCommandId + i, 2, args1);
}
helper_->Finish();
Mock::VerifyAndClearExpectations(api_mock_.get());
EXPECT_EQ(error::kNoError, GetError());
}
TEST_F(CommandBufferHelperTest, TestCommandWrappingExactMultiple) {
const int32_t kCommandSize = kTotalNumCommandEntries / 2;
const size_t kNumArgs = kCommandSize - 1;
static_assert(kTotalNumCommandEntries % kCommandSize == 0,
"kTotalNumCommandEntries should be a multiple of kCommandSize");
CommandBufferEntry args1[kNumArgs];
for (size_t ii = 0; ii < kNumArgs; ++ii) {
UNSAFE_TODO(args1[ii]).value_uint32 = ii + 1;
}
for (unsigned int i = 0; i < 5; ++i) {
AddCommandWithExpect(
error::kNoError, i + kUnusedCommandId, kNumArgs, args1);
}
helper_->Finish();
Mock::VerifyAndClearExpectations(api_mock_.get());
EXPECT_EQ(error::kNoError, GetError());
}
TEST_F(CommandBufferHelperTest, TestCommandWrappingFullAtStart) {
TestCommandWrappingFull(2, 0);
}
TEST_F(CommandBufferHelperTest, TestCommandWrappingFullInMiddle) {
TestCommandWrappingFull(2, 1);
}
TEST_F(CommandBufferHelperTest, TestCommandWrappingFullAtEnd) {
TestCommandWrappingFull(2, kTotalNumCommandEntries / 2);
}
TEST_F(CommandBufferHelperTest, TestAvailableEntries) {
CommandBufferEntry args[2];
args[0].value_uint32 = 3;
args[1].value_float = 4.f;
AddCommandWithExpect(error::kNoError, kUnusedCommandId + 1, 0, nullptr);
AddCommandWithExpect(error::kNoError, kUnusedCommandId + 2, 0, nullptr);
AddCommandWithExpect(error::kNoError, kUnusedCommandId + 3, 2, args);
AddCommandWithExpect(error::kNoError, kUnusedCommandId + 4, 2, args);
helper_->WaitForAvailableEntries(5);
CommandBufferOffset put = get_helper_put();
CheckFreeSpace(put, 5);
AddCommandWithExpect(error::kNoError, kUnusedCommandId + 5, 2, args);
helper_->Finish();
Mock::VerifyAndClearExpectations(api_mock_.get());
EXPECT_EQ(error::kNoError, GetError());
}
TEST_F(CommandBufferHelperTest, TestToken) {
CommandBufferEntry args[2];
args[0].value_uint32 = 3;
args[1].value_float = 4.f;
AddCommandWithExpect(error::kNoError, kUnusedCommandId + 3, 2, args);
CommandBufferOffset command1_put = get_helper_put();
int32_t token = helper_->InsertToken();
EXPECT_CALL(*api_mock_.get(), DoCommand(cmd::kSetToken, 1, _))
.WillOnce(DoAll(Invoke(api_mock_.get(), &AsyncAPIMock::SetToken),
Return(error::kNoError)));
AddCommandWithExpect(error::kNoError, kUnusedCommandId + 4, 2, args);
helper_->WaitForToken(token);
EXPECT_LE(command1_put, GetGetOffset());
helper_->Finish();
Mock::VerifyAndClearExpectations(api_mock_.get());
EXPECT_EQ(error::kNoError, GetError());
}
TEST_F(CommandBufferHelperTest, TestWaitForTokenFlush) {
CommandBufferEntry args[2];
args[0].value_uint32 = 3;
args[1].value_float = 4.f;
AddCommandWithExpect(error::kNoError, kUnusedCommandId + 3, 2, args);
int32_t token = helper_->InsertToken();
EXPECT_CALL(*api_mock_.get(), DoCommand(cmd::kSetToken, 1, _))
.WillOnce(DoAll(Invoke(api_mock_.get(), &AsyncAPIMock::SetToken),
Return(error::kNoError)));
int flush_count = command_buffer_->FlushCount();
helper_->WaitForToken(token);
EXPECT_EQ(command_buffer_->FlushCount(), flush_count + 1);
helper_->WaitForToken(token);
EXPECT_EQ(command_buffer_->FlushCount(), flush_count + 1);
AddCommandWithExpect(error::kNoError, kUnusedCommandId + 4, 2, args);
helper_->WaitForToken(token);
EXPECT_EQ(command_buffer_->FlushCount(), flush_count + 1);
helper_->Finish();
Mock::VerifyAndClearExpectations(api_mock_.get());
EXPECT_EQ(error::kNoError, GetError());
}
TEST_F(CommandBufferHelperTest, FreeRingBuffer) {
EXPECT_TRUE(helper_->HaveRingBuffer());
helper_->FreeRingBuffer();
EXPECT_FALSE(helper_->HaveRingBuffer());
int32_t token = helper_->InsertToken();
EXPECT_TRUE(helper_->HaveRingBuffer());
EXPECT_CALL(*api_mock_.get(), DoCommand(cmd::kSetToken, 1, _))
.WillOnce(DoAll(Invoke(api_mock_.get(), &AsyncAPIMock::SetToken),
Return(error::kNoError)));
helper_->WaitForToken(token);
helper_->FreeRingBuffer();
EXPECT_FALSE(helper_->HaveRingBuffer());
AddCommandWithExpect(error::kNoError, kUnusedCommandId, 0, nullptr);
EXPECT_TRUE(helper_->HaveRingBuffer());
helper_->Finish();
helper_->FreeRingBuffer();
EXPECT_FALSE(helper_->HaveRingBuffer());
Mock::VerifyAndClearExpectations(api_mock_.get());
AddCommandWithExpect(error::kNoError, kUnusedCommandId, 0, nullptr);
EXPECT_TRUE(helper_->HaveRingBuffer());
int32_t old_get_offset = command_buffer_->GetLastState().get_offset;
EXPECT_NE(helper_->GetPutOffsetForTest(), old_get_offset);
int old_flush_count = command_buffer_->FlushCount();
helper_->FreeRingBuffer();
EXPECT_FALSE(helper_->HaveRingBuffer());
EXPECT_EQ(command_buffer_->FlushCount(), old_flush_count + 2);
EXPECT_EQ(command_buffer_->GetLastState().get_offset, old_get_offset);
helper_->Finish();
EXPECT_FALSE(helper_->HaveRingBuffer());
EXPECT_EQ(command_buffer_->FlushCount(), old_flush_count + 2);
EXPECT_EQ(command_buffer_->GetLastState().get_offset,
helper_->GetPutOffsetForTest());
}
TEST_F(CommandBufferHelperTest, Noop) {
for (int ii = 1; ii < 4; ++ii) {
CommandBufferOffset put_before = get_helper_put();
helper_->Noop(ii);
CommandBufferOffset put_after = get_helper_put();
EXPECT_EQ(ii, put_after - put_before);
}
}
TEST_F(CommandBufferHelperTest, IsContextLost) {
EXPECT_FALSE(helper_->IsContextLost());
command_buffer_->service()->SetParseError(error::kGenericError);
EXPECT_TRUE(helper_->IsContextLost());
}
TEST_F(CommandBufferHelperTest, TestFlushGeneration) {
helper_->SetAutomaticFlushes(false);
uint32_t gen1, gen2, gen3;
gen1 = GetHelperFlushGeneration();
AddUniqueCommandWithExpect(error::kNoError, 2);
gen2 = GetHelperFlushGeneration();
helper_->Flush();
gen3 = GetHelperFlushGeneration();
EXPECT_EQ(gen2, gen1);
EXPECT_NE(gen3, gen2);
gen1 = GetHelperFlushGeneration();
AddUniqueCommandWithExpect(error::kNoError, 2);
gen2 = GetHelperFlushGeneration();
helper_->Finish();
gen3 = GetHelperFlushGeneration();
EXPECT_EQ(gen2, gen1);
EXPECT_NE(gen3, gen2);
helper_->Finish();
Mock::VerifyAndClearExpectations(api_mock_.get());
EXPECT_EQ(error::kNoError, GetError());
}
TEST_F(CommandBufferHelperTest, TestOrderingBarrierFlushGeneration) {
helper_->SetAutomaticFlushes(false);
uint32_t gen1, gen2, gen3;
gen1 = GetHelperFlushGeneration();
AddUniqueCommandWithExpect(error::kNoError, 2);
gen2 = GetHelperFlushGeneration();
helper_->OrderingBarrier();
gen3 = GetHelperFlushGeneration();
EXPECT_EQ(gen2, gen1);
EXPECT_NE(gen3, gen2);
helper_->Finish();
Mock::VerifyAndClearExpectations(api_mock_.get());
EXPECT_EQ(error::kNoError, GetError());
}
TEST_F(CommandBufferHelperTest, TestFlushToCommandBuffer) {
helper_->SetAutomaticFlushes(false);
int flush_count1, flush_count2, flush_count3;
flush_count1 = command_buffer_->FlushCount();
AddUniqueCommandWithExpect(error::kNoError, 2);
helper_->Flush();
flush_count2 = command_buffer_->FlushCount();
helper_->Flush();
flush_count3 = command_buffer_->FlushCount();
EXPECT_EQ(flush_count2, flush_count1 + 1);
EXPECT_EQ(flush_count3, flush_count2 + 1);
}
TEST_F(CommandBufferHelperTest, TestOrderingBarrierToCommandBuffer) {
helper_->SetAutomaticFlushes(false);
int flush_count1, flush_count2, flush_count3;
flush_count1 = command_buffer_->FlushCount();
AddUniqueCommandWithExpect(error::kNoError, 2);
helper_->OrderingBarrier();
flush_count2 = command_buffer_->FlushCount();
helper_->OrderingBarrier();
flush_count3 = command_buffer_->FlushCount();
EXPECT_EQ(flush_count2, flush_count1 + 1);
EXPECT_EQ(flush_count3, flush_count2 + 1);
}
TEST_F(CommandBufferHelperTest, TestWrapAroundAfterOrderingBarrier) {
helper_->SetAutomaticFlushes(false);
AddUniqueCommandWithExpect(error::kNoError, 3);
helper_->Flush();
AddUniqueCommandWithExpect(error::kNoError, 2);
helper_->OrderingBarrier();
WaitForGetOffsetInRange(5, 5);
ASSERT_EQ(kTotalNumCommandEntries % 2, 0);
for (int i = 0; i < kTotalNumCommandEntries / 2 - 2; ++i)
AddUniqueCommandWithExpect(error::kNoError, 2);
AddUniqueCommandWithExpect(error::kNoError, 1);
EXPECT_EQ(GetHelperPutOffset(), 3);
EXPECT_EQ(GetHelperGetOffset(), 5);
EXPECT_EQ(ImmediateEntryCount(), 1);
AddUniqueCommandWithExpect(error::kNoError, 2);
EXPECT_EQ(GetHelperPutOffset(), 5);
EXPECT_EQ(GetHelperGetOffset(), 3);
EXPECT_EQ(ImmediateEntryCount(), kTotalNumCommandEntries - 5);
helper_->Flush();
Mock::VerifyAndClearExpectations(api_mock_.get());
}
}