| // 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 parser. |
| |
| #include <stddef.h> |
| |
| #include <memory> |
| |
| #include "base/logging.h" |
| #include "gpu/command_buffer/client/client_test_helper.h" |
| #include "gpu/command_buffer/service/command_buffer_service.h" |
| #include "gpu/command_buffer/service/mocks.h" |
| #include "gpu/command_buffer/service/transfer_buffer_manager.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| namespace gpu { |
| |
| using testing::_; |
| using testing::Mock; |
| using testing::Return; |
| using testing::Sequence; |
| using testing::SetArgPointee; |
| |
| // Test fixture for CommandBufferService test - Creates a mock |
| // AsyncAPIInterface, and a fixed size memory buffer. Also provides a simple API |
| // to create a CommandBufferService. |
| class CommandBufferServiceTest : public testing::Test, |
| public CommandBufferServiceClient { |
| public: |
| MOCK_METHOD0(OnCommandProcessed, void()); |
| |
| protected: |
| void AddDoCommandsExpect(error::Error _return, |
| int num_entries, |
| int num_processed) { |
| EXPECT_CALL(*api_mock_, DoCommands(_, _, num_entries, _)) |
| .InSequence(sequence_) |
| .WillOnce(DoAll(SetArgPointee<3>(num_processed), Return(_return))); |
| } |
| |
| // Creates a CommandBufferService, with a buffer of the specified size (in |
| // entries). |
| void MakeService(unsigned int entry_count) { |
| transfer_buffer_manager_ = std::make_unique<TransferBufferManager>(nullptr); |
| command_buffer_service_ = std::make_unique<CommandBufferService>( |
| this, transfer_buffer_manager_.get()); |
| api_mock_.reset(new AsyncAPIMock(false, command_buffer_service_.get())); |
| SetNewGetBuffer(entry_count * sizeof(CommandBufferEntry)); |
| } |
| |
| AsyncAPIMock* api_mock() { return api_mock_.get(); } |
| CommandBufferEntry* buffer() { |
| return static_cast<CommandBufferEntry*>(buffer_->memory()); |
| } |
| |
| CommandBufferService* command_buffer_service() { |
| return command_buffer_service_.get(); |
| } |
| int32_t GetGet() { return command_buffer_service_->GetState().get_offset; } |
| int32_t GetPut() { return command_buffer_service_->put_offset(); } |
| |
| error::Error SetPutAndProcessAllCommands(int32_t put) { |
| command_buffer_service_->Flush(put, api_mock()); |
| EXPECT_EQ(put, GetPut()); |
| return command_buffer_service_->GetState().error; |
| } |
| |
| int32_t SetNewGetBuffer(size_t size) { |
| int32_t id = 0; |
| buffer_ = command_buffer_service_->CreateTransferBuffer(size, &id); |
| command_buffer_service_->SetGetBuffer(id); |
| return id; |
| } |
| |
| void AdvancePut(int32_t entries) { |
| DCHECK(entries > 0); |
| CommandBufferOffset put = GetPut(); |
| CommandHeader header; |
| header.size = entries; |
| header.command = 1; |
| buffer()[put].value_header = header; |
| put += entries; |
| AddDoCommandsExpect(error::kNoError, entries, entries); |
| EXPECT_EQ(error::kNoError, SetPutAndProcessAllCommands(put)); |
| EXPECT_EQ(put, GetPut()); |
| Mock::VerifyAndClearExpectations(api_mock()); |
| } |
| |
| // CommandBufferServiceBase implementation: |
| CommandBatchProcessedResult OnCommandBatchProcessed() override { |
| return kContinueExecution; |
| } |
| MOCK_METHOD0(OnParseError, void()); |
| |
| private: |
| std::unique_ptr<TransferBufferManager> transfer_buffer_manager_; |
| std::unique_ptr<CommandBufferService> command_buffer_service_; |
| std::unique_ptr<AsyncAPIMock> api_mock_; |
| scoped_refptr<Buffer> buffer_; |
| Sequence sequence_; |
| }; |
| |
| // Tests initialization conditions. |
| TEST_F(CommandBufferServiceTest, TestInit) { |
| MakeService(10); |
| CommandBuffer::State state = command_buffer_service()->GetState(); |
| EXPECT_EQ(0, GetGet()); |
| EXPECT_EQ(0, GetPut()); |
| EXPECT_EQ(0, state.token); |
| EXPECT_EQ(error::kNoError, state.error); |
| } |
| |
| TEST_F(CommandBufferServiceTest, TestEmpty) { |
| MakeService(10); |
| EXPECT_CALL(*api_mock(), DoCommands(_, _, _, _)).Times(0); |
| |
| EXPECT_EQ(error::kNoError, SetPutAndProcessAllCommands(0)); |
| EXPECT_EQ(0, GetGet()); |
| } |
| |
| // Tests simple commands. |
| TEST_F(CommandBufferServiceTest, TestSimple) { |
| MakeService(10); |
| CommandBufferOffset put = GetPut(); |
| CommandHeader header; |
| |
| // add a single command, no args |
| header.size = 1; |
| header.command = 123; |
| buffer()[put++].value_header = header; |
| |
| AddDoCommandsExpect(error::kNoError, 1, 1); |
| EXPECT_EQ(error::kNoError, SetPutAndProcessAllCommands(put)); |
| EXPECT_EQ(put, GetGet()); |
| Mock::VerifyAndClearExpectations(api_mock()); |
| |
| // add a single command, 2 args |
| header.size = 3; |
| header.command = 456; |
| buffer()[put++].value_header = header; |
| buffer()[put++].value_int32 = 2134; |
| buffer()[put++].value_float = 1.f; |
| |
| AddDoCommandsExpect(error::kNoError, 3, 3); |
| EXPECT_EQ(error::kNoError, SetPutAndProcessAllCommands(put)); |
| EXPECT_EQ(put, GetGet()); |
| Mock::VerifyAndClearExpectations(api_mock()); |
| } |
| |
| // Tests having multiple commands in the buffer. |
| TEST_F(CommandBufferServiceTest, TestMultipleCommands) { |
| MakeService(10); |
| CommandBufferOffset put = GetPut(); |
| CommandHeader header; |
| |
| // add 2 commands, test with single ProcessAllCommands() |
| header.size = 2; |
| header.command = 789; |
| buffer()[put++].value_header = header; |
| buffer()[put++].value_int32 = 5151; |
| |
| header.size = 2; |
| header.command = 876; |
| buffer()[put++].value_header = header; |
| buffer()[put++].value_int32 = 3434; |
| |
| // Process commands. 4 entries remaining. |
| AddDoCommandsExpect(error::kNoError, 4, 4); |
| EXPECT_EQ(error::kNoError, SetPutAndProcessAllCommands(put)); |
| EXPECT_EQ(put, GetGet()); |
| Mock::VerifyAndClearExpectations(api_mock()); |
| |
| // add 2 commands again, test with ProcessAllCommands() |
| header.size = 2; |
| header.command = 123; |
| buffer()[put++].value_header = header; |
| buffer()[put++].value_int32 = 5656; |
| |
| header.size = 2; |
| header.command = 321; |
| buffer()[put++].value_header = header; |
| buffer()[put++].value_int32 = 7878; |
| |
| // 4 entries remaining. |
| AddDoCommandsExpect(error::kNoError, 4, 4); |
| EXPECT_EQ(error::kNoError, SetPutAndProcessAllCommands(put)); |
| EXPECT_EQ(put, GetGet()); |
| Mock::VerifyAndClearExpectations(api_mock()); |
| } |
| |
| // Tests that the parser will wrap correctly at the end of the buffer. |
| TEST_F(CommandBufferServiceTest, TestWrap) { |
| MakeService(5); |
| CommandBufferOffset put = GetPut(); |
| CommandHeader header; |
| |
| // add 3 commands with no args (1 word each) |
| for (unsigned int i = 0; i < 3; ++i) { |
| header.size = 1; |
| header.command = i; |
| buffer()[put++].value_header = header; |
| } |
| |
| // Process up to 10 commands. 3 entries remaining to put. |
| AddDoCommandsExpect(error::kNoError, 3, 3); |
| EXPECT_EQ(error::kNoError, SetPutAndProcessAllCommands(put)); |
| EXPECT_EQ(put, GetGet()); |
| Mock::VerifyAndClearExpectations(api_mock()); |
| |
| // add 1 command with 1 arg (2 words). That should put us at the end of the |
| // buffer. |
| header.size = 2; |
| header.command = 3; |
| buffer()[put++].value_header = header; |
| buffer()[put++].value_int32 = 5; |
| |
| DCHECK_EQ(5, put); |
| put = 0; |
| |
| // add 1 command with 1 arg (2 words). |
| header.size = 2; |
| header.command = 4; |
| buffer()[put++].value_header = header; |
| buffer()[put++].value_int32 = 6; |
| |
| // 2 entries remaining to end of buffer. |
| AddDoCommandsExpect(error::kNoError, 2, 2); |
| // 2 entries remaining to put. |
| AddDoCommandsExpect(error::kNoError, 2, 2); |
| EXPECT_EQ(error::kNoError, SetPutAndProcessAllCommands(put)); |
| EXPECT_EQ(put, GetGet()); |
| Mock::VerifyAndClearExpectations(api_mock()); |
| } |
| |
| // Tests error conditions. |
| TEST_F(CommandBufferServiceTest, TestError) { |
| const unsigned int kNumEntries = 5; |
| MakeService(kNumEntries); |
| CommandBufferOffset put = GetPut(); |
| CommandHeader header; |
| |
| // Generate a command with size 0. |
| header.size = 0; |
| header.command = 3; |
| buffer()[put++].value_header = header; |
| |
| AddDoCommandsExpect(error::kInvalidSize, 1, 0); |
| EXPECT_CALL(*this, OnParseError()).Times(1); |
| EXPECT_EQ(error::kInvalidSize, SetPutAndProcessAllCommands(put)); |
| // check that no DoCommand call was made. |
| Mock::VerifyAndClearExpectations(api_mock()); |
| Mock::VerifyAndClearExpectations(this); |
| |
| MakeService(5); |
| put = GetPut(); |
| |
| // Generate a command with size 6, extends beyond the end of the buffer. |
| header.size = 6; |
| header.command = 3; |
| buffer()[put++].value_header = header; |
| |
| AddDoCommandsExpect(error::kOutOfBounds, 1, 0); |
| EXPECT_CALL(*this, OnParseError()).Times(1); |
| EXPECT_EQ(error::kOutOfBounds, SetPutAndProcessAllCommands(put)); |
| // check that no DoCommand call was made. |
| Mock::VerifyAndClearExpectations(api_mock()); |
| Mock::VerifyAndClearExpectations(this); |
| |
| MakeService(5); |
| put = GetPut(); |
| |
| // Generates 2 commands. |
| header.size = 1; |
| header.command = 3; |
| buffer()[put++].value_header = header; |
| CommandBufferOffset put_post_fail = put; |
| header.size = 1; |
| header.command = 4; |
| buffer()[put++].value_header = header; |
| |
| // have the first command fail to parse. |
| AddDoCommandsExpect(error::kUnknownCommand, 2, 1); |
| EXPECT_CALL(*this, OnParseError()).Times(1); |
| EXPECT_EQ(error::kUnknownCommand, SetPutAndProcessAllCommands(put)); |
| // check that only one command was executed, and that get reflects that |
| // correctly. |
| EXPECT_EQ(put_post_fail, GetGet()); |
| Mock::VerifyAndClearExpectations(api_mock()); |
| Mock::VerifyAndClearExpectations(this); |
| // make the second one succeed, and check that the service doesn't try to |
| // recover. |
| EXPECT_CALL(*this, OnParseError()).Times(0); |
| EXPECT_EQ(error::kUnknownCommand, SetPutAndProcessAllCommands(put)); |
| EXPECT_EQ(put_post_fail, GetGet()); |
| Mock::VerifyAndClearExpectations(api_mock()); |
| Mock::VerifyAndClearExpectations(this); |
| |
| // Try to flush out-of-bounds, should fail. |
| MakeService(kNumEntries); |
| AdvancePut(2); |
| EXPECT_EQ(2, GetPut()); |
| |
| EXPECT_CALL(*this, OnParseError()).Times(1); |
| command_buffer_service()->Flush(kNumEntries + 1, api_mock()); |
| CommandBuffer::State state1 = command_buffer_service()->GetState(); |
| EXPECT_EQ(2, GetPut()); |
| EXPECT_EQ(error::kOutOfBounds, state1.error); |
| Mock::VerifyAndClearExpectations(this); |
| |
| MakeService(kNumEntries); |
| AdvancePut(2); |
| EXPECT_EQ(2, GetPut()); |
| |
| EXPECT_CALL(*this, OnParseError()).Times(1); |
| command_buffer_service()->Flush(-1, api_mock()); |
| CommandBuffer::State state2 = command_buffer_service()->GetState(); |
| EXPECT_EQ(2, GetPut()); |
| EXPECT_EQ(error::kOutOfBounds, state2.error); |
| Mock::VerifyAndClearExpectations(this); |
| } |
| |
| TEST_F(CommandBufferServiceTest, SetBuffer) { |
| MakeService(5); |
| AdvancePut(2); |
| // We should have advanced 2 entries. |
| EXPECT_EQ(2, GetGet()); |
| |
| CommandBuffer::State state1 = command_buffer_service()->GetState(); |
| int32_t id = SetNewGetBuffer(5 * sizeof(CommandBufferEntry)); |
| CommandBuffer::State state2 = command_buffer_service()->GetState(); |
| // The put and get should have reset to 0. |
| EXPECT_EQ(0, GetGet()); |
| EXPECT_EQ(0, GetPut()); |
| EXPECT_EQ(error::kNoError, state2.error); |
| EXPECT_EQ(state1.token, state2.token); |
| EXPECT_EQ(state1.set_get_buffer_count + 1, state2.set_get_buffer_count); |
| |
| AdvancePut(2); |
| // We should have advanced 2 entries. |
| EXPECT_EQ(2, GetGet()); |
| |
| // Destroy current get buffer, should not reset. |
| command_buffer_service()->DestroyTransferBuffer(id); |
| CommandBuffer::State state3 = command_buffer_service()->GetState(); |
| EXPECT_EQ(2, GetGet()); |
| EXPECT_EQ(2, GetPut()); |
| EXPECT_EQ(error::kNoError, state3.error); |
| // Should not update the set_get_buffer_count either. |
| EXPECT_EQ(state2.set_get_buffer_count, state3.set_get_buffer_count); |
| |
| AdvancePut(2); |
| // We should have advanced 2 entries. |
| EXPECT_EQ(4, GetGet()); |
| |
| // Reseting the get buffer should reset get and put |
| command_buffer_service()->SetGetBuffer(-1); |
| CommandBuffer::State state4 = command_buffer_service()->GetState(); |
| EXPECT_EQ(0, GetGet()); |
| EXPECT_EQ(0, GetPut()); |
| EXPECT_EQ(error::kNoError, state4.error); |
| // Should not update the set_get_buffer_count either. |
| EXPECT_EQ(state3.set_get_buffer_count + 1, state4.set_get_buffer_count); |
| |
| // Trying to execute commands should now fail. |
| EXPECT_CALL(*this, OnParseError()).Times(1); |
| command_buffer_service()->Flush(2, api_mock()); |
| CommandBuffer::State state5 = command_buffer_service()->GetState(); |
| EXPECT_EQ(0, GetPut()); |
| EXPECT_EQ(error::kOutOfBounds, state5.error); |
| Mock::VerifyAndClearExpectations(this); |
| } |
| |
| TEST_F(CommandBufferServiceTest, InvalidSetBuffer) { |
| MakeService(3); |
| CommandBuffer::State state1 = command_buffer_service()->GetState(); |
| |
| // Set an invalid transfer buffer, should succeed. |
| command_buffer_service()->SetGetBuffer(-1); |
| CommandBuffer::State state2 = command_buffer_service()->GetState(); |
| EXPECT_EQ(0, GetGet()); |
| EXPECT_EQ(0, GetPut()); |
| EXPECT_EQ(error::kNoError, state2.error); |
| EXPECT_EQ(state1.token, state2.token); |
| EXPECT_EQ(state1.set_get_buffer_count + 1, state2.set_get_buffer_count); |
| |
| // Trying to execute commands should fail however. |
| EXPECT_CALL(*this, OnParseError()).Times(1); |
| command_buffer_service()->Flush(2, api_mock()); |
| CommandBuffer::State state3 = command_buffer_service()->GetState(); |
| EXPECT_EQ(0, GetPut()); |
| EXPECT_EQ(error::kOutOfBounds, state3.error); |
| Mock::VerifyAndClearExpectations(this); |
| } |
| |
| TEST_F(CommandBufferServiceTest, Token) { |
| MakeService(3); |
| command_buffer_service()->SetToken(7); |
| EXPECT_EQ(7, command_buffer_service()->GetState().token); |
| } |
| |
| TEST_F(CommandBufferServiceTest, CanSetParseError) { |
| MakeService(3); |
| |
| EXPECT_CALL(*this, OnParseError()).Times(1); |
| command_buffer_service()->SetParseError(error::kInvalidSize); |
| EXPECT_EQ(error::kInvalidSize, command_buffer_service()->GetState().error); |
| Mock::VerifyAndClearExpectations(this); |
| } |
| |
| class CommandBufferServicePauseExecutionTest : public CommandBufferServiceTest { |
| public: |
| // Will pause the command buffer execution after 2 runs. |
| error::Error DoCommands(unsigned int num_commands, |
| const volatile void* buffer, |
| int num_entries, |
| int* entries_processed) { |
| *entries_processed = 1; |
| return error::kNoError; |
| } |
| |
| CommandBatchProcessedResult OnCommandBatchProcessed() override { |
| ++calls_; |
| if (calls_ == 2) |
| pause_ = true; |
| return pause_ ? kPauseExecution : kContinueExecution; |
| } |
| |
| protected: |
| int calls_ = 0; |
| bool pause_ = false; |
| }; |
| |
| TEST_F(CommandBufferServicePauseExecutionTest, CommandsProcessed) { |
| MakeService(3); |
| AddDoCommandsExpect(error::kNoError, 2, 1); |
| AddDoCommandsExpect(error::kNoError, 1, 1); |
| EXPECT_EQ(error::kNoError, SetPutAndProcessAllCommands(2)); |
| EXPECT_EQ(2, calls_); |
| } |
| |
| TEST_F(CommandBufferServicePauseExecutionTest, PauseExecution) { |
| MakeService(5); |
| // Command buffer processing should stop after 2 commands. |
| AddDoCommandsExpect(error::kNoError, 4, 1); |
| AddDoCommandsExpect(error::kNoError, 3, 1); |
| EXPECT_EQ(error::kNoError, SetPutAndProcessAllCommands(4)); |
| EXPECT_EQ(2, GetGet()); |
| EXPECT_EQ(4, GetPut()); |
| EXPECT_EQ(2, calls_); |
| EXPECT_TRUE(pause_); |
| |
| // Processing should continue after resume. |
| pause_ = false; |
| AddDoCommandsExpect(error::kNoError, 2, 1); |
| AddDoCommandsExpect(error::kNoError, 1, 1); |
| EXPECT_EQ(error::kNoError, SetPutAndProcessAllCommands(4)); |
| EXPECT_EQ(4, GetGet()); |
| EXPECT_EQ(4, GetPut()); |
| EXPECT_EQ(4, calls_); |
| EXPECT_FALSE(pause_); |
| } |
| |
| class CommandBufferServiceUnscheduleExecutionTest |
| : public CommandBufferServiceTest { |
| public: |
| enum { kUnscheduleAfterCalls = 2 }; |
| |
| // Will unschedule the command buffer execution after 2 runs. |
| error::Error DoCommands(unsigned int num_commands, |
| const volatile void* buffer, |
| int num_entries, |
| int* entries_processed) { |
| ++calls_; |
| if (calls_ == kUnscheduleAfterCalls) { |
| command_buffer_service()->SetScheduled(false); |
| *entries_processed = 0; |
| return error::kDeferCommandUntilLater; |
| } |
| *entries_processed = 1; |
| return error::kNoError; |
| } |
| |
| protected: |
| int calls_ = 0; |
| }; |
| |
| TEST_F(CommandBufferServiceUnscheduleExecutionTest, Unschedule) { |
| MakeService(5); |
| EXPECT_CALL(*api_mock(), DoCommands(_, _, _, _)) |
| .WillRepeatedly(Invoke( |
| this, &CommandBufferServiceUnscheduleExecutionTest::DoCommands)); |
| // Command buffer processing should stop after 2 commands. |
| EXPECT_EQ(error::kNoError, SetPutAndProcessAllCommands(4)); |
| EXPECT_EQ(1, GetGet()); |
| EXPECT_EQ(4, GetPut()); |
| EXPECT_EQ(kUnscheduleAfterCalls, calls_); |
| EXPECT_FALSE(command_buffer_service()->scheduled()); |
| |
| // Processing should continue after rescheduling. |
| command_buffer_service()->SetScheduled(true); |
| EXPECT_EQ(error::kNoError, SetPutAndProcessAllCommands(4)); |
| EXPECT_EQ(4, GetGet()); |
| EXPECT_EQ(4, GetPut()); |
| EXPECT_EQ(5, calls_); |
| EXPECT_TRUE(command_buffer_service()->scheduled()); |
| } |
| |
| } // namespace gpu |