| // 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 <numeric> | |
| #include "base/bits.h" | |
| #include "base/compiler_specific.h" | |
| #include "base/containers/heap_array.h" | |
| #include "base/containers/span.h" | |
| #include "base/feature_list.h" | |
| #include "base/functional/bind.h" | |
| #include "base/memory/ptr_util.h" | |
| #include "base/memory/raw_ptr.h" | |
| #include "base/test/scoped_feature_list.h" | |
| #include "base/test/test_future.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 gfx::Size& source_size, | |
| const SkImageInfo& dst_info, | |
| GLuint dst_row_bytes, | |
| int src_x, | |
| int src_y, | |
| int plane_index, | |
| base::OnceCallback<void(bool)> readback_done, | |
| base::span<uint8_t> dst_pixels) { | |
| return gl_->ReadbackImagePixelsINTERNAL( | |
| source_mailbox, source_size, 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) { | |
| base::test::ScopedFeatureList feature({}, {kDisableErrorHandlingForReadback}); | |
| 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 shm_size = dst_info.computeMinByteSize(); | |
| GLuint total_size = | |
| pixels_offset + | |
| base::bits::AlignUp(shm_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, shm_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, shm_size); | |
| // Fill src_pixels with distinct values, e.g. 1 to shm_size | |
| for (size_t i = 0; i < shm_size; ++i) { | |
| src_pixels[i] = static_cast<uint8_t>(i + 1); | |
| } | |
| }) | |
| .RetiresOnSaturation(); | |
| bool success = gl_->ReadbackImagePixels( | |
| mailbox, /*source_size=*/gfx::Size(2, 2), 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..8. | |
| // - Row 1 (padding: 8 to 11) untouched: 0xAA, 0xAA, 0xAA, 0xAA. | |
| // - Row 2 (pixels: 12 to 19) copied from src_pixels (8 to 15): 9..16. | |
| // - 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_info.minRowBytes() + 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 shm_size = dst_info.computeMinByteSize(); | |
| GLuint total_size = | |
| pixels_offset + | |
| base::bits::AlignUp(shm_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, /*source_size=*/gfx::Size(2, 2), dst_info, dst_row_bytes, | |
| /*src_x=*/0, /*src_y=*/0, /*plane_index=*/0, std::move(readback_done), | |
| dst_pixels); | |
| 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, shm_size); | |
| for (size_t i = 0; i < shm_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_info.minRowBytes() + x; | |
| expected_pixels[dst_idx] = static_cast<uint8_t>(src_idx + 1); | |
| } | |
| } | |
| EXPECT_EQ(dst_pixels, expected_pixels); | |
| } | |
| TEST_F(RasterImplementationTest, ReadbackImagePixelsSyncOutOfBounds) { | |
| base::test::ScopedFeatureList feature({}, {kDisableErrorHandlingForReadback}); | |
| // Request a 4x4 read starting at (-1, -1) with row padding (20 bytes/row). | |
| // The intersection with the 2x2 source image is (0, 0, 2, 2), which lands at | |
| // offset (1, 1) in the 4x4 destination buffer. | |
| SkImageInfo dst_info = SkImageInfo::MakeN32Premul(4, 4); | |
| GLuint dst_row_bytes = 20; // 4 pixels * 4 bytes/pixel = 16 + 4 padding. | |
| SkImageInfo clipped_info = SkImageInfo::MakeN32Premul(2, 2); | |
| GLuint pixels_offset = base::bits::AlignUp( | |
| sizeof(cmds::ReadbackARGBImagePixelsINTERNALImmediate::Result), | |
| sizeof(uint64_t)); | |
| GLuint shm_size = clipped_info.computeMinByteSize(); | |
| GLuint total_size = | |
| pixels_offset + | |
| base::bits::AlignUp(shm_size, static_cast<GLuint>(sizeof(uint64_t))); | |
| ExpectedMemoryInfo mem = GetExpectedMappedMemory(total_size); | |
| EXPECT_CALL(*command_buffer(), OnFlush()).WillOnce([&]() { | |
| *reinterpret_cast<cmds::ReadbackARGBImagePixelsINTERNALImmediate::Result*>( | |
| mem.ptr) = 1; | |
| std::ranges::iota(mem.span.subspan(pixels_offset, shm_size), 0); | |
| }); | |
| std::vector<uint8_t> dst_pixels(dst_row_bytes * dst_info.height(), 0xAA); | |
| EXPECT_TRUE(gl_->ReadbackImagePixels( | |
| gpu::Mailbox::Generate(), /*source_size=*/gfx::Size(2, 2), dst_info, | |
| dst_row_bytes, /*src_x=*/-1, /*src_y=*/-1, /*plane_index=*/0, | |
| dst_pixels.data())); | |
| std::vector<uint8_t> expected_pixels(dst_row_bytes * dst_info.height(), 0xAA); | |
| for (int y = 0; y < clipped_info.height(); ++y) { | |
| for (size_t x = 0; x < clipped_info.minRowBytes(); ++x) { | |
| size_t dst_idx = (y + 1) * dst_row_bytes + dst_info.bytesPerPixel() + x; | |
| size_t src_idx = y * clipped_info.minRowBytes() + x; | |
| expected_pixels[dst_idx] = static_cast<uint8_t>(src_idx); | |
| } | |
| } | |
| EXPECT_EQ(dst_pixels, expected_pixels); | |
| } | |
| TEST_F(RasterImplementationTest, ReadbackImagePixelsSyncCompletelyOutOfBounds) { | |
| base::test::ScopedFeatureList feature({}, {kDisableErrorHandlingForReadback}); | |
| // Read a 2x2 image at index (3, 3), that is, completely out of bounds. | |
| SkImageInfo dst_info = SkImageInfo::MakeN32Premul(2, 2); | |
| std::vector<uint8_t> dst_pixels(dst_info.computeMinByteSize(), 0xAA); | |
| EXPECT_FALSE(gl_->ReadbackImagePixels( | |
| gpu::Mailbox::Generate(), /*source_size=*/gfx::Size(2, 2), dst_info, | |
| dst_info.minRowBytes(), /*src_x=*/3, /*src_y=*/3, /*plane_index=*/0, | |
| dst_pixels.data())); | |
| EXPECT_EQ(dst_pixels, | |
| std::vector<uint8_t>(dst_info.computeMinByteSize(), 0xAA)); | |
| } | |
| TEST_F(RasterImplementationTest, ReadbackImagePixelsAsyncOutOfBounds) { | |
| // Request a 4x4 read starting at (-1, -1) with row padding (20 bytes/row). | |
| // The intersection with the 2x2 source image is (0, 0, 2, 2), which lands at | |
| // offset (1, 1) in the 4x4 destination buffer. | |
| SkImageInfo dst_info = SkImageInfo::MakeN32Premul(4, 4); | |
| GLuint dst_row_bytes = 20; | |
| SkImageInfo clipped_info = SkImageInfo::MakeN32Premul(2, 2); | |
| GLuint pixels_offset = base::bits::AlignUp( | |
| sizeof(cmds::ReadbackARGBImagePixelsINTERNALImmediate::Result), | |
| sizeof(uint64_t)); | |
| GLuint shm_size = clipped_info.computeMinByteSize(); | |
| GLuint total_size = | |
| pixels_offset + | |
| base::bits::AlignUp(shm_size, static_cast<GLuint>(sizeof(uint64_t))); | |
| ExpectedMemoryInfo mem = GetExpectedMappedMemory(total_size); | |
| EXPECT_CALL(*gpu_control_, DoSignalQuery(_, _)) | |
| .WillOnce([&](uint32_t query, base::OnceClosure* callback) { | |
| *reinterpret_cast< | |
| cmds::ReadbackARGBImagePixelsINTERNALImmediate::Result*>(mem.ptr) = | |
| 1; | |
| std::ranges::iota(mem.span.subspan(pixels_offset, shm_size), 0); | |
| std::move(*callback).Run(); | |
| }); | |
| base::test::TestFuture<bool> result_future; | |
| std::vector<uint8_t> dst_pixels(dst_row_bytes * dst_info.height(), 0xAA); | |
| EXPECT_TRUE(ReadbackImagePixelsINTERNAL( | |
| gpu::Mailbox::Generate(), /*source_size=*/gfx::Size(2, 2), dst_info, | |
| dst_row_bytes, /*src_x=*/-1, /*src_y=*/-1, /*plane_index=*/0, | |
| result_future.GetCallback(), dst_pixels)); | |
| EXPECT_TRUE(result_future.Get()); | |
| std::vector<uint8_t> expected_pixels(dst_row_bytes * dst_info.height(), 0xAA); | |
| for (int y = 0; y < clipped_info.height(); ++y) { | |
| for (size_t x = 0; x < clipped_info.minRowBytes(); ++x) { | |
| size_t dst_idx = (y + 1) * dst_row_bytes + dst_info.bytesPerPixel() + x; | |
| size_t src_idx = y * clipped_info.minRowBytes() + x; | |
| expected_pixels[dst_idx] = static_cast<uint8_t>(src_idx); | |
| } | |
| } | |
| EXPECT_EQ(dst_pixels, expected_pixels); | |
| } | |
| TEST_F(RasterImplementationTest, | |
| ReadbackImagePixelsAsyncCompletelyOutOfBounds) { | |
| // Read a 2x2 image at index (3, 3), that is, completely out of bounds. | |
| SkImageInfo dst_info = SkImageInfo::MakeN32Premul(2, 2); | |
| std::vector<uint8_t> dst_pixels(dst_info.computeMinByteSize(), 0xAA); | |
| base::test::TestFuture<bool> result_future; | |
| EXPECT_FALSE(ReadbackImagePixelsINTERNAL( | |
| gpu::Mailbox::Generate(), /*source_size=*/gfx::Size(2, 2), dst_info, | |
| dst_info.minRowBytes(), /*src_x=*/3, /*src_y=*/3, /*plane_index=*/0, | |
| result_future.GetCallback(), dst_pixels)); | |
| EXPECT_FALSE(result_future.Get()); | |
| EXPECT_EQ(dst_pixels, | |
| std::vector<uint8_t>(dst_info.computeMinByteSize(), 0xAA)); | |
| } | |
| // 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 |