| // Copyright 2018 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| // Tests for RasterImplementation. |
| |
| #include "gpu/command_buffer/client/raster_implementation.h" |
| |
| #include <GLES2/gl2.h> |
| #include <GLES2/gl2extchromium.h> |
| #include <GLES3/gl32.h> |
| #include <stddef.h> |
| #include <stdint.h> |
| |
| #include <algorithm> |
| #include <array> |
| #include <memory> |
| |
| #include "base/bits.h" |
| #include "base/compiler_specific.h" |
| #include "base/containers/heap_array.h" |
| #include "base/containers/span.h" |
| #include "base/functional/bind.h" |
| #include "base/memory/ptr_util.h" |
| #include "base/memory/raw_ptr.h" |
| #include "cc/paint/raw_memory_transfer_cache_entry.h" |
| #include "cc/paint/transfer_cache_serialize_helper.h" |
| #include "gpu/command_buffer/client/client_test_helper.h" |
| #include "gpu/command_buffer/client/mock_transfer_buffer.h" |
| #include "gpu/command_buffer/client/query_tracker.h" |
| #include "gpu/command_buffer/client/raster_cmd_helper.h" |
| #include "gpu/command_buffer/client/ring_buffer.h" |
| #include "gpu/command_buffer/client/shared_memory_limits.h" |
| #include "gpu/command_buffer/client/transfer_buffer.h" |
| #include "gpu/command_buffer/common/command_buffer.h" |
| #include "gpu/command_buffer/common/sync_token.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| using gpu::gles2::QueryTracker; |
| using testing::_; |
| using testing::AtLeast; |
| using testing::AnyNumber; |
| using testing::DoAll; |
| using testing::InSequence; |
| using testing::Mock; |
| using testing::Sequence; |
| using testing::StrictMock; |
| using testing::Return; |
| using testing::ReturnRef; |
| |
| namespace gpu { |
| namespace raster { |
| |
| ACTION_P2(SetMemory, dst, obj) { |
| UNSAFE_TODO(memcpy(dst, &obj, sizeof(obj))); |
| } |
| |
| ACTION_P3(SetMemoryFromArray, dst, array, size) { |
| UNSAFE_TODO(memcpy(dst, array, size)); |
| } |
| |
| // Used to help set the transfer buffer result to SizedResult of a single value. |
| template <typename T> |
| class SizedResultHelper { |
| public: |
| explicit SizedResultHelper(T result) : size_(sizeof(result)) { |
| memcpy(result_, &result, sizeof(T)); |
| } |
| |
| private: |
| uint32_t size_; |
| char result_[sizeof(T)]; |
| }; |
| |
| class RasterImplementationTest : public testing::Test { |
| protected: |
| static const uint8_t kInitialValue = 0xBD; |
| static const uint32_t kNumCommandEntries = 500; |
| static const uint32_t kCommandBufferSizeBytes = |
| kNumCommandEntries * sizeof(CommandBufferEntry); |
| static const uint32_t kTransferBufferSize = 512; |
| |
| static const GLint kMaxTextureSize = 128; |
| static const GLuint kStartId = 1024; |
| static const GLuint kBuffersStartId = 1; |
| static const GLuint kTexturesStartId = 1; |
| static const GLuint kQueriesStartId = 1; |
| |
| typedef MockTransferBuffer::ExpectedMemoryInfo ExpectedMemoryInfo; |
| |
| class TestContext { |
| public: |
| TestContext() : commands_(nullptr), token_(0) {} |
| |
| bool Initialize(bool lose_context_when_out_of_memory, |
| bool transfer_buffer_initialize_fail, |
| bool sync_query) { |
| SharedMemoryLimits limits = SharedMemoryLimitsForTesting(); |
| command_buffer_ = std::make_unique<StrictMock<MockClientCommandBuffer>>(); |
| |
| transfer_buffer_ = base::WrapUnique(new MockTransferBuffer( |
| command_buffer_.get(), kTransferBufferSize, |
| RasterImplementation::kStartingOffset, |
| RasterImplementation::kAlignment, transfer_buffer_initialize_fail)); |
| |
| helper_ = std::make_unique<RasterCmdHelper>(command_buffer()); |
| helper_->Initialize(limits.command_buffer_size); |
| |
| gpu_control_ = std::make_unique<StrictMock<MockClientGpuControl>>(); |
| capabilities_.max_texture_size = kMaxTextureSize; |
| capabilities_.sync_query = sync_query; |
| EXPECT_CALL(*gpu_control_, GetCapabilities()) |
| .WillOnce(ReturnRef(capabilities_)); |
| |
| { |
| InSequence sequence; |
| |
| gl_ = std::make_unique<RasterImplementation>( |
| helper_.get(), transfer_buffer_.get(), |
| lose_context_when_out_of_memory, gpu_control_.get()); |
| } |
| |
| // The client should be set to something non-null. |
| EXPECT_CALL(*gpu_control_, SetGpuControlClient(gl_.get())).Times(1); |
| |
| if (gl_->Initialize(limits) != gpu::ContextResult::kSuccess) |
| return false; |
| |
| helper_->CommandBufferHelper::Finish(); |
| Mock::VerifyAndClearExpectations(gl_.get()); |
| |
| scoped_refptr<Buffer> ring_buffer = helper_->get_ring_buffer(); |
| commands_ = |
| UNSAFE_TODO(static_cast<CommandBufferEntry*>(ring_buffer->memory()) + |
| command_buffer()->GetServicePutOffset()); |
| ClearCommands(); |
| EXPECT_TRUE(transfer_buffer_->InSync()); |
| |
| Mock::VerifyAndClearExpectations(command_buffer()); |
| return true; |
| } |
| |
| void TearDown() { |
| Mock::VerifyAndClear(gl_.get()); |
| EXPECT_CALL(*command_buffer(), OnFlush()).Times(AnyNumber()); |
| // For command buffer. |
| EXPECT_CALL(*command_buffer(), DestroyTransferBuffer(_)) |
| .Times(AtLeast(1)); |
| // The client should be unset. |
| EXPECT_CALL(*gpu_control_, SetGpuControlClient(nullptr)).Times(1); |
| EXPECT_CALL(*gpu_control_, CancelAllQueries()).Times(1); |
| gl_.reset(); |
| } |
| |
| MockClientCommandBuffer* command_buffer() const { |
| return command_buffer_.get(); |
| } |
| |
| int GetNextToken() { return ++token_; } |
| |
| void ClearCommands() { |
| scoped_refptr<Buffer> ring_buffer = helper_->get_ring_buffer(); |
| UNSAFE_TODO( |
| memset(ring_buffer->memory(), kInitialValue, ring_buffer->size())); |
| } |
| |
| std::unique_ptr<MockClientCommandBuffer> command_buffer_; |
| std::unique_ptr<MockClientGpuControl> gpu_control_; |
| std::unique_ptr<RasterCmdHelper> helper_; |
| std::unique_ptr<MockTransferBuffer> transfer_buffer_; |
| std::unique_ptr<RasterImplementation> gl_; |
| raw_ptr<CommandBufferEntry> commands_; |
| int token_; |
| Capabilities capabilities_; |
| }; |
| |
| RasterImplementationTest() : commands_(nullptr) {} |
| |
| void SetUp() override; |
| void TearDown() override; |
| |
| bool NoCommandsWritten() { |
| scoped_refptr<Buffer> ring_buffer = helper_->get_ring_buffer(); |
| const uint8_t* cmds = static_cast<const uint8_t*>(ring_buffer->memory()); |
| const uint8_t* end = UNSAFE_TODO(cmds + ring_buffer->size()); |
| for (; cmds < end; UNSAFE_TODO(++cmds)) { |
| if (*cmds != kInitialValue) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| QueryTracker::Query* GetQuery(GLuint id) { |
| return gl_->query_tracker_->GetQuery(id); |
| } |
| |
| QueryTracker* GetQueryTracker() { return gl_->query_tracker_.get(); } |
| |
| struct ContextInitOptions { |
| bool lose_context_when_out_of_memory = false; |
| bool transfer_buffer_initialize_fail = false; |
| bool sync_query = true; |
| }; |
| |
| bool Initialize(const ContextInitOptions& init_options) { |
| bool success = true; |
| if (!test_context_.Initialize(init_options.lose_context_when_out_of_memory, |
| init_options.transfer_buffer_initialize_fail, |
| init_options.sync_query)) { |
| success = false; |
| } |
| |
| // Default to test context 0. |
| gpu_control_ = test_context_.gpu_control_.get(); |
| helper_ = test_context_.helper_.get(); |
| transfer_buffer_ = test_context_.transfer_buffer_.get(); |
| gl_ = test_context_.gl_.get(); |
| commands_ = test_context_.commands_; |
| return success; |
| } |
| |
| MockClientCommandBuffer* command_buffer() const { |
| return test_context_.command_buffer_.get(); |
| } |
| |
| int GetNextToken() { return test_context_.GetNextToken(); } |
| |
| const void* GetPut() { return helper_->GetSpace(0); } |
| |
| void ClearCommands() { |
| scoped_refptr<Buffer> ring_buffer = helper_->get_ring_buffer(); |
| UNSAFE_TODO( |
| memset(ring_buffer->memory(), kInitialValue, ring_buffer->size())); |
| } |
| |
| uint32_t MaxTransferBufferSize() { |
| return transfer_buffer_->MaxTransferBufferSize(); |
| } |
| |
| void SetMappedMemoryLimit(size_t limit) { |
| gl_->mapped_memory_->set_max_allocated_bytes(limit); |
| } |
| |
| ExpectedMemoryInfo GetExpectedMemory(uint32_t size) { |
| return transfer_buffer_->GetExpectedMemory(size); |
| } |
| |
| ExpectedMemoryInfo GetExpectedResultMemory(uint32_t size) { |
| return transfer_buffer_->GetExpectedResultMemory(size); |
| } |
| |
| ExpectedMemoryInfo GetExpectedMappedMemory(uint32_t size) { |
| ExpectedMemoryInfo mem; |
| |
| // Temporarily allocate memory and expect that memory block to be reused. |
| mem.span = gl_->mapped_memory_->Alloc(size, &mem.id, &mem.offset); |
| mem.ptr = mem.span.data(); |
| gl_->mapped_memory_->Free(mem.ptr); |
| |
| return mem; |
| } |
| |
| int CheckError() { |
| ExpectedMemoryInfo result = |
| GetExpectedResultMemory(sizeof(cmds::GetError::Result)); |
| EXPECT_CALL(*command_buffer(), OnFlush()) |
| .WillOnce(SetMemory(result.ptr, GLuint(GL_NO_ERROR))) |
| .RetiresOnSaturation(); |
| return gl_->GetError(); |
| } |
| |
| const std::string& GetLastError() { return gl_->GetLastError(); } |
| |
| bool GetBucketContents(uint32_t bucket_id, std::vector<int8_t>* data) { |
| return gl_->GetBucketContents(bucket_id, data); |
| } |
| |
| bool ReadbackImagePixelsINTERNAL(const gpu::Mailbox& source_mailbox, |
| const SkImageInfo& dst_info, |
| GLuint dst_row_bytes, |
| int src_x, |
| int src_y, |
| int plane_index, |
| base::OnceCallback<void(bool)> readback_done, |
| void* dst_pixels) { |
| return gl_->ReadbackImagePixelsINTERNAL( |
| source_mailbox, dst_info, dst_row_bytes, src_x, src_y, plane_index, |
| std::move(readback_done), dst_pixels); |
| } |
| |
| static SharedMemoryLimits SharedMemoryLimitsForTesting() { |
| SharedMemoryLimits limits; |
| limits.command_buffer_size = kCommandBufferSizeBytes; |
| limits.start_transfer_buffer_size = kTransferBufferSize; |
| limits.min_transfer_buffer_size = kTransferBufferSize; |
| limits.max_transfer_buffer_size = kTransferBufferSize; |
| limits.mapped_memory_reclaim_limit = SharedMemoryLimits::kNoLimit; |
| return limits; |
| } |
| |
| TestContext test_context_; |
| |
| raw_ptr<MockClientGpuControl> gpu_control_; |
| raw_ptr<RasterCmdHelper> helper_; |
| raw_ptr<MockTransferBuffer> transfer_buffer_; |
| raw_ptr<RasterImplementation> gl_; |
| raw_ptr<CommandBufferEntry> commands_; |
| }; |
| |
| void RasterImplementationTest::SetUp() { |
| ContextInitOptions init_options; |
| ASSERT_TRUE(Initialize(init_options)); |
| } |
| |
| void RasterImplementationTest::TearDown() { |
| gl_ = nullptr; |
| test_context_.TearDown(); |
| } |
| |
| class RasterImplementationManualInitTest : public RasterImplementationTest { |
| protected: |
| void SetUp() override {} |
| }; |
| |
| const uint8_t RasterImplementationTest::kInitialValue; |
| const uint32_t RasterImplementationTest::kNumCommandEntries; |
| const uint32_t RasterImplementationTest::kCommandBufferSizeBytes; |
| const uint32_t RasterImplementationTest::kTransferBufferSize; |
| const GLint RasterImplementationTest::kMaxTextureSize; |
| const GLuint RasterImplementationTest::kStartId; |
| const GLuint RasterImplementationTest::kBuffersStartId; |
| const GLuint RasterImplementationTest::kTexturesStartId; |
| const GLuint RasterImplementationTest::kQueriesStartId; |
| |
| TEST_F(RasterImplementationTest, GetBucketContents) { |
| const uint32_t kBucketId = RasterImplementation::kResultBucketId; |
| const uint32_t kTestSize = MaxTransferBufferSize() + 32; |
| |
| auto buf = base::HeapArray<uint8_t>::Uninit(kTestSize); |
| for (uint32_t ii = 0; ii < kTestSize; ++ii) { |
| buf[ii] = ii * 3; |
| } |
| |
| struct Cmds { |
| cmd::GetBucketStart get_bucket_start; |
| cmd::SetToken set_token1; |
| cmd::GetBucketData get_bucket_data; |
| cmd::SetToken set_token2; |
| cmd::SetBucketSize set_bucket_size2; |
| }; |
| |
| ExpectedMemoryInfo mem1 = GetExpectedMemory(MaxTransferBufferSize()); |
| ExpectedMemoryInfo result1 = GetExpectedResultMemory(sizeof(uint32_t)); |
| ExpectedMemoryInfo mem2 = |
| GetExpectedMemory(kTestSize - MaxTransferBufferSize()); |
| |
| Cmds expected; |
| expected.get_bucket_start.Init(kBucketId, result1.id, result1.offset, |
| MaxTransferBufferSize(), mem1.id, mem1.offset); |
| expected.set_token1.Init(GetNextToken()); |
| expected.get_bucket_data.Init(kBucketId, MaxTransferBufferSize(), |
| kTestSize - MaxTransferBufferSize(), mem2.id, |
| mem2.offset); |
| expected.set_bucket_size2.Init(kBucketId, 0); |
| expected.set_token2.Init(GetNextToken()); |
| |
| EXPECT_CALL(*command_buffer(), OnFlush()) |
| .WillOnce(DoAll( |
| SetMemory(result1.ptr, kTestSize), |
| SetMemoryFromArray(mem1.ptr, buf.data(), MaxTransferBufferSize()))) |
| .WillOnce(SetMemoryFromArray( |
| mem2.ptr, UNSAFE_TODO(buf.data() + MaxTransferBufferSize()), |
| kTestSize - MaxTransferBufferSize())) |
| .RetiresOnSaturation(); |
| |
| std::vector<int8_t> data; |
| GetBucketContents(kBucketId, &data); |
| UNSAFE_TODO(EXPECT_EQ(0, memcmp(&expected, commands_, sizeof(expected)))); |
| ASSERT_EQ(kTestSize, data.size()); |
| UNSAFE_TODO(EXPECT_EQ(0, memcmp(buf.data(), &data[0], data.size()))); |
| } |
| |
| TEST_F(RasterImplementationTest, BeginEndQueryEXT) { |
| // GL_COMMANDS_COMPLETED_CHROMIUM, |
| // GL_CURRENT_QUERY_EXT |
| |
| std::array<GLuint, 2> expected_ids = { |
| 1, 2}; // These must match what's actually genned. |
| struct GenCmds { |
| cmds::GenQueriesEXTImmediate gen; |
| GLuint data[2]; |
| }; |
| GenCmds expected_gen_cmds; |
| expected_gen_cmds.gen.Init(std::size(expected_ids), &expected_ids[0]); |
| std::array<GLuint, std::size(expected_ids)> ids = {}; |
| gl_->GenQueriesEXT(std::size(expected_ids), &ids[0]); |
| UNSAFE_TODO(EXPECT_EQ( |
| 0, memcmp(&expected_gen_cmds, commands_, sizeof(expected_gen_cmds)))); |
| GLuint id1 = ids[0]; |
| GLuint id2 = ids[1]; |
| ClearCommands(); |
| |
| // Test BeginQueryEXT fails if id = 0. |
| gl_->BeginQueryEXT(GL_COMMANDS_COMPLETED_CHROMIUM, 0); |
| EXPECT_TRUE(NoCommandsWritten()); |
| EXPECT_EQ(GL_INVALID_OPERATION, CheckError()); |
| |
| // Test BeginQueryEXT inserts command. |
| struct BeginCmds { |
| cmds::BeginQueryEXT begin_query; |
| }; |
| BeginCmds expected_begin_cmds; |
| const void* commands = GetPut(); |
| gl_->BeginQueryEXT(GL_COMMANDS_COMPLETED_CHROMIUM, id1); |
| QueryTracker::Query* query = GetQuery(id1); |
| ASSERT_TRUE(query != nullptr); |
| expected_begin_cmds.begin_query.Init(GL_COMMANDS_COMPLETED_CHROMIUM, id1, |
| query->shm_id(), query->shm_offset()); |
| UNSAFE_TODO(EXPECT_EQ( |
| 0, memcmp(&expected_begin_cmds, commands, sizeof(expected_begin_cmds)))); |
| ClearCommands(); |
| |
| // Test BeginQueryEXT fails if between Begin/End. |
| gl_->BeginQueryEXT(GL_COMMANDS_COMPLETED_CHROMIUM, id2); |
| EXPECT_TRUE(NoCommandsWritten()); |
| EXPECT_EQ(GL_INVALID_OPERATION, CheckError()); |
| |
| // Test EndQueryEXT sends command |
| struct EndCmds { |
| cmds::EndQueryEXT end_query; |
| }; |
| commands = GetPut(); |
| gl_->EndQueryEXT(GL_COMMANDS_COMPLETED_CHROMIUM); |
| EndCmds expected_end_cmds; |
| expected_end_cmds.end_query.Init(GL_COMMANDS_COMPLETED_CHROMIUM, |
| query->submit_count()); |
| UNSAFE_TODO(EXPECT_EQ( |
| 0, memcmp(&expected_end_cmds, commands, sizeof(expected_end_cmds)))); |
| |
| // Test EndQueryEXT fails if no current query. |
| ClearCommands(); |
| gl_->EndQueryEXT(GL_COMMANDS_COMPLETED_CHROMIUM); |
| EXPECT_TRUE(NoCommandsWritten()); |
| EXPECT_EQ(GL_INVALID_OPERATION, CheckError()); |
| |
| // Test 2nd Begin/End increments count. |
| base::subtle::Atomic32 old_submit_count = query->submit_count(); |
| gl_->BeginQueryEXT(GL_COMMANDS_COMPLETED_CHROMIUM, id1); |
| EXPECT_EQ(old_submit_count, query->submit_count()); |
| commands = GetPut(); |
| gl_->EndQueryEXT(GL_COMMANDS_COMPLETED_CHROMIUM); |
| EXPECT_NE(old_submit_count, query->submit_count()); |
| expected_end_cmds.end_query.Init(GL_COMMANDS_COMPLETED_CHROMIUM, |
| query->submit_count()); |
| UNSAFE_TODO(EXPECT_EQ( |
| 0, memcmp(&expected_end_cmds, commands, sizeof(expected_end_cmds)))); |
| |
| // Test GetQueryObjectuivEXT fails if unused id |
| GLuint available = 0xBDu; |
| ClearCommands(); |
| gl_->GetQueryObjectuivEXT(id2, GL_QUERY_RESULT_AVAILABLE_EXT, &available); |
| EXPECT_TRUE(NoCommandsWritten()); |
| EXPECT_EQ(0xBDu, available); |
| EXPECT_EQ(GL_INVALID_OPERATION, CheckError()); |
| |
| // Test GetQueryObjectuivEXT fails if bad id |
| ClearCommands(); |
| gl_->GetQueryObjectuivEXT(4567, GL_QUERY_RESULT_AVAILABLE_EXT, &available); |
| EXPECT_TRUE(NoCommandsWritten()); |
| EXPECT_EQ(0xBDu, available); |
| EXPECT_EQ(GL_INVALID_OPERATION, CheckError()); |
| |
| // Test GetQueryObjectuivEXT CheckResultsAvailable |
| ClearCommands(); |
| gl_->GetQueryObjectuivEXT(id1, GL_QUERY_RESULT_AVAILABLE_EXT, &available); |
| EXPECT_EQ(0u, available); |
| available = 1u; |
| gl_->GetQueryObjectuivEXT( |
| id1, GL_QUERY_RESULT_AVAILABLE_NO_FLUSH_CHROMIUM_EXT, &available); |
| EXPECT_EQ(0u, available); |
| } |
| |
| TEST_F(RasterImplementationManualInitTest, BadQueryTargets) { |
| ContextInitOptions init_options; |
| init_options.sync_query = false; |
| ASSERT_TRUE(Initialize(init_options)); |
| |
| GLuint id = 0; |
| gl_->GenQueriesEXT(1, &id); |
| ClearCommands(); |
| |
| gl_->BeginQueryEXT(GL_COMMANDS_COMPLETED_CHROMIUM, id); |
| EXPECT_EQ(GL_INVALID_OPERATION, CheckError()); |
| EXPECT_EQ(nullptr, GetQuery(id)); |
| |
| gl_->BeginQueryEXT(0x123, id); |
| EXPECT_EQ(GL_INVALID_ENUM, CheckError()); |
| EXPECT_EQ(nullptr, GetQuery(id)); |
| } |
| |
| TEST_F(RasterImplementationTest, GenUnverifiedSyncTokenCHROMIUM) { |
| const CommandBufferNamespace kNamespaceId = CommandBufferNamespace::GPU_IO; |
| const CommandBufferId kCommandBufferId = |
| CommandBufferId::FromUnsafeValue(234u); |
| const GLuint64 kFenceSync = 123u; |
| SyncToken sync_token; |
| |
| EXPECT_CALL(*gpu_control_, GetNamespaceID()) |
| .WillRepeatedly(Return(kNamespaceId)); |
| EXPECT_CALL(*gpu_control_, GetCommandBufferID()) |
| .WillRepeatedly(Return(kCommandBufferId)); |
| |
| gl_->GenUnverifiedSyncTokenCHROMIUM(nullptr); |
| EXPECT_TRUE(NoCommandsWritten()); |
| EXPECT_EQ(GL_INVALID_VALUE, CheckError()); |
| |
| const void* commands = GetPut(); |
| cmd::InsertFenceSync insert_fence_sync; |
| insert_fence_sync.Init(kFenceSync); |
| |
| EXPECT_CALL(*gpu_control_, GenerateFenceSyncRelease()) |
| .WillOnce(Return(kFenceSync)); |
| gl_->GenUnverifiedSyncTokenCHROMIUM(sync_token.GetData()); |
| UNSAFE_TODO(EXPECT_EQ( |
| 0, memcmp(&insert_fence_sync, commands, sizeof(insert_fence_sync)))); |
| EXPECT_EQ(GL_NO_ERROR, CheckError()); |
| |
| EXPECT_FALSE(sync_token.verified_flush()); |
| EXPECT_EQ(kNamespaceId, sync_token.namespace_id()); |
| EXPECT_EQ(kCommandBufferId, sync_token.command_buffer_id()); |
| EXPECT_EQ(kFenceSync, sync_token.release_count()); |
| } |
| |
| TEST_F(RasterImplementationTest, VerifySyncTokensCHROMIUM) { |
| ExpectedMemoryInfo result = |
| GetExpectedResultMemory(sizeof(cmds::GetError::Result)); |
| EXPECT_CALL(*command_buffer(), OnFlush()) |
| .WillRepeatedly(SetMemory(result.ptr, GLuint(GL_NO_ERROR))) |
| .RetiresOnSaturation(); |
| |
| const CommandBufferNamespace kNamespaceId = CommandBufferNamespace::GPU_IO; |
| const CommandBufferId kCommandBufferId = |
| CommandBufferId::FromUnsafeValue(234u); |
| const GLuint64 kFenceSync = 123u; |
| gpu::SyncToken sync_token; |
| GLbyte* sync_token_datas[] = {sync_token.GetData()}; |
| |
| EXPECT_CALL(*gpu_control_, GetNamespaceID()) |
| .WillRepeatedly(Return(kNamespaceId)); |
| EXPECT_CALL(*gpu_control_, GetCommandBufferID()) |
| .WillRepeatedly(Return(kCommandBufferId)); |
| |
| EXPECT_CALL(*gpu_control_, GenerateFenceSyncRelease()) |
| .WillOnce(Return(kFenceSync)); |
| gl_->GenUnverifiedSyncTokenCHROMIUM(sync_token.GetData()); |
| EXPECT_TRUE(sync_token.HasData()); |
| EXPECT_FALSE(sync_token.verified_flush()); |
| |
| ClearCommands(); |
| EXPECT_CALL(*gpu_control_, CanWaitUnverifiedSyncToken(sync_token)) |
| .WillOnce(Return(false)); |
| gl_->VerifySyncTokensCHROMIUM(sync_token_datas, 1); |
| EXPECT_TRUE(NoCommandsWritten()); |
| EXPECT_EQ(static_cast<GLenum>(GL_INVALID_VALUE), gl_->GetError()); |
| EXPECT_FALSE(sync_token.verified_flush()); |
| |
| ClearCommands(); |
| EXPECT_CALL(*gpu_control_, CanWaitUnverifiedSyncToken(sync_token)) |
| .WillOnce(Return(true)); |
| EXPECT_CALL(*gpu_control_, EnsureWorkVisible()); |
| gl_->VerifySyncTokensCHROMIUM(sync_token_datas, std::size(sync_token_datas)); |
| EXPECT_TRUE(NoCommandsWritten()); |
| EXPECT_EQ(GL_NO_ERROR, CheckError()); |
| |
| EXPECT_EQ(kNamespaceId, sync_token.namespace_id()); |
| EXPECT_EQ(kCommandBufferId, sync_token.command_buffer_id()); |
| EXPECT_EQ(kFenceSync, sync_token.release_count()); |
| EXPECT_TRUE(sync_token.verified_flush()); |
| } |
| |
| TEST_F(RasterImplementationTest, VerifySyncTokensCHROMIUM_Sequence) { |
| // To verify sync tokens, the sync tokens must all be verified after |
| // CanWaitUnverifiedSyncTokens() are called. This test ensures the right |
| // sequence. |
| ExpectedMemoryInfo result = |
| GetExpectedResultMemory(sizeof(cmds::GetError::Result)); |
| EXPECT_CALL(*command_buffer(), OnFlush()) |
| .WillRepeatedly(SetMemory(result.ptr, GLuint(GL_NO_ERROR))) |
| .RetiresOnSaturation(); |
| |
| const CommandBufferNamespace kNamespaceId = CommandBufferNamespace::GPU_IO; |
| const CommandBufferId kCommandBufferId = |
| CommandBufferId::FromUnsafeValue(234u); |
| const GLuint64 kFenceSync1 = 123u; |
| const GLuint64 kFenceSync2 = 234u; |
| gpu::SyncToken sync_token1; |
| gpu::SyncToken sync_token2; |
| GLbyte* sync_token_datas[] = {sync_token1.GetData(), sync_token2.GetData()}; |
| |
| EXPECT_CALL(*gpu_control_, GetNamespaceID()) |
| .WillRepeatedly(Return(kNamespaceId)); |
| EXPECT_CALL(*gpu_control_, GetCommandBufferID()) |
| .WillRepeatedly(Return(kCommandBufferId)); |
| |
| // Generate sync token 1. |
| EXPECT_CALL(*gpu_control_, GenerateFenceSyncRelease()) |
| .WillOnce(Return(kFenceSync1)); |
| gl_->GenUnverifiedSyncTokenCHROMIUM(sync_token1.GetData()); |
| EXPECT_TRUE(sync_token1.HasData()); |
| EXPECT_FALSE(sync_token1.verified_flush()); |
| |
| // Generate sync token 2. |
| EXPECT_CALL(*gpu_control_, GenerateFenceSyncRelease()) |
| .WillOnce(Return(kFenceSync2)); |
| gl_->GenUnverifiedSyncTokenCHROMIUM(sync_token2.GetData()); |
| EXPECT_TRUE(sync_token2.HasData()); |
| EXPECT_FALSE(sync_token2.verified_flush()); |
| |
| // Ensure proper sequence of checking and validating. |
| Sequence sequence; |
| EXPECT_CALL(*gpu_control_, CanWaitUnverifiedSyncToken(sync_token1)) |
| .InSequence(sequence) |
| .WillOnce(Return(true)); |
| EXPECT_CALL(*gpu_control_, CanWaitUnverifiedSyncToken(sync_token2)) |
| .InSequence(sequence) |
| .WillOnce(Return(true)); |
| EXPECT_CALL(*gpu_control_, EnsureWorkVisible()).InSequence(sequence); |
| gl_->VerifySyncTokensCHROMIUM(sync_token_datas, std::size(sync_token_datas)); |
| EXPECT_EQ(GL_NO_ERROR, CheckError()); |
| |
| EXPECT_TRUE(sync_token1.verified_flush()); |
| EXPECT_TRUE(sync_token2.verified_flush()); |
| } |
| |
| TEST_F(RasterImplementationTest, VerifySyncTokensCHROMIUM_EmptySyncToken) { |
| // To verify sync tokens, the sync tokens must all be verified after |
| // CanWaitUnverifiedSyncTokens() are called. This test ensures the right |
| // sequence. |
| ExpectedMemoryInfo result = |
| GetExpectedResultMemory(sizeof(cmds::GetError::Result)); |
| EXPECT_CALL(*command_buffer(), OnFlush()) |
| .WillRepeatedly(SetMemory(result.ptr, GLuint(GL_NO_ERROR))) |
| .RetiresOnSaturation(); |
| |
| gpu::SyncToken sync_token1, sync_token2; |
| GLbyte* sync_token_datas[] = {sync_token1.GetData(), sync_token2.GetData()}; |
| |
| // Ensure proper sequence of checking and validating. |
| EXPECT_CALL(*gpu_control_, CanWaitUnverifiedSyncToken(_)).Times(0); |
| EXPECT_CALL(*gpu_control_, EnsureWorkVisible()).Times(0); |
| gl_->VerifySyncTokensCHROMIUM(sync_token_datas, std::size(sync_token_datas)); |
| EXPECT_TRUE(NoCommandsWritten()); |
| EXPECT_EQ(GL_NO_ERROR, CheckError()); |
| |
| EXPECT_TRUE(sync_token1.verified_flush()); |
| EXPECT_TRUE(sync_token2.verified_flush()); |
| } |
| |
| TEST_F(RasterImplementationTest, WaitSyncTokenCHROMIUM) { |
| const CommandBufferNamespace kNamespaceId = CommandBufferNamespace::GPU_IO; |
| const CommandBufferId kCommandBufferId = |
| CommandBufferId::FromUnsafeValue(234u); |
| const GLuint64 kFenceSync = 456u; |
| |
| gpu::SyncToken sync_token; |
| GLbyte* sync_token_data = sync_token.GetData(); |
| |
| struct Cmds { |
| cmd::InsertFenceSync insert_fence_sync; |
| }; |
| Cmds expected; |
| expected.insert_fence_sync.Init(kFenceSync); |
| |
| EXPECT_CALL(*gpu_control_, GetNamespaceID()).WillOnce(Return(kNamespaceId)); |
| EXPECT_CALL(*gpu_control_, GetCommandBufferID()) |
| .WillOnce(Return(kCommandBufferId)); |
| EXPECT_CALL(*gpu_control_, GenerateFenceSyncRelease()) |
| .WillOnce(Return(kFenceSync)); |
| gl_->GenUnverifiedSyncTokenCHROMIUM(sync_token_data); |
| |
| EXPECT_CALL(*gpu_control_, CanWaitUnverifiedSyncToken(sync_token)) |
| .WillOnce(Return(true)); |
| gpu::SyncToken verified_sync_token = sync_token; |
| verified_sync_token.SetVerifyFlush(); |
| EXPECT_CALL(*gpu_control_, WaitSyncToken(verified_sync_token)); |
| gl_->WaitSyncTokenCHROMIUM(sync_token_data); |
| UNSAFE_TODO(EXPECT_EQ(0, memcmp(&expected, commands_, sizeof(expected)))); |
| } |
| |
| TEST_F(RasterImplementationTest, WaitSyncTokenCHROMIUMErrors) { |
| ExpectedMemoryInfo result = |
| GetExpectedResultMemory(sizeof(cmds::GetError::Result)); |
| EXPECT_CALL(*command_buffer(), OnFlush()) |
| .WillRepeatedly(SetMemory(result.ptr, GLuint(GL_NO_ERROR))) |
| .RetiresOnSaturation(); |
| |
| // Empty sync tokens should be produce no error and be a nop. |
| ClearCommands(); |
| gl_->WaitSyncTokenCHROMIUM(nullptr); |
| EXPECT_TRUE(NoCommandsWritten()); |
| EXPECT_EQ(static_cast<GLenum>(GL_NO_ERROR), gl_->GetError()); |
| |
| // Invalid sync tokens should produce no error and be a nop. |
| ClearCommands(); |
| gpu::SyncToken invalid_sync_token; |
| gl_->WaitSyncTokenCHROMIUM(invalid_sync_token.GetConstData()); |
| EXPECT_TRUE(NoCommandsWritten()); |
| EXPECT_EQ(static_cast<GLenum>(GL_NO_ERROR), gl_->GetError()); |
| |
| // Unverified sync token should produce INVALID_OPERATION. |
| ClearCommands(); |
| gpu::SyncToken unverified_sync_token(CommandBufferNamespace::GPU_IO, |
| gpu::CommandBufferId(), 0); |
| EXPECT_CALL(*gpu_control_, CanWaitUnverifiedSyncToken(unverified_sync_token)) |
| .WillOnce(Return(false)); |
| gl_->WaitSyncTokenCHROMIUM(unverified_sync_token.GetConstData()); |
| EXPECT_TRUE(NoCommandsWritten()); |
| EXPECT_EQ(static_cast<GLenum>(GL_INVALID_VALUE), gl_->GetError()); |
| } |
| |
| static void CountCallback(int* count) { |
| (*count)++; |
| } |
| |
| TEST_F(RasterImplementationTest, SignalSyncToken) { |
| const CommandBufferNamespace kNamespaceId = CommandBufferNamespace::GPU_IO; |
| const CommandBufferId kCommandBufferId = CommandBufferId::FromUnsafeValue(1); |
| const uint64_t kFenceSync = 123u; |
| |
| EXPECT_CALL(*gpu_control_, GetNamespaceID()) |
| .WillRepeatedly(Return(kNamespaceId)); |
| EXPECT_CALL(*gpu_control_, GetCommandBufferID()) |
| .WillRepeatedly(Return(kCommandBufferId)); |
| |
| EXPECT_CALL(*gpu_control_, GenerateFenceSyncRelease()) |
| .WillOnce(Return(kFenceSync)); |
| gpu::SyncToken sync_token; |
| gl_->GenUnverifiedSyncTokenCHROMIUM(sync_token.GetData()); |
| |
| int signaled_count = 0; |
| |
| // Request a signal sync token, which gives a callback to the GpuControl to |
| // run when the sync token is reached. |
| base::OnceClosure signal_closure; |
| EXPECT_CALL(*gpu_control_, DoSignalSyncToken(_, _)) |
| .WillOnce([&signal_closure](const SyncToken& sync_token, |
| base::OnceClosure* callback) { |
| signal_closure = std::move(*callback); |
| }); |
| EXPECT_CALL(*gpu_control_, CanWaitUnverifiedSyncToken(sync_token)) |
| .WillOnce(Return(true)); |
| gl_->SignalSyncToken(sync_token, |
| base::BindOnce(&CountCallback, &signaled_count)); |
| EXPECT_EQ(0, signaled_count); |
| |
| // When GpuControl runs the callback, the original callback we gave to |
| // RasterImplementation is run. |
| std::move(signal_closure).Run(); |
| EXPECT_EQ(1, signaled_count); |
| } |
| |
| TEST_F(RasterImplementationTest, SignalSyncTokenAfterContextLoss) { |
| const CommandBufferNamespace kNamespaceId = CommandBufferNamespace::GPU_IO; |
| const CommandBufferId kCommandBufferId = CommandBufferId::FromUnsafeValue(1); |
| const uint64_t kFenceSync = 123u; |
| |
| EXPECT_CALL(*gpu_control_, GetNamespaceID()).WillOnce(Return(kNamespaceId)); |
| EXPECT_CALL(*gpu_control_, GetCommandBufferID()) |
| .WillOnce(Return(kCommandBufferId)); |
| EXPECT_CALL(*gpu_control_, GenerateFenceSyncRelease()) |
| .WillOnce(Return(kFenceSync)); |
| gpu::SyncToken sync_token; |
| gl_->GenUnverifiedSyncTokenCHROMIUM(sync_token.GetData()); |
| |
| int signaled_count = 0; |
| |
| // Request a signal sync token, which gives a callback to the GpuControl to |
| // run when the sync token is reached. |
| base::OnceClosure signal_closure; |
| EXPECT_CALL(*gpu_control_, DoSignalSyncToken(_, _)) |
| .WillOnce([&signal_closure](const SyncToken& sync_token, |
| base::OnceClosure* callback) { |
| signal_closure = std::move(*callback); |
| }); |
| EXPECT_CALL(*gpu_control_, CanWaitUnverifiedSyncToken(sync_token)) |
| .WillOnce(Return(true)); |
| gl_->SignalSyncToken(sync_token, |
| base::BindOnce(&CountCallback, &signaled_count)); |
| EXPECT_EQ(0, signaled_count); |
| |
| // Inform the RasterImplementation that the context is lost. |
| GpuControlClient* gl_as_client = gl_; |
| gl_as_client->OnGpuControlLostContext(); |
| |
| // When GpuControl runs the callback, the original callback we gave to |
| // RasterImplementation is *not* run, since the context is lost and we |
| // have already run the lost context callback. |
| std::move(signal_closure).Run(); |
| EXPECT_EQ(0, signaled_count); |
| } |
| |
| TEST_F(RasterImplementationTest, ReportLoss) { |
| GpuControlClient* gl_as_client = gl_; |
| int lost_count = 0; |
| gl_->SetLostContextCallback(base::BindOnce(&CountCallback, &lost_count)); |
| EXPECT_EQ(0, lost_count); |
| |
| EXPECT_EQ(static_cast<GLenum>(GL_NO_ERROR), gl_->GetGraphicsResetStatusKHR()); |
| gl_as_client->OnGpuControlLostContext(); |
| EXPECT_NE(static_cast<GLenum>(GL_NO_ERROR), gl_->GetGraphicsResetStatusKHR()); |
| // The lost context callback should be run when RasterImplementation is |
| // notified of the loss. |
| EXPECT_EQ(1, lost_count); |
| } |
| |
| TEST_F(RasterImplementationTest, ReportLossReentrant) { |
| GpuControlClient* gl_as_client = gl_; |
| int lost_count = 0; |
| gl_->SetLostContextCallback(base::BindOnce(&CountCallback, &lost_count)); |
| EXPECT_EQ(0, lost_count); |
| |
| EXPECT_EQ(static_cast<GLenum>(GL_NO_ERROR), gl_->GetGraphicsResetStatusKHR()); |
| gl_as_client->OnGpuControlLostContextMaybeReentrant(); |
| EXPECT_NE(static_cast<GLenum>(GL_NO_ERROR), gl_->GetGraphicsResetStatusKHR()); |
| // The lost context callback should not be run yet to avoid calling back into |
| // clients re-entrantly, and having them re-enter RasterImplementation. |
| EXPECT_EQ(0, lost_count); |
| } |
| |
| TEST_F(RasterImplementationManualInitTest, FailInitOnTransferBufferFail) { |
| ContextInitOptions init_options; |
| init_options.transfer_buffer_initialize_fail = true; |
| EXPECT_FALSE(Initialize(init_options)); |
| } |
| |
| TEST_F(RasterImplementationTest, TransferCacheSerialization) { |
| gl_->set_max_inlined_entry_size_for_testing(768u); |
| uint32_t buffer_size = transfer_buffer_->MaxTransferBufferSize(); |
| ScopedTransferBufferPtr buffer(buffer_size, helper_, transfer_buffer_); |
| ASSERT_EQ(buffer.size(), buffer_size); |
| |
| base::span<uint8_t> buffer_span = buffer.as_byte_span(); |
| std::ranges::fill(buffer_span, 0u); |
| gl_->SetRasterMappedBufferForTesting(std::move(buffer)); |
| auto transfer_cache = gl_->CreateTransferCacheHelperForTesting(); |
| |
| std::vector<uint8_t> data(buffer_size - 16u); |
| base::span<uint8_t> memory = buffer_span.subspan(8u); |
| cc::ClientRawMemoryTransferCacheEntry inlined_entry(data); |
| EXPECT_EQ(transfer_cache->CreateEntry(inlined_entry, memory), data.size()); |
| EXPECT_EQ(base::span(data), memory.first(data.size())); |
| |
| data.resize(buffer_size + 16u); |
| memory = buffer_span.subspan(8u); |
| cc::ClientRawMemoryTransferCacheEntry non_inlined_entry(data); |
| EXPECT_EQ(transfer_cache->CreateEntry(non_inlined_entry, memory), 0u); |
| } |
| |
| TEST_F(RasterImplementationTest, SetActiveURLCHROMIUM) { |
| const uint32_t kURLBucketId = RasterImplementation::kResultBucketId; |
| const std::string url = "chrome://test"; |
| const uint32_t kPaddedStringSize = |
| transfer_buffer_->RoundToAlignment(url.size()); |
| |
| gl_->SetActiveURLCHROMIUM(url.c_str()); |
| EXPECT_EQ(GL_NO_ERROR, CheckError()); |
| |
| struct Cmds { |
| cmd::SetBucketSize url_size; |
| cmd::SetBucketData url_data; |
| cmd::SetToken set_token; |
| cmds::SetActiveURLCHROMIUM set_url_call; |
| cmd::SetBucketSize url_size_end; |
| }; |
| |
| ExpectedMemoryInfo mem = GetExpectedMemory(kPaddedStringSize); |
| UNSAFE_TODO(EXPECT_EQ( |
| 0, memcmp(url.c_str(), reinterpret_cast<char*>(mem.ptr), url.size()))); |
| |
| Cmds expected; |
| expected.url_size.Init(kURLBucketId, url.size()); |
| expected.url_data.Init(kURLBucketId, 0, url.size(), mem.id, mem.offset); |
| expected.set_token.Init(GetNextToken()); |
| expected.set_url_call.Init(kURLBucketId); |
| expected.url_size_end.Init(kURLBucketId, 0); |
| UNSAFE_TODO(EXPECT_EQ(0, memcmp(&expected, commands_, sizeof(expected)))); |
| |
| // Same URL shouldn't make any commands. |
| EXPECT_FALSE(NoCommandsWritten()); |
| ClearCommands(); |
| gl_->SetActiveURLCHROMIUM(url.c_str()); |
| EXPECT_TRUE(NoCommandsWritten()); |
| } |
| |
| // https://crbug.com/543707066 |
| TEST_F(RasterImplementationTest, ReadbackImagePixelsSyncPadding) { |
| gpu::Mailbox mailbox = gpu::Mailbox::Generate(); |
| SkImageInfo dst_info = SkImageInfo::MakeN32Premul(2, 2); |
| GLuint dst_row_bytes = |
| 12; // 2 pixels * 4 bytes/pixel = 8 bytes. Row padding = 4 bytes. |
| |
| GLuint color_space_offset = base::bits::AlignUp( |
| sizeof(cmds::ReadbackARGBImagePixelsINTERNALImmediate::Result), |
| sizeof(uint64_t)); |
| GLuint pixels_offset = color_space_offset; |
| GLuint dst_size = dst_info.computeByteSize(dst_row_bytes); |
| GLuint total_size = |
| pixels_offset + |
| base::bits::AlignUp(dst_size, static_cast<GLuint>(sizeof(uint64_t))); |
| |
| ExpectedMemoryInfo mem = GetExpectedMappedMemory(total_size); |
| |
| std::vector<uint8_t> dst_pixels(dst_row_bytes * dst_info.height(), 0xAA); |
| |
| EXPECT_CALL(*command_buffer(), OnFlush()) |
| .WillOnce([mem, pixels_offset, dst_size]() { |
| // Write 1 to readback_result (at the beginning of shm). |
| auto* result = reinterpret_cast< |
| cmds::ReadbackARGBImagePixelsINTERNALImmediate::Result*>(mem.ptr); |
| *result = 1; |
| |
| // Write test data to the pixel portion of the shared memory. |
| auto src_pixels = mem.span.subspan(pixels_offset, dst_size); |
| // Fill src_pixels with distinct values, e.g. 1 to dst_size |
| for (size_t i = 0; i < dst_size; ++i) { |
| src_pixels[i] = static_cast<uint8_t>(i + 1); |
| } |
| }) |
| .RetiresOnSaturation(); |
| |
| bool success = gl_->ReadbackImagePixels(mailbox, dst_info, dst_row_bytes, |
| /*src_x=*/0, /*src_y=*/0, |
| /*plane_index=*/0, dst_pixels.data()); |
| |
| EXPECT_TRUE(success); |
| |
| // Expected output: |
| // Row 1 (pixels: 0 to 7) copied from src_pixels (0 to 7): 1, 2, 3, 4, 5, 6, |
| // 7, 8. Row 1 (padding: 8 to 11) untouched: 0xAA, 0xAA, 0xAA, 0xAA. Row 2 |
| // (pixels: 12 to 19) copied from src_pixels (12 to 19): 13, 14, 15, 16, 17, |
| // 18, 19, 20. Row 2 (padding: 20 to 23) untouched: 0xAA, 0xAA, 0xAA, 0xAA. |
| |
| std::vector<uint8_t> expected_pixels(dst_row_bytes * dst_info.height(), 0xAA); |
| for (int y = 0; y < dst_info.height(); ++y) { |
| for (size_t x = 0; x < dst_info.minRowBytes(); ++x) { |
| size_t dst_idx = y * dst_row_bytes + x; |
| size_t src_idx = y * dst_row_bytes + x; |
| expected_pixels[dst_idx] = static_cast<uint8_t>(src_idx + 1); |
| } |
| } |
| |
| EXPECT_EQ(dst_pixels, expected_pixels); |
| } |
| |
| // https://crbug.com/543707066 |
| TEST_F(RasterImplementationTest, ReadbackImagePixelsAsyncPadding) { |
| gpu::Mailbox mailbox = gpu::Mailbox::Generate(); |
| SkImageInfo dst_info = SkImageInfo::MakeN32Premul(2, 2); |
| GLuint dst_row_bytes = |
| 12; // 2 pixels * 4 bytes/pixel = 8 bytes. Row padding = 4 bytes. |
| |
| GLuint color_space_offset = base::bits::AlignUp( |
| sizeof(cmds::ReadbackARGBImagePixelsINTERNALImmediate::Result), |
| sizeof(uint64_t)); |
| GLuint pixels_offset = color_space_offset; |
| GLuint dst_size = dst_info.computeByteSize(dst_row_bytes); |
| GLuint total_size = |
| pixels_offset + |
| base::bits::AlignUp(dst_size, static_cast<GLuint>(sizeof(uint64_t))); |
| |
| ExpectedMemoryInfo mem = GetExpectedMappedMemory(total_size); |
| |
| std::vector<uint8_t> dst_pixels(dst_row_bytes * dst_info.height(), 0xAA); |
| |
| base::OnceClosure signal_closure; |
| EXPECT_CALL(*gpu_control_, DoSignalQuery(_, _)) |
| .WillOnce([&signal_closure](uint32_t query, base::OnceClosure* callback) { |
| signal_closure = std::move(*callback); |
| }) |
| .RetiresOnSaturation(); |
| |
| bool callback_run = false; |
| bool callback_success = false; |
| base::OnceCallback<void(bool)> readback_done = base::BindOnce( |
| [](bool* run, bool* success, bool val) { |
| *run = true; |
| *success = val; |
| }, |
| &callback_run, &callback_success); |
| |
| bool success = ReadbackImagePixelsINTERNAL( |
| mailbox, dst_info, dst_row_bytes, /*src_x=*/0, /*src_y=*/0, |
| /*plane_index=*/0, std::move(readback_done), dst_pixels.data()); |
| |
| EXPECT_TRUE(success); |
| EXPECT_FALSE(callback_run); |
| |
| // Now simulate the GPU process completing the operation by writing to shared |
| // memory. |
| auto* result = |
| reinterpret_cast<cmds::ReadbackARGBImagePixelsINTERNALImmediate::Result*>( |
| mem.ptr); |
| *result = 1; |
| |
| auto src_pixels = mem.span.subspan(pixels_offset, dst_size); |
| for (size_t i = 0; i < dst_size; ++i) { |
| src_pixels[i] = static_cast<uint8_t>(i + 1); |
| } |
| |
| EXPECT_CALL(*command_buffer(), OnFlush()).Times(AnyNumber()); |
| |
| // Run the signal query callback. |
| ASSERT_TRUE(signal_closure); |
| std::move(signal_closure).Run(); |
| |
| EXPECT_TRUE(callback_run); |
| EXPECT_TRUE(callback_success); |
| |
| // Verify pixels and padding. |
| std::vector<uint8_t> expected_pixels(dst_row_bytes * dst_info.height(), 0xAA); |
| for (int y = 0; y < dst_info.height(); ++y) { |
| for (size_t x = 0; x < dst_info.minRowBytes(); ++x) { |
| size_t dst_idx = y * dst_row_bytes + x; |
| size_t src_idx = y * dst_row_bytes + x; |
| expected_pixels[dst_idx] = static_cast<uint8_t>(src_idx + 1); |
| } |
| } |
| |
| EXPECT_EQ(dst_pixels, expected_pixels); |
| } |
| |
| // https://crbug.com/543707066 |
| TEST_F(RasterImplementationTest, ReadbackYUVPixelsAsyncPadding) { |
| gpu::Mailbox mailbox = gpu::Mailbox::Generate(); |
| gfx::Rect source_rect(0, 0, 4, 4); |
| gfx::Rect output_rect(0, 0, 4, 4); |
| int y_plane_stride = 8; |
| int u_plane_stride = 4; |
| int v_plane_stride = 4; |
| |
| auto y_offset = static_cast<GLuint>(base::bits::AlignUp( |
| sizeof(cmds::ReadbackYUVImagePixelsINTERNALImmediate::Result), |
| sizeof(uint64_t))); |
| GLuint y_padded_size = output_rect.height() * y_plane_stride; |
| |
| constexpr auto kSizeofUint64 = static_cast<GLuint>(sizeof(uint64_t)); |
| GLuint u_offset = |
| base::bits::AlignUp(y_offset + y_padded_size, kSizeofUint64); |
| GLuint u_padded_size = (output_rect.height() / 2) * u_plane_stride; |
| |
| GLuint v_offset = |
| base::bits::AlignUp(u_offset + u_padded_size, kSizeofUint64); |
| GLuint v_padded_size = (output_rect.height() / 2) * v_plane_stride; |
| |
| size_t total_size = |
| base::bits::AlignUp(v_offset + v_padded_size, kSizeofUint64); |
| |
| ExpectedMemoryInfo mem = GetExpectedMappedMemory(total_size); |
| |
| std::vector<uint8_t> y_plane_data(y_plane_stride * output_rect.height(), |
| 0xAA); |
| std::vector<uint8_t> u_plane_data(u_plane_stride * (output_rect.height() / 2), |
| 0xAA); |
| std::vector<uint8_t> v_plane_data(v_plane_stride * (output_rect.height() / 2), |
| 0xAA); |
| |
| base::OnceClosure signal_closure; |
| EXPECT_CALL(*gpu_control_, DoSignalQuery(_, _)) |
| .WillOnce([&signal_closure](uint32_t query, base::OnceClosure* callback) { |
| signal_closure = std::move(*callback); |
| }) |
| .RetiresOnSaturation(); |
| |
| bool release_mailbox_called = false; |
| base::OnceClosure release_mailbox = base::BindOnce( |
| [](bool* called) { *called = true; }, &release_mailbox_called); |
| |
| bool callback_run = false; |
| bool callback_success = false; |
| base::OnceCallback<void(bool)> readback_done = base::BindOnce( |
| [](bool* run, bool* success, bool val) { |
| *run = true; |
| *success = val; |
| }, |
| &callback_run, &callback_success); |
| |
| gl_->ReadbackYUVPixelsAsync( |
| mailbox, GL_TEXTURE_2D, source_rect, output_rect, |
| /*vertically_flip_texture=*/false, y_plane_stride, |
| base::span(y_plane_data), u_plane_stride, base::span(u_plane_data), |
| v_plane_stride, base::span(v_plane_data), std::move(release_mailbox), |
| std::move(readback_done)); |
| |
| EXPECT_FALSE(callback_run); |
| |
| // Simulate GPU process completion. |
| auto* result = |
| reinterpret_cast<cmds::ReadbackYUVImagePixelsINTERNALImmediate::Result*>( |
| mem.ptr); |
| *result = 1; |
| |
| // Fill in mock source data for each plane in the shared memory. |
| auto y_src_pixels = mem.span.subspan(y_offset, y_padded_size); |
| for (size_t i = 0; i < y_padded_size; ++i) { |
| y_src_pixels[i] = static_cast<uint8_t>(i + 1); |
| } |
| |
| auto u_src_pixels = mem.span.subspan(u_offset, u_padded_size); |
| for (size_t i = 0; i < u_padded_size; ++i) { |
| u_src_pixels[i] = static_cast<uint8_t>(i + 41); |
| } |
| |
| auto v_src_pixels = mem.span.subspan(v_offset, v_padded_size); |
| for (size_t i = 0; i < v_padded_size; ++i) { |
| v_src_pixels[i] = static_cast<uint8_t>(i + 51); |
| } |
| |
| EXPECT_CALL(*command_buffer(), OnFlush()).Times(AnyNumber()); |
| |
| // Run the signal query callback. |
| ASSERT_TRUE(signal_closure); |
| std::move(signal_closure).Run(); |
| |
| EXPECT_TRUE(callback_run); |
| EXPECT_TRUE(callback_success); |
| EXPECT_TRUE(release_mailbox_called); |
| |
| // Verify Y plane. Width = 4, Stride = 8, Height = 4. |
| std::vector<uint8_t> expected_y(y_plane_stride * output_rect.height(), 0xAA); |
| for (int y = 0; y < output_rect.height(); ++y) { |
| for (int x = 0; x < output_rect.width(); ++x) { |
| size_t dst_idx = y * y_plane_stride + x; |
| size_t src_idx = y * y_plane_stride + x; |
| expected_y[dst_idx] = static_cast<uint8_t>(src_idx + 1); |
| } |
| } |
| EXPECT_EQ(y_plane_data, expected_y); |
| |
| // Verify U plane. Width = 2, Stride = 4, Height = 2. |
| std::vector<uint8_t> expected_u(u_plane_stride * (output_rect.height() / 2), |
| 0xAA); |
| for (int y = 0; y < output_rect.height() / 2; ++y) { |
| for (int x = 0; x < output_rect.width() / 2; ++x) { |
| size_t dst_idx = y * u_plane_stride + x; |
| size_t src_idx = y * u_plane_stride + x; |
| expected_u[dst_idx] = static_cast<uint8_t>(src_idx + 41); |
| } |
| } |
| EXPECT_EQ(u_plane_data, expected_u); |
| |
| // Verify V plane. Width = 2, Stride = 4, Height = 2. |
| std::vector<uint8_t> expected_v(v_plane_stride * (output_rect.height() / 2), |
| 0xAA); |
| for (int y = 0; y < output_rect.height() / 2; ++y) { |
| for (int x = 0; x < output_rect.width() / 2; ++x) { |
| size_t dst_idx = y * v_plane_stride + x; |
| size_t src_idx = y * v_plane_stride + x; |
| expected_v[dst_idx] = static_cast<uint8_t>(src_idx + 51); |
| } |
| } |
| EXPECT_EQ(v_plane_data, expected_v); |
| } |
| |
| #include "gpu/command_buffer/client/raster_implementation_unittest_autogen.h" |
| |
| } // namespace raster |
| } // namespace gpu |