// Copyright (c) 2011 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 "base/callback.h" #include "base/message_loop.h" #include "gpu/command_buffer/client/cmd_buffer_helper.h" #include "gpu/command_buffer/service/mocks.h" #include "gpu/command_buffer/service/command_buffer_service.h" #include "gpu/command_buffer/service/gpu_scheduler.h" #include "testing/gtest/include/gtest/gtest.h" #if defined(OS_MACOSX) #include "base/mac/scoped_nsautorelease_pool.h" #endif namespace gpu { using testing::Return; using testing::Mock; using testing::Truly; using testing::Sequence; using testing::DoAll; using testing::Invoke; using testing::_; const int32 kTotalNumCommandEntries = 12; const int32 kUsableNumCommandEntries = 10; const int32 kCommandBufferSizeBytes = kTotalNumCommandEntries * sizeof(CommandBufferEntry); const int32 kUnusedCommandId = 5; // we use 0 and 2 currently. // Test fixture for CommandBufferHelper test - Creates a CommandBufferHelper, // using a CommandBufferEngine with a mock AsyncAPIInterface for its interface // (calling it directly, not through the RPC mechanism). class CommandBufferHelperTest : public testing::Test { protected: // Helper so mock can handle the Jump command. class DoJumpCommand { public: explicit DoJumpCommand(CommandParser* parser) : parser_(parser) { } error::Error DoCommand( unsigned int command, unsigned int arg_count, const void* cmd_data) { const cmd::Jump* jump_cmd = static_cast(cmd_data); parser_->set_get(jump_cmd->offset); return error::kNoError; }; private: CommandParser* parser_; }; virtual void SetUp() { api_mock_.reset(new AsyncAPIMock); // ignore noops in the mock - we don't want to inspect the internals of the // helper. EXPECT_CALL(*api_mock_, DoCommand(cmd::kNoop, _, _)) .WillRepeatedly(Return(error::kNoError)); command_buffer_.reset(new CommandBufferService); command_buffer_->Initialize(kCommandBufferSizeBytes); Buffer ring_buffer = command_buffer_->GetRingBuffer(); parser_ = new CommandParser(ring_buffer.ptr, ring_buffer.size, 0, ring_buffer.size, 0, api_mock_.get()); do_jump_command_.reset(new DoJumpCommand(parser_)); EXPECT_CALL(*api_mock_, DoCommand(cmd::kJump, _, _)) .WillRepeatedly( Invoke(do_jump_command_.get(), &DoJumpCommand::DoCommand)); gpu_scheduler_.reset(new GpuScheduler( command_buffer_.get(), NULL, parser_)); command_buffer_->SetPutOffsetChangeCallback(NewCallback( gpu_scheduler_.get(), &GpuScheduler::PutChanged)); api_mock_->set_engine(gpu_scheduler_.get()); helper_.reset(new CommandBufferHelper(command_buffer_.get())); helper_->Initialize(kCommandBufferSizeBytes); } virtual void TearDown() { // If the GpuScheduler posts any tasks, this forces them to run. MessageLoop::current()->RunAllPending(); } // Adds a command to the buffer through the helper, while adding it as an // expected call on the API mock. 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 = helper_->GetSpace(arg_count + 1); CommandBufferOffset put = 0; cmds[put++].value_header = header; for (int ii = 0; ii < arg_count; ++ii) { cmds[put++] = args[ii]; } EXPECT_CALL(*api_mock_, DoCommand(command, arg_count, Truly(AsyncAPIMock::IsArgs(arg_count, args)))) .InSequence(sequence_) .WillOnce(Return(_return)); } // Checks that the buffer from put to put+size is free in the parser. void CheckFreeSpace(CommandBufferOffset put, unsigned int size) { CommandBufferOffset parser_put = parser_->put(); CommandBufferOffset parser_get = parser_->get(); CommandBufferOffset limit = put + size; if (parser_get > parser_put) { // "busy" buffer wraps, so "free" buffer is between put (inclusive) and // get (exclusive). EXPECT_LE(parser_put, put); EXPECT_GT(parser_get, limit); } else { // "busy" buffer does not wrap, so the "free" buffer is the top side (from // put to the limit) and the bottom side (from 0 to get). if (put >= parser_put) { // we're on the top side, check we are below the limit. EXPECT_GE(kTotalNumCommandEntries, limit); } else { // we're on the bottom side, check we are below get. EXPECT_GT(parser_get, limit); } } } int32 GetGetOffset() { return command_buffer_->GetState().get_offset; } int32 GetPutOffset() { return command_buffer_->GetState().put_offset; } error::Error GetError() { return command_buffer_->GetState().error; } CommandBufferOffset get_helper_put() { return helper_->put_; } #if defined(OS_MACOSX) base::mac::ScopedNSAutoreleasePool autorelease_pool_; #endif MessageLoop message_loop_; scoped_ptr api_mock_; scoped_ptr command_buffer_; scoped_ptr gpu_scheduler_; CommandParser* parser_; scoped_ptr helper_; Sequence sequence_; scoped_ptr do_jump_command_; }; // Checks that commands in the buffer are properly executed, and that the // status/error stay valid. TEST_F(CommandBufferHelperTest, TestCommandProcessing) { // Check initial state of the engine - it should have been configured by the // helper. EXPECT_TRUE(parser_ != NULL); EXPECT_EQ(error::kNoError, GetError()); EXPECT_EQ(0, GetGetOffset()); // Add 3 commands through the helper AddCommandWithExpect(error::kNoError, kUnusedCommandId, 0, NULL); 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); // Wait until it's done. helper_->Finish(); // Check that the engine has no more work to do. EXPECT_TRUE(parser_->IsEmpty()); // Check that the commands did happen. Mock::VerifyAndClearExpectations(api_mock_.get()); // Check the error status. EXPECT_EQ(error::kNoError, GetError()); } // Checks that commands in the buffer are properly executed when wrapping the // buffer, and that the status/error stay valid. TEST_F(CommandBufferHelperTest, TestCommandWrapping) { // Add 5 commands of size 3 through the helper to make sure we do wrap. CommandBufferEntry args1[2]; args1[0].value_uint32 = 5; args1[1].value_float = 4.f; for (unsigned int i = 0; i < 5; ++i) { AddCommandWithExpect(error::kNoError, kUnusedCommandId + i, 2, args1); } helper_->Finish(); // Check that the commands did happen. Mock::VerifyAndClearExpectations(api_mock_.get()); // Check the error status. EXPECT_EQ(error::kNoError, GetError()); } // Checks the case where the command inserted exactly matches the space left in // the command buffer. TEST_F(CommandBufferHelperTest, TestCommandWrappingExactMultiple) { const int32 kCommandSize = 5; const size_t kNumArgs = kCommandSize - 1; COMPILE_ASSERT(kUsableNumCommandEntries % kCommandSize == 0, Not_multiple_of_num_command_entries); CommandBufferEntry args1[kNumArgs]; for (size_t ii = 0; ii < kNumArgs; ++ii) { args1[0].value_uint32 = ii + 1; } for (unsigned int i = 0; i < 5; ++i) { AddCommandWithExpect( error::kNoError, i + kUnusedCommandId, kNumArgs, args1); } helper_->Finish(); // Check that the commands did happen. Mock::VerifyAndClearExpectations(api_mock_.get()); // Check the error status. EXPECT_EQ(error::kNoError, GetError()); } // Checks that asking for available entries work, and that the parser // effectively won't use that space. TEST_F(CommandBufferHelperTest, TestAvailableEntries) { CommandBufferEntry args[2]; args[0].value_uint32 = 3; args[1].value_float = 4.f; // Add 2 commands through the helper - 8 entries AddCommandWithExpect(error::kNoError, kUnusedCommandId + 1, 0, NULL); AddCommandWithExpect(error::kNoError, kUnusedCommandId + 2, 0, NULL); AddCommandWithExpect(error::kNoError, kUnusedCommandId + 3, 2, args); AddCommandWithExpect(error::kNoError, kUnusedCommandId + 4, 2, args); // Ask for 5 entries. helper_->WaitForAvailableEntries(5); CommandBufferOffset put = get_helper_put(); CheckFreeSpace(put, 5); // Add more commands. AddCommandWithExpect(error::kNoError, kUnusedCommandId + 5, 2, args); // Wait until everything is done done. helper_->Finish(); // Check that the commands did happen. Mock::VerifyAndClearExpectations(api_mock_.get()); // Check the error status. EXPECT_EQ(error::kNoError, GetError()); } // Checks that the InsertToken/WaitForToken work. TEST_F(CommandBufferHelperTest, TestToken) { CommandBufferEntry args[2]; args[0].value_uint32 = 3; args[1].value_float = 4.f; // Add a first command. AddCommandWithExpect(error::kNoError, kUnusedCommandId + 3, 2, args); // keep track of the buffer position. CommandBufferOffset command1_put = get_helper_put(); int32 token = helper_->InsertToken(); EXPECT_CALL(*api_mock_.get(), DoCommand(cmd::kSetToken, 1, _)) .WillOnce(DoAll(Invoke(api_mock_.get(), &AsyncAPIMock::SetToken), Return(error::kNoError))); // Add another command. AddCommandWithExpect(error::kNoError, kUnusedCommandId + 4, 2, args); helper_->WaitForToken(token); // check that the get pointer is beyond the first command. EXPECT_LE(command1_put, GetGetOffset()); helper_->Finish(); // Check that the commands did happen. Mock::VerifyAndClearExpectations(api_mock_.get()); // Check the error status. EXPECT_EQ(error::kNoError, GetError()); } } // namespace gpu