| // Copyright 2012 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 GLES2Implementation. | |
| #include "gpu/command_buffer/client/gles2_implementation.h" | |
| #include <GLES2/gl2.h> | |
| #include <GLES2/gl2ext.h> | |
| #include <GLES2/gl2extchromium.h> | |
| #include <GLES3/gl3.h> | |
| #include <GLES3/gl32.h> | |
| #include <stddef.h> | |
| #include <stdint.h> | |
| #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/raw_ptr.h" | |
| #include "gpu/command_buffer/client/client_test_helper.h" | |
| #include "gpu/command_buffer/client/gles2_cmd_helper.h" | |
| #include "gpu/command_buffer/client/mock_transfer_buffer.h" | |
| #include "gpu/command_buffer/client/program_info_manager.h" | |
| #include "gpu/command_buffer/client/query_tracker.h" | |
| #include "gpu/command_buffer/client/ring_buffer.h" | |
| #include "gpu/command_buffer/client/shared_memory_limits.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 testing::_; | |
| using testing::AtLeast; | |
| using testing::AnyNumber; | |
| using testing::DoAll; | |
| using testing::InSequence; | |
| using testing::Mock; | |
| using testing::Pointee; | |
| using testing::SaveArg; | |
| using testing::Sequence; | |
| using testing::StrictMock; | |
| using testing::Truly; | |
| using testing::Return; | |
| using testing::ReturnRef; | |
| namespace gpu { | |
| namespace gles2 { | |
| ACTION_P2(SetMemory, dst, obj) { | |
| UNSAFE_TODO(memcpy(dst, &obj, sizeof(obj))); | |
| } | |
| ACTION_P3(SetMemoryFromArray, dst_span, src_span, size) { | |
| // GMock actions are by default const so we need to const_cast to set the | |
| // contents of the span. | |
| // const_cast only works on pointers so first take the address, to work with | |
| // the macros we use decltype to get the correct type. | |
| const_cast<decltype(dst_span)*>(&dst_span)->copy_prefix_from( | |
| src_span.first(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)) { | |
| UNSAFE_TODO(memcpy(result_, &result, sizeof(T))); | |
| } | |
| private: | |
| uint32_t size_; | |
| char result_[sizeof(T)]; | |
| }; | |
| // Struct to make it easy to pass a vec4 worth of floats. | |
| struct FourFloats { | |
| FourFloats(float _x, float _y, float _z, float _w) | |
| : x(_x), | |
| y(_y), | |
| z(_z), | |
| w(_w) { | |
| } | |
| float x; | |
| float y; | |
| float z; | |
| float w; | |
| }; | |
| #pragma pack(push, 1) | |
| // Struct that holds 7 characters. | |
| struct Str7 { | |
| char str[7]; | |
| }; | |
| #pragma pack(pop) | |
| // API wrapper for Buffers. | |
| class GenBuffersAPI { | |
| public: | |
| static void Gen(GLES2Implementation* gl_impl, GLsizei n, GLuint* ids) { | |
| gl_impl->GenBuffers(n, ids); | |
| } | |
| static void Delete(GLES2Implementation* gl_impl, | |
| GLsizei n, | |
| const GLuint* ids) { | |
| gl_impl->DeleteBuffers(n, ids); | |
| } | |
| }; | |
| // API wrapper for Renderbuffers. | |
| class GenRenderbuffersAPI { | |
| public: | |
| static void Gen(GLES2Implementation* gl_impl, GLsizei n, GLuint* ids) { | |
| gl_impl->GenRenderbuffers(n, ids); | |
| } | |
| static void Delete(GLES2Implementation* gl_impl, | |
| GLsizei n, | |
| const GLuint* ids) { | |
| gl_impl->DeleteRenderbuffers(n, ids); | |
| } | |
| }; | |
| // API wrapper for Textures. | |
| class GenTexturesAPI { | |
| public: | |
| static void Gen(GLES2Implementation* gl_impl, GLsizei n, GLuint* ids) { | |
| gl_impl->GenTextures(n, ids); | |
| } | |
| static void Delete(GLES2Implementation* gl_impl, | |
| GLsizei n, | |
| const GLuint* ids) { | |
| gl_impl->DeleteTextures(n, ids); | |
| } | |
| }; | |
| class GLES2ImplementationTest : public testing::Test { | |
| protected: | |
| static const int kNumTestContexts = 2; | |
| static const uint8_t kInitialValue = 0xBD; | |
| static const int32_t kNumCommandEntries = 500; | |
| static const int32_t kCommandBufferSizeBytes = | |
| kNumCommandEntries * sizeof(CommandBufferEntry); | |
| static const size_t kTransferBufferSize = 512; | |
| static const GLint kMaxCombinedTextureImageUnits = 8; | |
| static const GLint kMaxCubeMapTextureSize = 64; | |
| static const GLint kMaxFragmentUniformVectors = 16; | |
| static const GLint kMaxRenderbufferSize = 64; | |
| static const GLint kMaxTextureImageUnits = 8; | |
| static const GLint kMaxTextureSize = 128; | |
| static const GLint kMaxVaryingVectors = 8; | |
| static const GLint kMaxVertexAttribs = 8; | |
| static const GLint kMaxVertexTextureImageUnits = 0; | |
| static const GLint kMaxVertexUniformVectors = 128; | |
| static const GLint kMaxViewportWidth = 8192; | |
| static const GLint kMaxViewportHeight = 6144; | |
| static const GLint kNumCompressedTextureFormats = 0; | |
| static const GLint kNumShaderBinaryFormats = 0; | |
| static const GLuint kMaxTransformFeedbackSeparateAttribs = 4; | |
| static const GLuint kMaxUniformBufferBindings = 36; | |
| static const GLuint kStartId = 1024; | |
| static const GLuint kBuffersStartId = 1; | |
| static const GLuint kFramebuffersStartId = 1; | |
| static const GLuint kProgramsAndShadersStartId = 1; | |
| static const GLuint kRenderbuffersStartId = 1; | |
| static const GLuint kSamplersStartId = 1; | |
| static const GLuint kTexturesStartId = 1; | |
| static const GLuint kTransformFeedbacksStartId = 1; | |
| static const GLuint kQueriesStartId = 1; | |
| static const GLuint kVertexArraysStartId = 1; | |
| typedef MockTransferBuffer::ExpectedMemoryInfo ExpectedMemoryInfo; | |
| class TestContext { | |
| public: | |
| TestContext() : commands_(nullptr), token_(0) {} | |
| bool Initialize(ShareGroup* share_group, | |
| bool lose_context_when_out_of_memory, | |
| bool transfer_buffer_initialize_fail, | |
| bool sync_query, | |
| bool occlusion_query_boolean, | |
| bool timer_queries, | |
| int major_version, | |
| int minor_version) { | |
| SharedMemoryLimits limits = SharedMemoryLimitsForTesting(); | |
| command_buffer_.reset(new StrictMock<MockClientCommandBuffer>()); | |
| transfer_buffer_.reset( | |
| new MockTransferBuffer(command_buffer_.get(), | |
| kTransferBufferSize, | |
| GLES2Implementation::kStartingOffset, | |
| GLES2Implementation::kAlignment, | |
| transfer_buffer_initialize_fail)); | |
| helper_.reset(new GLES2CmdHelper(command_buffer())); | |
| helper_->Initialize(limits.command_buffer_size); | |
| gpu_control_.reset(new StrictMock<MockClientGpuControl>()); | |
| gl_capabilities_.VisitPrecisions( | |
| [](GLenum shader, GLenum type, | |
| GLCapabilities::ShaderPrecision* precision) { | |
| precision->min_range = 3; | |
| precision->max_range = 5; | |
| precision->precision = 7; | |
| }); | |
| gl_capabilities_.max_combined_texture_image_units = | |
| kMaxCombinedTextureImageUnits; | |
| gl_capabilities_.max_cube_map_texture_size = kMaxCubeMapTextureSize; | |
| gl_capabilities_.max_fragment_uniform_vectors = | |
| kMaxFragmentUniformVectors; | |
| gl_capabilities_.max_renderbuffer_size = kMaxRenderbufferSize; | |
| gl_capabilities_.max_texture_image_units = kMaxTextureImageUnits; | |
| capabilities_.max_texture_size = kMaxTextureSize; | |
| gl_capabilities_.max_texture_size = kMaxTextureSize; | |
| gl_capabilities_.max_varying_vectors = kMaxVaryingVectors; | |
| gl_capabilities_.max_vertex_attribs = kMaxVertexAttribs; | |
| gl_capabilities_.max_vertex_texture_image_units = | |
| kMaxVertexTextureImageUnits; | |
| gl_capabilities_.max_vertex_uniform_vectors = kMaxVertexUniformVectors; | |
| gl_capabilities_.max_viewport_width = kMaxViewportWidth; | |
| gl_capabilities_.max_viewport_height = kMaxViewportHeight; | |
| gl_capabilities_.num_compressed_texture_formats = | |
| kNumCompressedTextureFormats; | |
| gl_capabilities_.num_shader_binary_formats = kNumShaderBinaryFormats; | |
| gl_capabilities_.max_transform_feedback_separate_attribs = | |
| kMaxTransformFeedbackSeparateAttribs; | |
| gl_capabilities_.max_uniform_buffer_bindings = kMaxUniformBufferBindings; | |
| capabilities_.sync_query = sync_query; | |
| gl_capabilities_.sync_query = sync_query; | |
| gl_capabilities_.occlusion_query_boolean = occlusion_query_boolean; | |
| gl_capabilities_.timer_queries = timer_queries; | |
| gl_capabilities_.major_version = major_version; | |
| gl_capabilities_.minor_version = minor_version; | |
| EXPECT_CALL(*gpu_control_, GetCapabilities()) | |
| .WillOnce(ReturnRef(capabilities_)); | |
| EXPECT_CALL(*gpu_control_, GetGLCapabilities()) | |
| .WillOnce(ReturnRef(gl_capabilities_)); | |
| { | |
| InSequence sequence; | |
| gl_ = std::make_unique<GLES2Implementation>( | |
| helper_.get(), share_group, 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); | |
| 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<GLES2CmdHelper> helper_; | |
| std::unique_ptr<MockTransferBuffer> transfer_buffer_; | |
| std::unique_ptr<GLES2Implementation> gl_; | |
| raw_ptr<CommandBufferEntry> commands_; | |
| int token_; | |
| Capabilities capabilities_; | |
| GLCapabilities gl_capabilities_; | |
| }; | |
| GLES2ImplementationTest() : 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 occlusion_query_boolean = true; | |
| bool timer_queries = true; | |
| int major_version = 2; | |
| int minor_version = 0; | |
| }; | |
| bool Initialize(const ContextInitOptions& init_options) { | |
| bool success = true; | |
| share_group_ = base::MakeRefCounted<ShareGroup>(/*tracing_id=*/0); | |
| for (int i = 0; i < kNumTestContexts; i++) { | |
| if (!test_contexts_[i].Initialize( | |
| share_group_.get(), | |
| init_options.lose_context_when_out_of_memory, | |
| init_options.transfer_buffer_initialize_fail, | |
| init_options.sync_query, | |
| init_options.occlusion_query_boolean, | |
| init_options.timer_queries, | |
| init_options.major_version, | |
| init_options.minor_version)) | |
| success = false; | |
| } | |
| // Default to test context 0. | |
| gpu_control_ = test_contexts_[0].gpu_control_.get(); | |
| helper_ = test_contexts_[0].helper_.get(); | |
| transfer_buffer_ = test_contexts_[0].transfer_buffer_.get(); | |
| gl_ = test_contexts_[0].gl_.get(); | |
| commands_ = test_contexts_[0].commands_; | |
| return success; | |
| } | |
| MockClientCommandBuffer* command_buffer() const { | |
| return test_contexts_[0].command_buffer_.get(); | |
| } | |
| int GetNextToken() { return test_contexts_[0].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())); | |
| } | |
| size_t MaxTransferBufferSize() { | |
| return transfer_buffer_->MaxTransferBufferSize(); | |
| } | |
| void SetMappedMemoryLimit(size_t limit) { | |
| gl_->mapped_memory_->set_max_allocated_bytes(limit); | |
| } | |
| ExpectedMemoryInfo GetExpectedMemory(size_t size) { | |
| return transfer_buffer_->GetExpectedMemory(size); | |
| } | |
| ExpectedMemoryInfo GetExpectedResultMemory(size_t size) { | |
| return transfer_buffer_->GetExpectedResultMemory(size); | |
| } | |
| ExpectedMemoryInfo GetExpectedMappedMemory(size_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; | |
| } | |
| // Sets the ProgramInfoManager. The manager will be owned | |
| // by the ShareGroup. | |
| void SetProgramInfoManager(ProgramInfoManager* manager) { | |
| gl_->share_group()->SetProgramInfoManagerForTesting(manager); | |
| } | |
| 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 AllowExtraTransferBufferSize() { | |
| return gl_->max_extra_transfer_buffer_size_ > 0; | |
| } | |
| void SetQueryProcessCount(QueryTracker::Query* q, int32_t count) { | |
| q->info_.sync->process_count = count; | |
| } | |
| MappedMemoryManager* mapped_memory() { return gl_->mapped_memory_.get(); } | |
| 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; | |
| } | |
| void ResetErrorMessageCallback() { gl_->error_message_callback_.Reset(); } | |
| std::array<TestContext, kNumTestContexts> test_contexts_; | |
| scoped_refptr<ShareGroup> share_group_; | |
| raw_ptr<MockClientGpuControl> gpu_control_; | |
| raw_ptr<GLES2CmdHelper> helper_; | |
| raw_ptr<MockTransferBuffer> transfer_buffer_; | |
| raw_ptr<GLES2Implementation> gl_; | |
| raw_ptr<CommandBufferEntry> commands_; | |
| }; | |
| void GLES2ImplementationTest::SetUp() { | |
| ContextInitOptions init_options; | |
| ASSERT_TRUE(Initialize(init_options)); | |
| } | |
| void GLES2ImplementationTest::TearDown() { | |
| gl_ = nullptr; | |
| for (int i = 0; i < kNumTestContexts; i++) | |
| test_contexts_[i].TearDown(); | |
| } | |
| class GLES2ImplementationManualInitTest : public GLES2ImplementationTest { | |
| protected: | |
| void SetUp() override {} | |
| }; | |
| class GLES2ImplementationStrictSharedTest : public GLES2ImplementationTest { | |
| protected: | |
| void SetUp() override; | |
| template <class ResApi> | |
| void FlushGenerationTest() { | |
| GLuint id1, id2, id3; | |
| // Generate valid id. | |
| ResApi::Gen(gl_, 1, &id1); | |
| EXPECT_NE(id1, 0u); | |
| // Delete id1 and generate id2. id1 should not be reused. | |
| ResApi::Delete(gl_, 1, &id1); | |
| ResApi::Gen(gl_, 1, &id2); | |
| EXPECT_NE(id2, 0u); | |
| EXPECT_NE(id2, id1); | |
| // Expect id1 reuse after Flush. | |
| gl_->Flush(); | |
| ResApi::Gen(gl_, 1, &id3); | |
| EXPECT_EQ(id3, id1); | |
| } | |
| // Ids should not be reused unless the |Deleting| context does a Flush() | |
| // AND triggers a lazy release after that. | |
| template <class ResApi> | |
| void CrossContextGenerationTest() { | |
| GLES2Implementation* gl1 = test_contexts_[0].gl_.get(); | |
| GLES2Implementation* gl2 = test_contexts_[1].gl_.get(); | |
| GLuint id1, id2, id3; | |
| // Delete, no flush on context 1. No reuse. | |
| ResApi::Gen(gl1, 1, &id1); | |
| ResApi::Delete(gl1, 1, &id1); | |
| ResApi::Gen(gl1, 1, &id2); | |
| EXPECT_NE(id1, id2); | |
| // Flush context 2. Still no reuse. | |
| gl2->Flush(); | |
| ResApi::Gen(gl2, 1, &id3); | |
| EXPECT_NE(id1, id3); | |
| EXPECT_NE(id2, id3); | |
| // Flush on context 1, but no lazy release. Still no reuse. | |
| gl1->Flush(); | |
| ResApi::Gen(gl2, 1, &id3); | |
| EXPECT_NE(id1, id3); | |
| // Lazy release triggered by another Delete. Should reuse id1. | |
| ResApi::Delete(gl1, 1, &id2); | |
| ResApi::Gen(gl2, 1, &id3); | |
| EXPECT_EQ(id1, id3); | |
| } | |
| // Same as CrossContextGenerationTest(), but triggers an Auto Flush on | |
| // the Delete(). Tests an edge case regression. | |
| template <class ResApi> | |
| void CrossContextGenerationAutoFlushTest() { | |
| GLES2Implementation* gl1 = test_contexts_[0].gl_.get(); | |
| GLES2Implementation* gl2 = test_contexts_[1].gl_.get(); | |
| GLuint id1, id2, id3; | |
| // Delete, no flush on context 1. No reuse. | |
| // By half filling the buffer, an internal flush is forced on the Delete(). | |
| ResApi::Gen(gl1, 1, &id1); | |
| gl1->helper()->Noop(kNumCommandEntries / 2); | |
| ResApi::Delete(gl1, 1, &id1); | |
| ResApi::Gen(gl1, 1, &id2); | |
| EXPECT_NE(id1, id2); | |
| // Flush context 2. Still no reuse. | |
| gl2->Flush(); | |
| ResApi::Gen(gl2, 1, &id3); | |
| EXPECT_NE(id1, id3); | |
| EXPECT_NE(id2, id3); | |
| // Flush on context 1, but no lazy release. Still no reuse. | |
| gl1->Flush(); | |
| ResApi::Gen(gl2, 1, &id3); | |
| EXPECT_NE(id1, id3); | |
| // Lazy release triggered by another Delete. Should reuse id1. | |
| ResApi::Delete(gl1, 1, &id2); | |
| ResApi::Gen(gl2, 1, &id3); | |
| EXPECT_EQ(id1, id3); | |
| } | |
| // Require that deleting definitely-invalid IDs produces an error. | |
| template <class ResApi> | |
| void DeletingInvalidIdGeneratesError() { | |
| GLES2Implementation* gl1 = test_contexts_[0].gl_.get(); | |
| GLuint id1; | |
| ResApi::Gen(gl1, 1, &id1); | |
| const GLuint kDefinitelyInvalidId = 0xBEEF; | |
| EXPECT_EQ(GL_NO_ERROR, CheckError()); | |
| ResApi::Delete(gl1, 1, &kDefinitelyInvalidId); | |
| EXPECT_EQ(GL_INVALID_VALUE, CheckError()); | |
| } | |
| // Require that double-deleting IDs produces an error. | |
| template <class ResApi> | |
| void DoubleDeletingIdGeneratesError() { | |
| GLES2Implementation* gl1 = test_contexts_[0].gl_.get(); | |
| GLuint id1; | |
| ResApi::Gen(gl1, 1, &id1); | |
| ResApi::Delete(gl1, 1, &id1); | |
| EXPECT_EQ(GL_NO_ERROR, CheckError()); | |
| ResApi::Delete(gl1, 1, &id1); | |
| EXPECT_EQ(GL_INVALID_VALUE, CheckError()); | |
| } | |
| }; | |
| void GLES2ImplementationStrictSharedTest::SetUp() { | |
| ContextInitOptions init_options; | |
| ASSERT_TRUE(Initialize(init_options)); | |
| } | |
| class GLES3ImplementationTest : public GLES2ImplementationTest { | |
| protected: | |
| void SetUp() override; | |
| }; | |
| void GLES3ImplementationTest::SetUp() { | |
| ContextInitOptions init_options; | |
| init_options.major_version = 3; | |
| init_options.minor_version = 0; | |
| ASSERT_TRUE(Initialize(init_options)); | |
| } | |
| const uint8_t GLES2ImplementationTest::kInitialValue; | |
| const int32_t GLES2ImplementationTest::kNumCommandEntries; | |
| const int32_t GLES2ImplementationTest::kCommandBufferSizeBytes; | |
| const size_t GLES2ImplementationTest::kTransferBufferSize; | |
| const GLint GLES2ImplementationTest::kMaxCombinedTextureImageUnits; | |
| const GLint GLES2ImplementationTest::kMaxCubeMapTextureSize; | |
| const GLint GLES2ImplementationTest::kMaxFragmentUniformVectors; | |
| const GLint GLES2ImplementationTest::kMaxRenderbufferSize; | |
| const GLint GLES2ImplementationTest::kMaxTextureImageUnits; | |
| const GLint GLES2ImplementationTest::kMaxTextureSize; | |
| const GLint GLES2ImplementationTest::kMaxVaryingVectors; | |
| const GLint GLES2ImplementationTest::kMaxVertexAttribs; | |
| const GLint GLES2ImplementationTest::kMaxVertexTextureImageUnits; | |
| const GLint GLES2ImplementationTest::kMaxVertexUniformVectors; | |
| const GLint GLES2ImplementationTest::kNumCompressedTextureFormats; | |
| const GLint GLES2ImplementationTest::kNumShaderBinaryFormats; | |
| const GLuint GLES2ImplementationTest::kStartId; | |
| const GLuint GLES2ImplementationTest::kBuffersStartId; | |
| const GLuint GLES2ImplementationTest::kFramebuffersStartId; | |
| const GLuint GLES2ImplementationTest::kProgramsAndShadersStartId; | |
| const GLuint GLES2ImplementationTest::kRenderbuffersStartId; | |
| const GLuint GLES2ImplementationTest::kSamplersStartId; | |
| const GLuint GLES2ImplementationTest::kTexturesStartId; | |
| const GLuint GLES2ImplementationTest::kTransformFeedbacksStartId; | |
| const GLuint GLES2ImplementationTest::kQueriesStartId; | |
| const GLuint GLES2ImplementationTest::kVertexArraysStartId; | |
| TEST_F(GLES2ImplementationTest, Basic) { | |
| EXPECT_TRUE(gl_->share_group()); | |
| } | |
| TEST_F(GLES2ImplementationTest, GetBucketContents) { | |
| const uint32_t kBucketId = GLES2Implementation::kResultBucketId; | |
| const uint32_t kTestSize = MaxTransferBufferSize() + 32; | |
| auto buf = base::HeapArray<uint8_t>::Uninit(kTestSize); | |
| base::span<uint8_t> expected_data = buf; | |
| for (uint32_t ii = 0; ii < kTestSize; ++ii) { | |
| expected_data[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.span, expected_data, | |
| MaxTransferBufferSize()))) | |
| .WillOnce(SetMemoryFromArray( | |
| mem2.span, expected_data.subspan(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()); | |
| EXPECT_THAT(data, testing::ElementsAreArray(expected_data)); | |
| } | |
| TEST_F(GLES2ImplementationTest, GetShaderPrecisionFormat) { | |
| struct Cmds { | |
| cmds::GetShaderPrecisionFormat cmd; | |
| }; | |
| typedef cmds::GetShaderPrecisionFormat::Result Result; | |
| const unsigned kDummyType1 = 3; | |
| const unsigned kDummyType2 = 4; | |
| // The first call for dummy type 1 should trigger a command buffer request. | |
| GLint range1[2] = {0, 0}; | |
| GLint precision1 = 0; | |
| Cmds expected1; | |
| ExpectedMemoryInfo client_result1 = GetExpectedResultMemory(4); | |
| expected1.cmd.Init(GL_FRAGMENT_SHADER, kDummyType1, client_result1.id, | |
| client_result1.offset); | |
| Result server_result1 = {true, 14, 14, 10}; | |
| EXPECT_CALL(*command_buffer(), OnFlush()) | |
| .WillOnce(SetMemory(client_result1.ptr, server_result1)) | |
| .RetiresOnSaturation(); | |
| gl_->GetShaderPrecisionFormat(GL_FRAGMENT_SHADER, kDummyType1, range1, | |
| &precision1); | |
| const void* commands2 = GetPut(); | |
| EXPECT_NE(commands_, commands2); | |
| UNSAFE_TODO(EXPECT_EQ(0, memcmp(&expected1, commands_, sizeof(expected1)))); | |
| EXPECT_EQ(range1[0], 14); | |
| EXPECT_EQ(range1[1], 14); | |
| EXPECT_EQ(precision1, 10); | |
| // The second call for dummy type 1 should use the cached value and avoid | |
| // triggering a command buffer request, so we do not expect a call to | |
| // OnFlush() here. We do expect the results to be correct though. | |
| GLint range2[2] = {0, 0}; | |
| GLint precision2 = 0; | |
| gl_->GetShaderPrecisionFormat(GL_FRAGMENT_SHADER, kDummyType1, range2, | |
| &precision2); | |
| const void* commands3 = GetPut(); | |
| EXPECT_EQ(commands2, commands3); | |
| EXPECT_EQ(range2[0], 14); | |
| EXPECT_EQ(range2[1], 14); | |
| EXPECT_EQ(precision2, 10); | |
| // If we then make a request for dummy type 2, we should get another command | |
| // buffer request since it hasn't been cached yet. | |
| GLint range3[2] = {0, 0}; | |
| GLint precision3 = 0; | |
| Cmds expected3; | |
| ExpectedMemoryInfo result3 = GetExpectedResultMemory(4); | |
| expected3.cmd.Init(GL_FRAGMENT_SHADER, kDummyType2, result3.id, | |
| result3.offset); | |
| Result result3_source = {true, 62, 62, 16}; | |
| EXPECT_CALL(*command_buffer(), OnFlush()) | |
| .WillOnce(SetMemory(result3.ptr, result3_source)) | |
| .RetiresOnSaturation(); | |
| gl_->GetShaderPrecisionFormat(GL_FRAGMENT_SHADER, kDummyType2, range3, | |
| &precision3); | |
| const void* commands4 = GetPut(); | |
| EXPECT_NE(commands3, commands4); | |
| UNSAFE_TODO(EXPECT_EQ(0, memcmp(&expected3, commands3, sizeof(expected3)))); | |
| EXPECT_EQ(range3[0], 62); | |
| EXPECT_EQ(range3[1], 62); | |
| EXPECT_EQ(precision3, 16); | |
| // Any call for predefined types should use the cached value from the | |
| // Capabilities and avoid triggering a command buffer request, so we do not | |
| // expect a call to OnFlush() here. We do expect the results to be correct | |
| // though. | |
| GLint range4[2] = {0, 0}; | |
| GLint precision4 = 0; | |
| gl_->GetShaderPrecisionFormat(GL_FRAGMENT_SHADER, GL_MEDIUM_FLOAT, range4, | |
| &precision4); | |
| const void* commands5 = GetPut(); | |
| EXPECT_EQ(commands4, commands5); | |
| EXPECT_EQ(range4[0], 3); | |
| EXPECT_EQ(range4[1], 5); | |
| EXPECT_EQ(precision4, 7); | |
| } | |
| TEST_F(GLES2ImplementationTest, GetShaderSource) { | |
| const uint32_t kBucketId = GLES2Implementation::kResultBucketId; | |
| const GLuint kShaderId = 456; | |
| const Str7 kString = {"foobar"}; | |
| const char kBad = 0x12; | |
| struct Cmds { | |
| cmd::SetBucketSize set_bucket_size1; | |
| cmds::GetShaderSource get_shader_source; | |
| cmd::GetBucketStart get_bucket_start; | |
| cmd::SetToken set_token1; | |
| cmd::SetBucketSize set_bucket_size2; | |
| }; | |
| ExpectedMemoryInfo mem1 = GetExpectedMemory(MaxTransferBufferSize()); | |
| ExpectedMemoryInfo result1 = GetExpectedResultMemory(sizeof(uint32_t)); | |
| Cmds expected; | |
| expected.set_bucket_size1.Init(kBucketId, 0); | |
| expected.get_shader_source.Init(kShaderId, kBucketId); | |
| expected.get_bucket_start.Init( | |
| kBucketId, result1.id, result1.offset, | |
| MaxTransferBufferSize(), mem1.id, mem1.offset); | |
| expected.set_token1.Init(GetNextToken()); | |
| expected.set_bucket_size2.Init(kBucketId, 0); | |
| char buf[sizeof(kString) + 1]; | |
| UNSAFE_TODO(memset(buf, kBad, sizeof(buf))); | |
| EXPECT_CALL(*command_buffer(), OnFlush()) | |
| .WillOnce(DoAll(SetMemory(result1.ptr, uint32_t(sizeof(kString))), | |
| SetMemory(mem1.ptr, kString))) | |
| .RetiresOnSaturation(); | |
| GLsizei length = 0; | |
| gl_->GetShaderSource(kShaderId, sizeof(buf), &length, buf); | |
| UNSAFE_TODO(EXPECT_EQ(0, memcmp(&expected, commands_, sizeof(expected)))); | |
| EXPECT_EQ(sizeof(kString) - 1, static_cast<size_t>(length)); | |
| EXPECT_STREQ(kString.str, buf); | |
| EXPECT_EQ(buf[sizeof(kString)], kBad); | |
| } | |
| // Check that for GETn functions (like glShaderiv) the writes to the result | |
| // pointer are clamped even if the service side returns more data. See | |
| // http://issues.chromium.org/issues/551593376. | |
| TEST_F(GLES2ImplementationTest, GetShaderiv_OversizeReturn) { | |
| // Make the SizedResult hold two GLints instead of the single one for | |
| // GL_SHADER_TYPE. | |
| struct TwoInts { | |
| GLint first = 0xBEEF; | |
| GLint second = 0xCAFE; | |
| }; | |
| ExpectedMemoryInfo resultBuffer = | |
| GetExpectedResultMemory(sizeof(uint32_t) + sizeof(TwoInts)); | |
| EXPECT_CALL(*command_buffer(), OnFlush()) | |
| .WillOnce(SetMemory(resultBuffer.ptr, SizedResultHelper<TwoInts>({}))) | |
| .RetiresOnSaturation(); | |
| std::array<GLint, 2> resultAndGuard = {{0, 0}}; | |
| gl_->GetShaderiv(123, GL_SHADER_TYPE, resultAndGuard.data()); | |
| // Only the first GLint should be written to the output. | |
| EXPECT_EQ(0xBEEF, resultAndGuard[0]); | |
| EXPECT_EQ(0, resultAndGuard[1]); | |
| } | |
| TEST_F(GLES2ImplementationTest, ReadPixels2Reads) { | |
| struct Cmds { | |
| cmds::ReadPixels read1; | |
| cmd::SetToken set_token1; | |
| cmds::ReadPixels read2; | |
| cmd::SetToken set_token2; | |
| }; | |
| const GLint kBytesPerPixel = 4; | |
| const GLint kWidth = | |
| (kTransferBufferSize - GLES2Implementation::kStartingOffset) / | |
| kBytesPerPixel; | |
| const GLint kHeight = 2; | |
| const GLenum kFormat = GL_RGBA; | |
| const GLenum kType = GL_UNSIGNED_BYTE; | |
| ExpectedMemoryInfo mem1 = | |
| GetExpectedMemory(kWidth * kHeight / 2 * kBytesPerPixel); | |
| ExpectedMemoryInfo result1 = | |
| GetExpectedResultMemory(sizeof(cmds::ReadPixels::Result)); | |
| ExpectedMemoryInfo mem2 = | |
| GetExpectedMemory(kWidth * kHeight / 2 * kBytesPerPixel); | |
| ExpectedMemoryInfo result2 = | |
| GetExpectedResultMemory(sizeof(cmds::ReadPixels::Result)); | |
| Cmds expected; | |
| expected.read1.Init( | |
| 0, 0, kWidth, kHeight / 2, kFormat, kType, | |
| mem1.id, mem1.offset, result1.id, result1.offset, | |
| false); | |
| expected.set_token1.Init(GetNextToken()); | |
| expected.read2.Init( | |
| 0, kHeight / 2, kWidth, kHeight / 2, kFormat, kType, | |
| mem2.id, mem2.offset, result2.id, result2.offset, false); | |
| expected.set_token2.Init(GetNextToken()); | |
| auto buffer = | |
| base::HeapArray<int8_t>::Uninit(kWidth * kHeight * kBytesPerPixel); | |
| EXPECT_CALL(*command_buffer(), OnFlush()) | |
| .WillOnce(SetMemory(result1.ptr, static_cast<uint32_t>(1))) | |
| .WillOnce(SetMemory(result2.ptr, static_cast<uint32_t>(1))) | |
| .RetiresOnSaturation(); | |
| gl_->ReadPixels(0, 0, kWidth, kHeight, kFormat, kType, buffer.data()); | |
| UNSAFE_TODO(EXPECT_EQ(0, memcmp(&expected, commands_, sizeof(expected)))); | |
| } | |
| TEST_F(GLES2ImplementationTest, ReadPixelsBadFormatType) { | |
| struct Cmds { | |
| cmds::ReadPixels read; | |
| cmd::SetToken set_token; | |
| }; | |
| const GLint kBytesPerPixel = 4; | |
| const GLint kWidth = 2; | |
| const GLint kHeight = 2; | |
| const GLenum kFormat = 0; | |
| const GLenum kType = 0; | |
| ExpectedMemoryInfo mem1 = | |
| GetExpectedMemory(kWidth * kHeight * kBytesPerPixel); | |
| ExpectedMemoryInfo result1 = | |
| GetExpectedResultMemory(sizeof(cmds::ReadPixels::Result)); | |
| Cmds expected; | |
| expected.read.Init( | |
| 0, 0, kWidth, kHeight, kFormat, kType, | |
| mem1.id, mem1.offset, result1.id, result1.offset, false); | |
| expected.set_token.Init(GetNextToken()); | |
| auto buffer = | |
| base::HeapArray<int8_t>::Uninit(kWidth * kHeight * kBytesPerPixel); | |
| EXPECT_CALL(*command_buffer(), OnFlush()) | |
| .Times(1) | |
| .RetiresOnSaturation(); | |
| gl_->ReadPixels(0, 0, kWidth, kHeight, kFormat, kType, buffer.data()); | |
| } | |
| TEST_F(GLES2ImplementationTest, FreeUnusedSharedMemory) { | |
| struct Cmds { | |
| cmds::BufferSubData buf; | |
| cmd::SetToken set_token; | |
| }; | |
| const GLenum kTarget = GL_ELEMENT_ARRAY_BUFFER; | |
| const GLintptr kOffset = 15; | |
| const GLsizeiptr kSize = 16; | |
| ExpectedMemoryInfo mem1 = GetExpectedMemory(kSize); | |
| Cmds expected; | |
| expected.buf.Init( | |
| kTarget, kOffset, kSize, mem1.id, mem1.offset); | |
| expected.set_token.Init(GetNextToken()); | |
| void* mem = gl_->MapBufferSubDataCHROMIUM( | |
| kTarget, kOffset, kSize, GL_WRITE_ONLY); | |
| ASSERT_TRUE(mem != nullptr); | |
| gl_->UnmapBufferSubDataCHROMIUM(mem); | |
| EXPECT_CALL(*command_buffer(), DestroyTransferBuffer(_)) | |
| .Times(1) | |
| .RetiresOnSaturation(); | |
| gl_->FreeUnusedSharedMemory(); | |
| } | |
| TEST_F(GLES2ImplementationTest, MapUnmapBufferSubDataCHROMIUM) { | |
| struct Cmds { | |
| cmds::BufferSubData buf; | |
| cmd::SetToken set_token; | |
| }; | |
| const GLenum kTarget = GL_ELEMENT_ARRAY_BUFFER; | |
| const GLintptr kOffset = 15; | |
| const GLsizeiptr kSize = 16; | |
| uint32_t offset = 0; | |
| Cmds expected; | |
| expected.buf.Init( | |
| kTarget, kOffset, kSize, | |
| command_buffer()->GetNextFreeTransferBufferId(), offset); | |
| expected.set_token.Init(GetNextToken()); | |
| void* mem = gl_->MapBufferSubDataCHROMIUM( | |
| kTarget, kOffset, kSize, GL_WRITE_ONLY); | |
| ASSERT_TRUE(mem != nullptr); | |
| gl_->UnmapBufferSubDataCHROMIUM(mem); | |
| UNSAFE_TODO(EXPECT_EQ(0, memcmp(&expected, commands_, sizeof(expected)))); | |
| } | |
| TEST_F(GLES2ImplementationTest, MapUnmapBufferSubDataCHROMIUMBadArgs) { | |
| const GLenum kTarget = GL_ELEMENT_ARRAY_BUFFER; | |
| const GLintptr kOffset = 15; | |
| const GLsizeiptr kSize = 16; | |
| ExpectedMemoryInfo result1 = | |
| GetExpectedResultMemory(sizeof(cmds::GetError::Result)); | |
| ExpectedMemoryInfo result2 = | |
| GetExpectedResultMemory(sizeof(cmds::GetError::Result)); | |
| ExpectedMemoryInfo result3 = | |
| GetExpectedResultMemory(sizeof(cmds::GetError::Result)); | |
| ExpectedMemoryInfo result4 = | |
| GetExpectedResultMemory(sizeof(cmds::GetError::Result)); | |
| // Calls to flush to wait for GetError | |
| EXPECT_CALL(*command_buffer(), OnFlush()) | |
| .WillOnce(SetMemory(result1.ptr, GLuint(GL_NO_ERROR))) | |
| .WillOnce(SetMemory(result2.ptr, GLuint(GL_NO_ERROR))) | |
| .WillOnce(SetMemory(result3.ptr, GLuint(GL_NO_ERROR))) | |
| .WillOnce(SetMemory(result4.ptr, GLuint(GL_NO_ERROR))) | |
| .RetiresOnSaturation(); | |
| void* mem; | |
| mem = gl_->MapBufferSubDataCHROMIUM(kTarget, -1, kSize, GL_WRITE_ONLY); | |
| ASSERT_TRUE(mem == nullptr); | |
| EXPECT_EQ(static_cast<GLenum>(GL_INVALID_VALUE), gl_->GetError()); | |
| mem = gl_->MapBufferSubDataCHROMIUM(kTarget, kOffset, -1, GL_WRITE_ONLY); | |
| ASSERT_TRUE(mem == nullptr); | |
| EXPECT_EQ(static_cast<GLenum>(GL_INVALID_VALUE), gl_->GetError()); | |
| mem = gl_->MapBufferSubDataCHROMIUM(kTarget, kOffset, kSize, GL_READ_ONLY); | |
| ASSERT_TRUE(mem == nullptr); | |
| EXPECT_EQ(static_cast<GLenum>(GL_INVALID_ENUM), gl_->GetError()); | |
| const char* kPtr = "something"; | |
| gl_->UnmapBufferSubDataCHROMIUM(kPtr); | |
| EXPECT_EQ(static_cast<GLenum>(GL_INVALID_VALUE), gl_->GetError()); | |
| } | |
| TEST_F(GLES2ImplementationTest, MapUnmapTexSubImage2DCHROMIUM) { | |
| struct Cmds { | |
| cmds::TexSubImage2D tex; | |
| cmd::SetToken set_token; | |
| }; | |
| const GLint kLevel = 1; | |
| const GLint kXOffset = 2; | |
| const GLint kYOffset = 3; | |
| const GLint kWidth = 4; | |
| const GLint kHeight = 5; | |
| const GLenum kFormat = GL_RGBA; | |
| const GLenum kType = GL_UNSIGNED_BYTE; | |
| uint32_t offset = 0; | |
| Cmds expected; | |
| expected.tex.Init( | |
| GL_TEXTURE_2D, kLevel, kXOffset, kYOffset, kWidth, kHeight, kFormat, | |
| kType, | |
| command_buffer()->GetNextFreeTransferBufferId(), offset, GL_FALSE); | |
| expected.set_token.Init(GetNextToken()); | |
| void* mem = gl_->MapTexSubImage2DCHROMIUM( | |
| GL_TEXTURE_2D, | |
| kLevel, | |
| kXOffset, | |
| kYOffset, | |
| kWidth, | |
| kHeight, | |
| kFormat, | |
| kType, | |
| GL_WRITE_ONLY); | |
| ASSERT_TRUE(mem != nullptr); | |
| gl_->UnmapTexSubImage2DCHROMIUM(mem); | |
| UNSAFE_TODO(EXPECT_EQ(0, memcmp(&expected, commands_, sizeof(expected)))); | |
| } | |
| TEST_F(GLES2ImplementationTest, MapUnmapTexSubImage2DCHROMIUMBadArgs) { | |
| const GLint kLevel = 1; | |
| const GLint kXOffset = 2; | |
| const GLint kYOffset = 3; | |
| const GLint kWidth = 4; | |
| const GLint kHeight = 5; | |
| const GLenum kFormat = GL_RGBA; | |
| const GLenum kType = GL_UNSIGNED_BYTE; | |
| ExpectedMemoryInfo result1 = | |
| GetExpectedResultMemory(sizeof(cmds::GetError::Result)); | |
| ExpectedMemoryInfo result2 = | |
| GetExpectedResultMemory(sizeof(cmds::GetError::Result)); | |
| ExpectedMemoryInfo result3 = | |
| GetExpectedResultMemory(sizeof(cmds::GetError::Result)); | |
| ExpectedMemoryInfo result4 = | |
| GetExpectedResultMemory(sizeof(cmds::GetError::Result)); | |
| ExpectedMemoryInfo result5 = | |
| GetExpectedResultMemory(sizeof(cmds::GetError::Result)); | |
| ExpectedMemoryInfo result6 = | |
| GetExpectedResultMemory(sizeof(cmds::GetError::Result)); | |
| ExpectedMemoryInfo result7 = | |
| GetExpectedResultMemory(sizeof(cmds::GetError::Result)); | |
| // Calls to flush to wait for GetError | |
| EXPECT_CALL(*command_buffer(), OnFlush()) | |
| .WillOnce(SetMemory(result1.ptr, GLuint(GL_NO_ERROR))) | |
| .WillOnce(SetMemory(result2.ptr, GLuint(GL_NO_ERROR))) | |
| .WillOnce(SetMemory(result3.ptr, GLuint(GL_NO_ERROR))) | |
| .WillOnce(SetMemory(result4.ptr, GLuint(GL_NO_ERROR))) | |
| .WillOnce(SetMemory(result5.ptr, GLuint(GL_NO_ERROR))) | |
| .WillOnce(SetMemory(result6.ptr, GLuint(GL_NO_ERROR))) | |
| .WillOnce(SetMemory(result7.ptr, GLuint(GL_NO_ERROR))) | |
| .RetiresOnSaturation(); | |
| void* mem; | |
| mem = gl_->MapTexSubImage2DCHROMIUM( | |
| GL_TEXTURE_2D, | |
| -1, | |
| kXOffset, | |
| kYOffset, | |
| kWidth, | |
| kHeight, | |
| kFormat, | |
| kType, | |
| GL_WRITE_ONLY); | |
| EXPECT_TRUE(mem == nullptr); | |
| EXPECT_EQ(static_cast<GLenum>(GL_INVALID_VALUE), gl_->GetError()); | |
| mem = gl_->MapTexSubImage2DCHROMIUM( | |
| GL_TEXTURE_2D, | |
| kLevel, | |
| -1, | |
| kYOffset, | |
| kWidth, | |
| kHeight, | |
| kFormat, | |
| kType, | |
| GL_WRITE_ONLY); | |
| EXPECT_TRUE(mem == nullptr); | |
| EXPECT_EQ(static_cast<GLenum>(GL_INVALID_VALUE), gl_->GetError()); | |
| mem = gl_->MapTexSubImage2DCHROMIUM( | |
| GL_TEXTURE_2D, | |
| kLevel, | |
| kXOffset, | |
| -1, | |
| kWidth, | |
| kHeight, | |
| kFormat, | |
| kType, | |
| GL_WRITE_ONLY); | |
| EXPECT_TRUE(mem == nullptr); | |
| EXPECT_EQ(static_cast<GLenum>(GL_INVALID_VALUE), gl_->GetError()); | |
| mem = gl_->MapTexSubImage2DCHROMIUM( | |
| GL_TEXTURE_2D, | |
| kLevel, | |
| kXOffset, | |
| kYOffset, | |
| -1, | |
| kHeight, | |
| kFormat, | |
| kType, | |
| GL_WRITE_ONLY); | |
| EXPECT_TRUE(mem == nullptr); | |
| EXPECT_EQ(static_cast<GLenum>(GL_INVALID_VALUE), gl_->GetError()); | |
| mem = gl_->MapTexSubImage2DCHROMIUM( | |
| GL_TEXTURE_2D, | |
| kLevel, | |
| kXOffset, | |
| kYOffset, | |
| kWidth, | |
| -1, | |
| kFormat, | |
| kType, | |
| GL_WRITE_ONLY); | |
| EXPECT_TRUE(mem == nullptr); | |
| EXPECT_EQ(static_cast<GLenum>(GL_INVALID_VALUE), gl_->GetError()); | |
| mem = gl_->MapTexSubImage2DCHROMIUM( | |
| GL_TEXTURE_2D, | |
| kLevel, | |
| kXOffset, | |
| kYOffset, | |
| kWidth, | |
| kHeight, | |
| kFormat, | |
| kType, | |
| GL_READ_ONLY); | |
| EXPECT_TRUE(mem == nullptr); | |
| EXPECT_EQ(static_cast<GLenum>(GL_INVALID_ENUM), gl_->GetError()); | |
| const char* kPtr = "something"; | |
| gl_->UnmapTexSubImage2DCHROMIUM(kPtr); | |
| EXPECT_EQ(static_cast<GLenum>(GL_INVALID_VALUE), gl_->GetError()); | |
| } | |
| TEST_F(GLES2ImplementationTest, GetProgramInfoCHROMIUMGoodArgs) { | |
| const uint32_t kBucketId = GLES2Implementation::kResultBucketId; | |
| const GLuint kProgramId = 123; | |
| const char kBad = 0x12; | |
| GLsizei size = 0; | |
| const Str7 kString = {"foobar"}; | |
| char buf[20]; | |
| ExpectedMemoryInfo mem1 = | |
| GetExpectedMemory(MaxTransferBufferSize()); | |
| ExpectedMemoryInfo result1 = | |
| GetExpectedResultMemory(sizeof(cmd::GetBucketStart::Result)); | |
| ExpectedMemoryInfo result2 = | |
| GetExpectedResultMemory(sizeof(cmds::GetError::Result)); | |
| UNSAFE_TODO(memset(buf, kBad, sizeof(buf))); | |
| EXPECT_CALL(*command_buffer(), OnFlush()) | |
| .WillOnce(DoAll(SetMemory(result1.ptr, uint32_t(sizeof(kString))), | |
| SetMemory(mem1.ptr, kString))) | |
| .WillOnce(SetMemory(result2.ptr, GLuint(GL_NO_ERROR))) | |
| .RetiresOnSaturation(); | |
| struct Cmds { | |
| cmd::SetBucketSize set_bucket_size1; | |
| cmds::GetProgramInfoCHROMIUM get_program_info; | |
| cmd::GetBucketStart get_bucket_start; | |
| cmd::SetToken set_token1; | |
| cmd::SetBucketSize set_bucket_size2; | |
| }; | |
| Cmds expected; | |
| expected.set_bucket_size1.Init(kBucketId, 0); | |
| expected.get_program_info.Init(kProgramId, kBucketId); | |
| expected.get_bucket_start.Init( | |
| kBucketId, result1.id, result1.offset, | |
| MaxTransferBufferSize(), mem1.id, mem1.offset); | |
| expected.set_token1.Init(GetNextToken()); | |
| expected.set_bucket_size2.Init(kBucketId, 0); | |
| gl_->GetProgramInfoCHROMIUM(kProgramId, sizeof(buf), &size, &buf); | |
| UNSAFE_TODO(EXPECT_EQ(0, memcmp(&expected, commands_, sizeof(expected)))); | |
| EXPECT_EQ(static_cast<GLenum>(GL_NO_ERROR), gl_->GetError()); | |
| EXPECT_EQ(sizeof(kString), static_cast<size_t>(size)); | |
| EXPECT_STREQ(kString.str, buf); | |
| EXPECT_EQ(buf[sizeof(kString)], kBad); | |
| } | |
| TEST_F(GLES2ImplementationTest, GetProgramInfoCHROMIUMBadArgs) { | |
| const uint32_t kBucketId = GLES2Implementation::kResultBucketId; | |
| const GLuint kProgramId = 123; | |
| GLsizei size = 0; | |
| const Str7 kString = {"foobar"}; | |
| char buf[20]; | |
| ExpectedMemoryInfo mem1 = GetExpectedMemory(MaxTransferBufferSize()); | |
| ExpectedMemoryInfo result1 = | |
| GetExpectedResultMemory(sizeof(cmd::GetBucketStart::Result)); | |
| ExpectedMemoryInfo result2 = | |
| GetExpectedResultMemory(sizeof(cmds::GetError::Result)); | |
| ExpectedMemoryInfo result3 = | |
| GetExpectedResultMemory(sizeof(cmds::GetError::Result)); | |
| ExpectedMemoryInfo result4 = | |
| GetExpectedResultMemory(sizeof(cmds::GetError::Result)); | |
| EXPECT_CALL(*command_buffer(), OnFlush()) | |
| .WillOnce(DoAll(SetMemory(result1.ptr, uint32_t(sizeof(kString))), | |
| SetMemory(mem1.ptr, kString))) | |
| .WillOnce(SetMemory(result2.ptr, GLuint(GL_NO_ERROR))) | |
| .WillOnce(SetMemory(result3.ptr, GLuint(GL_NO_ERROR))) | |
| .WillOnce(SetMemory(result4.ptr, GLuint(GL_NO_ERROR))) | |
| .RetiresOnSaturation(); | |
| // try bufsize not big enough. | |
| struct Cmds { | |
| cmd::SetBucketSize set_bucket_size1; | |
| cmds::GetProgramInfoCHROMIUM get_program_info; | |
| cmd::GetBucketStart get_bucket_start; | |
| cmd::SetToken set_token1; | |
| cmd::SetBucketSize set_bucket_size2; | |
| }; | |
| Cmds expected; | |
| expected.set_bucket_size1.Init(kBucketId, 0); | |
| expected.get_program_info.Init(kProgramId, kBucketId); | |
| expected.get_bucket_start.Init( | |
| kBucketId, result1.id, result1.offset, | |
| MaxTransferBufferSize(), mem1.id, mem1.offset); | |
| expected.set_token1.Init(GetNextToken()); | |
| expected.set_bucket_size2.Init(kBucketId, 0); | |
| gl_->GetProgramInfoCHROMIUM(kProgramId, 6, &size, &buf); | |
| UNSAFE_TODO(EXPECT_EQ(0, memcmp(&expected, commands_, sizeof(expected)))); | |
| EXPECT_EQ(static_cast<GLenum>(GL_INVALID_OPERATION), gl_->GetError()); | |
| ClearCommands(); | |
| // try bad bufsize | |
| gl_->GetProgramInfoCHROMIUM(kProgramId, -1, &size, &buf); | |
| EXPECT_TRUE(NoCommandsWritten()); | |
| EXPECT_EQ(static_cast<GLenum>(GL_INVALID_VALUE), gl_->GetError()); | |
| ClearCommands(); | |
| // try no size ptr. | |
| gl_->GetProgramInfoCHROMIUM(kProgramId, sizeof(buf), nullptr, &buf); | |
| EXPECT_TRUE(NoCommandsWritten()); | |
| EXPECT_EQ(static_cast<GLenum>(GL_INVALID_VALUE), gl_->GetError()); | |
| } | |
| TEST_F(GLES2ImplementationTest, GetUniformBlocksCHROMIUMGoodArgs) { | |
| const uint32_t kBucketId = GLES2Implementation::kResultBucketId; | |
| const GLuint kProgramId = 123; | |
| const char kBad = 0x12; | |
| GLsizei size = 0; | |
| const Str7 kString = {"foobar"}; | |
| char buf[20]; | |
| ExpectedMemoryInfo mem1 = | |
| GetExpectedMemory(MaxTransferBufferSize()); | |
| ExpectedMemoryInfo result1 = | |
| GetExpectedResultMemory(sizeof(cmd::GetBucketStart::Result)); | |
| ExpectedMemoryInfo result2 = | |
| GetExpectedResultMemory(sizeof(cmds::GetError::Result)); | |
| UNSAFE_TODO(memset(buf, kBad, sizeof(buf))); | |
| EXPECT_CALL(*command_buffer(), OnFlush()) | |
| .WillOnce(DoAll(SetMemory(result1.ptr, uint32_t(sizeof(kString))), | |
| SetMemory(mem1.ptr, kString))) | |
| .WillOnce(SetMemory(result2.ptr, GLuint(GL_NO_ERROR))) | |
| .RetiresOnSaturation(); | |
| struct Cmds { | |
| cmd::SetBucketSize set_bucket_size1; | |
| cmds::GetUniformBlocksCHROMIUM get_uniform_blocks; | |
| cmd::GetBucketStart get_bucket_start; | |
| cmd::SetToken set_token1; | |
| cmd::SetBucketSize set_bucket_size2; | |
| }; | |
| Cmds expected; | |
| expected.set_bucket_size1.Init(kBucketId, 0); | |
| expected.get_uniform_blocks.Init(kProgramId, kBucketId); | |
| expected.get_bucket_start.Init( | |
| kBucketId, result1.id, result1.offset, | |
| MaxTransferBufferSize(), mem1.id, mem1.offset); | |
| expected.set_token1.Init(GetNextToken()); | |
| expected.set_bucket_size2.Init(kBucketId, 0); | |
| gl_->GetUniformBlocksCHROMIUM(kProgramId, sizeof(buf), &size, &buf); | |
| UNSAFE_TODO(EXPECT_EQ(0, memcmp(&expected, commands_, sizeof(expected)))); | |
| EXPECT_EQ(static_cast<GLenum>(GL_NO_ERROR), gl_->GetError()); | |
| EXPECT_EQ(sizeof(kString), static_cast<size_t>(size)); | |
| EXPECT_STREQ(kString.str, buf); | |
| EXPECT_EQ(buf[sizeof(kString)], kBad); | |
| } | |
| TEST_F(GLES2ImplementationTest, GetUniformBlocksCHROMIUMBadArgs) { | |
| const uint32_t kBucketId = GLES2Implementation::kResultBucketId; | |
| const GLuint kProgramId = 123; | |
| GLsizei size = 0; | |
| const Str7 kString = {"foobar"}; | |
| char buf[20]; | |
| ExpectedMemoryInfo mem1 = GetExpectedMemory(MaxTransferBufferSize()); | |
| ExpectedMemoryInfo result1 = | |
| GetExpectedResultMemory(sizeof(cmd::GetBucketStart::Result)); | |
| ExpectedMemoryInfo result2 = | |
| GetExpectedResultMemory(sizeof(cmds::GetError::Result)); | |
| ExpectedMemoryInfo result3 = | |
| GetExpectedResultMemory(sizeof(cmds::GetError::Result)); | |
| ExpectedMemoryInfo result4 = | |
| GetExpectedResultMemory(sizeof(cmds::GetError::Result)); | |
| EXPECT_CALL(*command_buffer(), OnFlush()) | |
| .WillOnce(DoAll(SetMemory(result1.ptr, uint32_t(sizeof(kString))), | |
| SetMemory(mem1.ptr, kString))) | |
| .WillOnce(SetMemory(result2.ptr, GLuint(GL_NO_ERROR))) | |
| .WillOnce(SetMemory(result3.ptr, GLuint(GL_NO_ERROR))) | |
| .WillOnce(SetMemory(result4.ptr, GLuint(GL_NO_ERROR))) | |
| .RetiresOnSaturation(); | |
| // try bufsize not big enough. | |
| struct Cmds { | |
| cmd::SetBucketSize set_bucket_size1; | |
| cmds::GetUniformBlocksCHROMIUM get_uniform_blocks; | |
| cmd::GetBucketStart get_bucket_start; | |
| cmd::SetToken set_token1; | |
| cmd::SetBucketSize set_bucket_size2; | |
| }; | |
| Cmds expected; | |
| expected.set_bucket_size1.Init(kBucketId, 0); | |
| expected.get_uniform_blocks.Init(kProgramId, kBucketId); | |
| expected.get_bucket_start.Init( | |
| kBucketId, result1.id, result1.offset, | |
| MaxTransferBufferSize(), mem1.id, mem1.offset); | |
| expected.set_token1.Init(GetNextToken()); | |
| expected.set_bucket_size2.Init(kBucketId, 0); | |
| gl_->GetUniformBlocksCHROMIUM(kProgramId, 6, &size, &buf); | |
| UNSAFE_TODO(EXPECT_EQ(0, memcmp(&expected, commands_, sizeof(expected)))); | |
| EXPECT_EQ(static_cast<GLenum>(GL_INVALID_OPERATION), gl_->GetError()); | |
| ClearCommands(); | |
| // try bad bufsize | |
| gl_->GetUniformBlocksCHROMIUM(kProgramId, -1, &size, &buf); | |
| EXPECT_TRUE(NoCommandsWritten()); | |
| EXPECT_EQ(static_cast<GLenum>(GL_INVALID_VALUE), gl_->GetError()); | |
| ClearCommands(); | |
| // try no size ptr. | |
| gl_->GetUniformBlocksCHROMIUM(kProgramId, sizeof(buf), nullptr, &buf); | |
| EXPECT_TRUE(NoCommandsWritten()); | |
| EXPECT_EQ(static_cast<GLenum>(GL_INVALID_VALUE), gl_->GetError()); | |
| } | |
| // Test that things are cached | |
| TEST_F(GLES2ImplementationTest, GetIntegerCacheRead) { | |
| struct PNameValue { | |
| GLenum pname; | |
| GLint expected; | |
| }; | |
| const auto pairs = std::to_array<PNameValue>({ | |
| { | |
| GL_ACTIVE_TEXTURE, | |
| GL_TEXTURE0, | |
| }, | |
| { | |
| GL_TEXTURE_BINDING_2D, | |
| 0, | |
| }, | |
| { | |
| GL_TEXTURE_BINDING_CUBE_MAP, | |
| 0, | |
| }, | |
| { | |
| GL_TEXTURE_BINDING_EXTERNAL_OES, | |
| 0, | |
| }, | |
| { | |
| GL_FRAMEBUFFER_BINDING, | |
| 0, | |
| }, | |
| { | |
| GL_RENDERBUFFER_BINDING, | |
| 0, | |
| }, | |
| { | |
| GL_ARRAY_BUFFER_BINDING, | |
| 0, | |
| }, | |
| { | |
| GL_ELEMENT_ARRAY_BUFFER_BINDING, | |
| 0, | |
| }, | |
| { | |
| GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS, | |
| kMaxCombinedTextureImageUnits, | |
| }, | |
| { | |
| GL_MAX_CUBE_MAP_TEXTURE_SIZE, | |
| kMaxCubeMapTextureSize, | |
| }, | |
| { | |
| GL_MAX_FRAGMENT_UNIFORM_VECTORS, | |
| kMaxFragmentUniformVectors, | |
| }, | |
| { | |
| GL_MAX_RENDERBUFFER_SIZE, | |
| kMaxRenderbufferSize, | |
| }, | |
| { | |
| GL_MAX_TEXTURE_IMAGE_UNITS, | |
| kMaxTextureImageUnits, | |
| }, | |
| { | |
| GL_MAX_TEXTURE_SIZE, | |
| kMaxTextureSize, | |
| }, | |
| { | |
| GL_MAX_VARYING_VECTORS, | |
| kMaxVaryingVectors, | |
| }, | |
| { | |
| GL_MAX_VERTEX_ATTRIBS, | |
| kMaxVertexAttribs, | |
| }, | |
| { | |
| GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS, | |
| kMaxVertexTextureImageUnits, | |
| }, | |
| { | |
| GL_MAX_VERTEX_UNIFORM_VECTORS, | |
| kMaxVertexUniformVectors, | |
| }, | |
| { | |
| GL_NUM_COMPRESSED_TEXTURE_FORMATS, | |
| kNumCompressedTextureFormats, | |
| }, | |
| { | |
| GL_NUM_SHADER_BINARY_FORMATS, | |
| kNumShaderBinaryFormats, | |
| }, | |
| }); | |
| size_t num_pairs = | |
| (pairs.size() * sizeof(decltype(pairs)::value_type)) / sizeof(pairs[0]); | |
| for (size_t ii = 0; ii < num_pairs; ++ii) { | |
| const PNameValue& pv = pairs[ii]; | |
| GLint v = -1; | |
| gl_->GetIntegerv(pv.pname, &v); | |
| EXPECT_TRUE(NoCommandsWritten()); | |
| EXPECT_EQ(pv.expected, v); | |
| } | |
| ExpectedMemoryInfo result1 = | |
| GetExpectedResultMemory(sizeof(cmds::GetError::Result)); | |
| EXPECT_CALL(*command_buffer(), OnFlush()) | |
| .WillOnce(SetMemory(result1.ptr, GLuint(GL_NO_ERROR))) | |
| .RetiresOnSaturation(); | |
| EXPECT_EQ(static_cast<GLenum>(GL_NO_ERROR), gl_->GetError()); | |
| } | |
| TEST_F(GLES2ImplementationTest, GetIntegerDisjointValue) { | |
| ExpectedMemoryInfo mem = GetExpectedMappedMemory(sizeof(DisjointValueSync)); | |
| gl_->SetDisjointValueSyncCHROMIUM(); | |
| ASSERT_EQ(mem.id, GetQueryTracker()->DisjointCountSyncShmID()); | |
| ASSERT_EQ(mem.offset, GetQueryTracker()->DisjointCountSyncShmOffset()); | |
| DisjointValueSync* disjoint_sync = | |
| reinterpret_cast<DisjointValueSync*>(mem.ptr); | |
| ClearCommands(); | |
| GLint disjoint_value = -1; | |
| gl_->GetIntegerv(GL_GPU_DISJOINT_EXT, &disjoint_value); | |
| EXPECT_TRUE(NoCommandsWritten()); | |
| EXPECT_EQ(0, disjoint_value); | |
| // After setting disjoint, it should be true. | |
| disjoint_value = -1; | |
| disjoint_sync->SetDisjointCount(1); | |
| gl_->GetIntegerv(GL_GPU_DISJOINT_EXT, &disjoint_value); | |
| EXPECT_TRUE(NoCommandsWritten()); | |
| EXPECT_EQ(1, disjoint_value); | |
| // After checking disjoint, it should be false again. | |
| disjoint_value = -1; | |
| gl_->GetIntegerv(GL_GPU_DISJOINT_EXT, &disjoint_value); | |
| EXPECT_TRUE(NoCommandsWritten()); | |
| EXPECT_EQ(0, disjoint_value); | |
| // Check for errors. | |
| ExpectedMemoryInfo result1 = | |
| GetExpectedResultMemory(sizeof(cmds::GetError::Result)); | |
| EXPECT_CALL(*command_buffer(), OnFlush()) | |
| .WillOnce(SetMemory(result1.ptr, GLuint(GL_NO_ERROR))) | |
| .RetiresOnSaturation(); | |
| EXPECT_EQ(static_cast<GLenum>(GL_NO_ERROR), gl_->GetError()); | |
| } | |
| TEST_F(GLES2ImplementationTest, GetIntegerCacheWrite) { | |
| struct PNameValue { | |
| GLenum pname; | |
| GLuint expected; | |
| }; | |
| GLuint buffer_ids[2]; | |
| gl_->GenBuffers(std::size(buffer_ids), buffer_ids); | |
| GLuint framebuffer_id; | |
| gl_->GenFramebuffers(1, &framebuffer_id); | |
| GLuint renderbuffer_id; | |
| gl_->GenRenderbuffers(1, &renderbuffer_id); | |
| GLuint texture_ids[3]; | |
| gl_->GenTextures(std::size(texture_ids), texture_ids); | |
| gl_->ActiveTexture(GL_TEXTURE4); | |
| gl_->BindBuffer(GL_ARRAY_BUFFER, buffer_ids[0]); | |
| gl_->BindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffer_ids[1]); | |
| gl_->BindFramebuffer(GL_FRAMEBUFFER, framebuffer_id); | |
| gl_->BindRenderbuffer(GL_RENDERBUFFER, renderbuffer_id); | |
| gl_->BindTexture(GL_TEXTURE_2D, texture_ids[0]); | |
| gl_->BindTexture(GL_TEXTURE_CUBE_MAP, texture_ids[1]); | |
| gl_->BindTexture(GL_TEXTURE_EXTERNAL_OES, texture_ids[2]); | |
| const auto pairs = std::to_array<PNameValue>({ | |
| { | |
| GL_ACTIVE_TEXTURE, | |
| GL_TEXTURE4, | |
| }, | |
| { | |
| GL_ARRAY_BUFFER_BINDING, | |
| buffer_ids[0], | |
| }, | |
| { | |
| GL_ELEMENT_ARRAY_BUFFER_BINDING, | |
| buffer_ids[1], | |
| }, | |
| { | |
| GL_FRAMEBUFFER_BINDING, | |
| framebuffer_id, | |
| }, | |
| { | |
| GL_RENDERBUFFER_BINDING, | |
| renderbuffer_id, | |
| }, | |
| { | |
| GL_TEXTURE_BINDING_2D, | |
| texture_ids[0], | |
| }, | |
| { | |
| GL_TEXTURE_BINDING_CUBE_MAP, | |
| texture_ids[1], | |
| }, | |
| { | |
| GL_TEXTURE_BINDING_EXTERNAL_OES, | |
| texture_ids[2], | |
| }, | |
| }); | |
| size_t num_pairs = | |
| (pairs.size() * sizeof(decltype(pairs)::value_type)) / sizeof(pairs[0]); | |
| for (size_t ii = 0; ii < num_pairs; ++ii) { | |
| const PNameValue& pv = pairs[ii]; | |
| GLint v = -1; | |
| gl_->GetIntegerv(pv.pname, &v); | |
| EXPECT_EQ(pv.expected, static_cast<GLuint>(v)); | |
| } | |
| ExpectedMemoryInfo result1 = | |
| GetExpectedResultMemory(sizeof(cmds::GetError::Result)); | |
| EXPECT_CALL(*command_buffer(), OnFlush()) | |
| .WillOnce(SetMemory(result1.ptr, GLuint(GL_NO_ERROR))) | |
| .RetiresOnSaturation(); | |
| EXPECT_EQ(static_cast<GLenum>(GL_NO_ERROR), gl_->GetError()); | |
| } | |
| static bool CheckRect(int width, | |
| int height, | |
| GLenum format, | |
| GLenum type, | |
| int alignment, | |
| const uint8_t* r1, | |
| const uint8_t* r2) { | |
| uint32_t size = 0; | |
| uint32_t unpadded_row_size = 0; | |
| uint32_t padded_row_size = 0; | |
| if (!GLES2Util::ComputeImageDataSizes( | |
| width, height, 1, format, type, alignment, &size, &unpadded_row_size, | |
| &padded_row_size)) { | |
| return false; | |
| } | |
| int r2_stride = static_cast<int>(padded_row_size); | |
| for (int y = 0; y < height; ++y) { | |
| if (UNSAFE_TODO(memcmp(r1, r2, unpadded_row_size)) != 0) { | |
| return false; | |
| } | |
| UNSAFE_TODO(r1 += padded_row_size); | |
| UNSAFE_TODO(r2 += r2_stride); | |
| } | |
| return true; | |
| } | |
| ACTION_P7(CheckRectAction, width, height, format, type, alignment, r1, r2) { | |
| EXPECT_TRUE(CheckRect( | |
| width, height, format, type, alignment, r1, r2)); | |
| } | |
| TEST_F(GLES2ImplementationTest, TexImage2D) { | |
| struct Cmds { | |
| cmds::TexImage2D tex_image_2d; | |
| cmd::SetToken set_token; | |
| }; | |
| struct Cmds2 { | |
| cmds::TexImage2D tex_image_2d; | |
| cmd::SetToken set_token; | |
| }; | |
| const GLenum kTarget = GL_TEXTURE_2D; | |
| const GLint kLevel = 0; | |
| const GLenum kFormat = GL_RGB; | |
| const GLsizei kWidth = 3; | |
| const GLsizei kHeight = 4; | |
| const GLint kBorder = 0; | |
| const GLenum kType = GL_UNSIGNED_BYTE; | |
| const GLint kPixelStoreUnpackAlignment = 4; | |
| static uint8_t pixels[] = { | |
| 11, 12, 13, 13, 14, 15, 15, 16, 17, 101, 102, 103, 21, 22, 23, | |
| 23, 24, 25, 25, 26, 27, 201, 202, 203, 31, 32, 33, 33, 34, 35, | |
| 35, 36, 37, 123, 124, 125, 41, 42, 43, 43, 44, 45, 45, 46, 47, | |
| }; | |
| ExpectedMemoryInfo mem1 = GetExpectedMemory(sizeof(pixels)); | |
| Cmds expected; | |
| expected.tex_image_2d.Init( | |
| kTarget, kLevel, kFormat, kWidth, kHeight, kFormat, kType, | |
| mem1.id, mem1.offset); | |
| expected.set_token.Init(GetNextToken()); | |
| gl_->TexImage2D( | |
| kTarget, kLevel, kFormat, kWidth, kHeight, kBorder, kFormat, kType, | |
| pixels); | |
| UNSAFE_TODO(EXPECT_EQ(0, memcmp(&expected, commands_, sizeof(expected)))); | |
| EXPECT_TRUE(CheckRect( | |
| kWidth, kHeight, kFormat, kType, kPixelStoreUnpackAlignment, | |
| pixels, mem1.ptr)); | |
| } | |
| TEST_F(GLES2ImplementationTest, TexImage2DViaMappedMem) { | |
| if (!AllowExtraTransferBufferSize()) { | |
| LOG(WARNING) << "Low memory device do not support MappedMem. Skipping test"; | |
| return; | |
| } | |
| struct Cmds { | |
| cmds::TexImage2D tex_image_2d; | |
| cmd::SetToken set_token; | |
| }; | |
| const GLenum kTarget = GL_TEXTURE_2D; | |
| const GLint kLevel = 0; | |
| const GLenum kFormat = GL_RGB; | |
| const GLsizei kWidth = 3; | |
| const GLint kBorder = 0; | |
| const GLenum kType = GL_UNSIGNED_BYTE; | |
| const GLint kPixelStoreUnpackAlignment = 4; | |
| uint32_t size = 0; | |
| uint32_t unpadded_row_size = 0; | |
| uint32_t padded_row_size = 0; | |
| ASSERT_TRUE(GLES2Util::ComputeImageDataSizes( | |
| kWidth, 2, 1, kFormat, kType, kPixelStoreUnpackAlignment, | |
| &size, &unpadded_row_size, &padded_row_size)); | |
| const GLsizei kMaxHeight = (MaxTransferBufferSize() / padded_row_size) * 2; | |
| const GLsizei kHeight = kMaxHeight * 2; | |
| ASSERT_TRUE(GLES2Util::ComputeImageDataSizes( | |
| kWidth, kHeight, 1, kFormat, kType, kPixelStoreUnpackAlignment, | |
| &size, &unpadded_row_size, &padded_row_size)); | |
| auto pixels = base::HeapArray<uint8_t>::Uninit(size); | |
| for (uint32_t ii = 0; ii < size; ++ii) { | |
| pixels[ii] = static_cast<uint8_t>(ii); | |
| } | |
| ExpectedMemoryInfo mem1 = GetExpectedMappedMemory(size); | |
| Cmds expected; | |
| expected.tex_image_2d.Init( | |
| kTarget, kLevel, kFormat, kWidth, kHeight, kFormat, kType, | |
| mem1.id, mem1.offset); | |
| expected.set_token.Init(GetNextToken()); | |
| gl_->TexImage2D(kTarget, kLevel, kFormat, kWidth, kHeight, kBorder, kFormat, | |
| kType, pixels.data()); | |
| UNSAFE_TODO(EXPECT_EQ(0, memcmp(&expected, commands_, sizeof(expected)))); | |
| EXPECT_TRUE(CheckRect(kWidth, kHeight, kFormat, kType, | |
| kPixelStoreUnpackAlignment, pixels.data(), mem1.ptr)); | |
| } | |
| // Test TexImage2D with 2 writes | |
| TEST_F(GLES2ImplementationTest, TexImage2DViaTexSubImage2D) { | |
| // Set limit to 1 to effectively disable mapped memory. | |
| SetMappedMemoryLimit(1); | |
| struct Cmds { | |
| cmds::TexImage2D tex_image_2d; | |
| cmds::TexSubImage2D tex_sub_image_2d1; | |
| cmd::SetToken set_token1; | |
| cmds::TexSubImage2D tex_sub_image_2d2; | |
| cmd::SetToken set_token2; | |
| }; | |
| const GLenum kTarget = GL_TEXTURE_2D; | |
| const GLint kLevel = 0; | |
| const GLenum kFormat = GL_RGB; | |
| const GLint kBorder = 0; | |
| const GLenum kType = GL_UNSIGNED_BYTE; | |
| const GLint kPixelStoreUnpackAlignment = 4; | |
| const GLsizei kWidth = 3; | |
| uint32_t size = 0; | |
| uint32_t unpadded_row_size = 0; | |
| uint32_t padded_row_size = 0; | |
| ASSERT_TRUE(GLES2Util::ComputeImageDataSizes( | |
| kWidth, 2, 1, kFormat, kType, kPixelStoreUnpackAlignment, | |
| &size, &unpadded_row_size, &padded_row_size)); | |
| const GLsizei kHeight = (MaxTransferBufferSize() / padded_row_size) * 2; | |
| ASSERT_TRUE(GLES2Util::ComputeImageDataSizes( | |
| kWidth, kHeight, 1, kFormat, kType, kPixelStoreUnpackAlignment, &size, | |
| nullptr, nullptr)); | |
| uint32_t half_size = 0; | |
| ASSERT_TRUE(GLES2Util::ComputeImageDataSizes( | |
| kWidth, kHeight / 2, 1, kFormat, kType, kPixelStoreUnpackAlignment, | |
| &half_size, nullptr, nullptr)); | |
| auto pixels = base::HeapArray<uint8_t>::Uninit(size); | |
| for (uint32_t ii = 0; ii < size; ++ii) { | |
| pixels[ii] = static_cast<uint8_t>(ii); | |
| } | |
| ExpectedMemoryInfo mem1 = GetExpectedMemory(half_size); | |
| ExpectedMemoryInfo mem2 = GetExpectedMemory(half_size); | |
| Cmds expected; | |
| expected.tex_image_2d.Init( | |
| kTarget, kLevel, kFormat, kWidth, kHeight, kFormat, kType, | |
| 0, 0); | |
| expected.tex_sub_image_2d1.Init( | |
| kTarget, kLevel, 0, 0, kWidth, kHeight / 2, kFormat, kType, | |
| mem1.id, mem1.offset, true); | |
| expected.set_token1.Init(GetNextToken()); | |
| expected.tex_sub_image_2d2.Init( | |
| kTarget, kLevel, 0, kHeight / 2, kWidth, kHeight / 2, kFormat, kType, | |
| mem2.id, mem2.offset, true); | |
| expected.set_token2.Init(GetNextToken()); | |
| // TODO(gman): Make it possible to run this test | |
| // EXPECT_CALL(*command_buffer(), OnFlush()) | |
| // .WillOnce(CheckRectAction( | |
| // kWidth, kHeight / 2, kFormat, kType, kPixelStoreUnpackAlignment, | |
| // false, pixels.data(), | |
| // GetExpectedTransferAddressFromOffsetAs<uint8_t>(offset1, | |
| // half_size))) | |
| // .RetiresOnSaturation(); | |
| gl_->TexImage2D(kTarget, kLevel, kFormat, kWidth, kHeight, kBorder, kFormat, | |
| kType, pixels.data()); | |
| UNSAFE_TODO(EXPECT_EQ(0, memcmp(&expected, commands_, sizeof(expected)))); | |
| UNSAFE_TODO(EXPECT_TRUE( | |
| CheckRect(kWidth, kHeight / 2, kFormat, kType, kPixelStoreUnpackAlignment, | |
| pixels.data() + kHeight / 2 * padded_row_size, mem2.ptr))); | |
| } | |
| TEST_F(GLES2ImplementationTest, SubImage2DUnpack) { | |
| static const auto unpack_alignments = std::to_array<GLint>({1, 2, 4, 8}); | |
| static const GLenum kFormat = GL_RGB; | |
| static const GLenum kType = GL_UNSIGNED_BYTE; | |
| static const GLint kLevel = 0; | |
| static const GLint kBorder = 0; | |
| // We're testing using the unpack params to pull a subimage out of a larger | |
| // source of pixels. Here we specify the subimage by its border rows / | |
| // columns. | |
| static const GLint kSrcWidth = 33; | |
| static const GLint kSrcSubImageX0 = 11; | |
| static const GLint kSrcSubImageX1 = 20; | |
| static const GLint kSrcSubImageY0 = 18; | |
| static const GLint kSrcSubImageY1 = 23; | |
| static const GLint kSrcSubImageWidth = kSrcSubImageX1 - kSrcSubImageX0; | |
| static const GLint kSrcSubImageHeight = kSrcSubImageY1 - kSrcSubImageY0; | |
| // these are only used in the texsubimage tests | |
| static const GLint kTexWidth = 1023; | |
| static const GLint kTexHeight = 511; | |
| static const GLint kTexSubXOffset = 419; | |
| static const GLint kTexSubYOffset = 103; | |
| struct { | |
| cmds::PixelStorei pixel_store_i; | |
| cmds::TexImage2D tex_image_2d; | |
| } texImageExpected; | |
| struct { | |
| cmds::PixelStorei pixel_store_i; | |
| cmds::TexImage2D tex_image_2d; | |
| cmds::TexSubImage2D tex_sub_image_2d; | |
| } texSubImageExpected; | |
| uint32_t pixel_size; | |
| PixelStoreParams pixel_params; | |
| // Makes sure the pixels size is large enough for all tests. | |
| pixel_params.alignment = 8; | |
| ASSERT_TRUE(GLES2Util::ComputeImageDataSizesES3( | |
| kSrcWidth, kSrcSubImageY1, 1, kFormat, kType, | |
| pixel_params, &pixel_size, nullptr, nullptr, nullptr, nullptr)); | |
| auto src_pixels = base::HeapArray<uint8_t>::WithSize(pixel_size); | |
| for (size_t i = 0; i < pixel_size; ++i) { | |
| src_pixels[i] = static_cast<uint8_t>(i % 255); | |
| } | |
| for (int sub = 0; sub < 2; ++sub) { | |
| for (size_t a = 0; a < std::size(unpack_alignments); ++a) { | |
| const void* commands = GetPut(); | |
| GLint alignment = unpack_alignments[a]; | |
| gl_->PixelStorei(GL_UNPACK_ALIGNMENT, alignment); | |
| gl_->PixelStorei(GL_UNPACK_ROW_LENGTH, kSrcWidth); | |
| gl_->PixelStorei(GL_UNPACK_SKIP_PIXELS, kSrcSubImageX0); | |
| gl_->PixelStorei(GL_UNPACK_SKIP_ROWS, kSrcSubImageY0); | |
| uint32_t client_size; | |
| uint32_t client_unpadded_row_size; | |
| uint32_t client_padded_row_size; | |
| uint32_t client_skip_size; | |
| { | |
| PixelStoreParams params; | |
| params.alignment = alignment; | |
| params.row_length = kSrcWidth; | |
| params.skip_pixels = kSrcSubImageX0; | |
| params.skip_rows = kSrcSubImageY0; | |
| ASSERT_TRUE(GLES2Util::ComputeImageDataSizesES3( | |
| kSrcSubImageWidth, kSrcSubImageHeight, 1, kFormat, kType, params, | |
| &client_size, &client_unpadded_row_size, &client_padded_row_size, | |
| &client_skip_size, nullptr)); | |
| ASSERT_TRUE(client_size + client_skip_size <= pixel_size); | |
| } | |
| uint32_t service_size; | |
| uint32_t service_unpadded_row_size; | |
| uint32_t service_padded_row_size; | |
| uint32_t service_skip_size; | |
| { | |
| PixelStoreParams params; | |
| // For pixels we send to service side, we already applied all unpack | |
| // parameters except for UNPACK_ALIGNMENT. | |
| params.alignment = alignment; | |
| ASSERT_TRUE(GLES2Util::ComputeImageDataSizesES3( | |
| kSrcSubImageWidth, kSrcSubImageHeight, 1, kFormat, kType, params, | |
| &service_size, &service_unpadded_row_size, &service_padded_row_size, | |
| &service_skip_size, nullptr)); | |
| ASSERT_TRUE(service_size <= MaxTransferBufferSize()); | |
| ASSERT_TRUE(service_skip_size == 0); | |
| ASSERT_TRUE(client_unpadded_row_size == service_unpadded_row_size); | |
| } | |
| ExpectedMemoryInfo mem = GetExpectedMemory(service_size); | |
| if (sub) { | |
| gl_->TexImage2D( | |
| GL_TEXTURE_2D, kLevel, kFormat, kTexWidth, kTexHeight, kBorder, | |
| kFormat, kType, nullptr); | |
| gl_->TexSubImage2D(GL_TEXTURE_2D, kLevel, kTexSubXOffset, | |
| kTexSubYOffset, kSrcSubImageWidth, | |
| kSrcSubImageHeight, kFormat, kType, | |
| src_pixels.data()); | |
| texSubImageExpected.pixel_store_i.Init(GL_UNPACK_ALIGNMENT, alignment); | |
| texSubImageExpected.tex_image_2d.Init( | |
| GL_TEXTURE_2D, kLevel, kFormat, kTexWidth, kTexHeight, | |
| kFormat, kType, 0, 0); | |
| texSubImageExpected.tex_sub_image_2d.Init( | |
| GL_TEXTURE_2D, kLevel, kTexSubXOffset, kTexSubYOffset, | |
| kSrcSubImageWidth, kSrcSubImageHeight, kFormat, kType, mem.id, | |
| mem.offset, GL_FALSE); | |
| UNSAFE_TODO(EXPECT_EQ(0, memcmp(&texSubImageExpected, commands, | |
| sizeof(texSubImageExpected)))); | |
| } else { | |
| gl_->TexImage2D(GL_TEXTURE_2D, kLevel, kFormat, kSrcSubImageWidth, | |
| kSrcSubImageHeight, kBorder, kFormat, kType, | |
| src_pixels.data()); | |
| texImageExpected.pixel_store_i.Init(GL_UNPACK_ALIGNMENT, alignment); | |
| texImageExpected.tex_image_2d.Init( | |
| GL_TEXTURE_2D, kLevel, kFormat, kSrcSubImageWidth, | |
| kSrcSubImageHeight, kFormat, kType, mem.id, mem.offset); | |
| UNSAFE_TODO(EXPECT_EQ( | |
| 0, memcmp(&texImageExpected, commands, sizeof(texImageExpected)))); | |
| } | |
| for (int y = 0; y < kSrcSubImageHeight; ++y) { | |
| const uint8_t* src_row = UNSAFE_TODO( | |
| src_pixels.data() + client_skip_size + y * client_padded_row_size); | |
| const uint8_t* dst_row = | |
| UNSAFE_TODO(mem.ptr + y * service_padded_row_size); | |
| UNSAFE_TODO( | |
| EXPECT_EQ(0, memcmp(src_row, dst_row, service_unpadded_row_size))); | |
| } | |
| ClearCommands(); | |
| } | |
| } | |
| } | |
| TEST_F(GLES3ImplementationTest, SubImage3DUnpack) { | |
| static const auto unpack_alignments = std::to_array<GLint>({1, 2, 4, 8}); | |
| static const GLenum kFormat = GL_RGB; | |
| static const GLenum kType = GL_UNSIGNED_BYTE; | |
| static const GLint kLevel = 0; | |
| static const GLint kBorder = 0; | |
| // We're testing using the unpack params to pull a subimage out of a larger | |
| // source of pixels. Here we specify the subimage by its border rows / | |
| // columns. | |
| static const GLint kSrcWidth = 23; | |
| static const GLint kSrcHeight = 7; | |
| static const GLint kSrcSubImageX0 = 11; | |
| static const GLint kSrcSubImageX1 = 16; | |
| static const GLint kSrcSubImageY0 = 1; | |
| static const GLint kSrcSubImageY1 = 4; | |
| static const GLint kSrcSubImageZ0 = 2; | |
| static const GLint kSrcSubImageZ1 = 5; | |
| static const GLint kSrcSubImageWidth = kSrcSubImageX1 - kSrcSubImageX0; | |
| static const GLint kSrcSubImageHeight = kSrcSubImageY1 - kSrcSubImageY0; | |
| static const GLint kSrcSubImageDepth = kSrcSubImageZ1 - kSrcSubImageZ0; | |
| // these are only used in the texsubimage tests | |
| static const GLint kTexWidth = 255; | |
| static const GLint kTexHeight = 127; | |
| static const GLint kTexDepth = 11; | |
| static const GLint kTexSubXOffset = 119; | |
| static const GLint kTexSubYOffset = 63; | |
| static const GLint kTexSubZOffset = 1; | |
| struct { | |
| cmds::PixelStorei pixel_store_i[3]; | |
| cmds::TexImage3D tex_image_3d; | |
| } texImageExpected; | |
| struct { | |
| cmds::PixelStorei pixel_store_i[3]; | |
| cmds::TexImage3D tex_image_3d; | |
| cmds::TexSubImage3D tex_sub_image_3d; | |
| } texSubImageExpected; | |
| uint32_t pixel_size; | |
| PixelStoreParams pixel_params; | |
| // Makes sure the pixels size is large enough for all tests. | |
| pixel_params.alignment = 8; | |
| ASSERT_TRUE(GLES2Util::ComputeImageDataSizesES3( | |
| kSrcWidth, kSrcHeight, kSrcSubImageZ1, kFormat, kType, | |
| pixel_params, &pixel_size, nullptr, nullptr, nullptr, nullptr)); | |
| auto src_pixels = base::HeapArray<uint8_t>::WithSize(pixel_size); | |
| for (size_t i = 0; i < pixel_size; ++i) { | |
| src_pixels[i] = static_cast<uint8_t>(i % 255); | |
| } | |
| for (int sub = 0; sub < 2; ++sub) { | |
| for (size_t a = 0; a < std::size(unpack_alignments); ++a) { | |
| const void* commands = GetPut(); | |
| GLint alignment = unpack_alignments[a]; | |
| gl_->PixelStorei(GL_UNPACK_ALIGNMENT, alignment); | |
| gl_->PixelStorei(GL_UNPACK_ROW_LENGTH, kSrcWidth); | |
| gl_->PixelStorei(GL_UNPACK_IMAGE_HEIGHT, kSrcHeight); | |
| gl_->PixelStorei(GL_UNPACK_SKIP_PIXELS, kSrcSubImageX0); | |
| gl_->PixelStorei(GL_UNPACK_SKIP_ROWS, kSrcSubImageY0); | |
| gl_->PixelStorei(GL_UNPACK_SKIP_IMAGES, kSrcSubImageZ0); | |
| uint32_t client_size; | |
| uint32_t client_unpadded_row_size; | |
| uint32_t client_padded_row_size; | |
| uint32_t client_skip_size; | |
| { | |
| PixelStoreParams params; | |
| params.alignment = alignment; | |
| params.row_length = kSrcWidth; | |
| params.image_height = kSrcHeight; | |
| params.skip_pixels = kSrcSubImageX0; | |
| params.skip_rows = kSrcSubImageY0; | |
| params.skip_images = kSrcSubImageZ0; | |
| ASSERT_TRUE(GLES2Util::ComputeImageDataSizesES3( | |
| kSrcSubImageWidth, kSrcSubImageHeight, kSrcSubImageDepth, | |
| kFormat, kType, params, | |
| &client_size, &client_unpadded_row_size, &client_padded_row_size, | |
| &client_skip_size, nullptr)); | |
| ASSERT_TRUE(client_size + client_skip_size <= pixel_size); | |
| } | |
| uint32_t service_size; | |
| uint32_t service_unpadded_row_size; | |
| uint32_t service_padded_row_size; | |
| uint32_t service_skip_size; | |
| { | |
| PixelStoreParams params; | |
| // For pixels we send to service side, we already applied all unpack | |
| // parameters except for UNPACK_ALIGNMENT. | |
| params.alignment = alignment; | |
| ASSERT_TRUE(GLES2Util::ComputeImageDataSizesES3( | |
| kSrcSubImageWidth, kSrcSubImageHeight, kSrcSubImageDepth, | |
| kFormat, kType, params, | |
| &service_size, &service_unpadded_row_size, &service_padded_row_size, | |
| &service_skip_size, nullptr)); | |
| ASSERT_TRUE(service_size <= MaxTransferBufferSize()); | |
| ASSERT_TRUE(service_skip_size == 0); | |
| ASSERT_TRUE(client_unpadded_row_size == service_unpadded_row_size); | |
| } | |
| ExpectedMemoryInfo mem = GetExpectedMemory(service_size); | |
| if (sub) { | |
| gl_->TexImage3D( | |
| GL_TEXTURE_3D, kLevel, kFormat, kTexWidth, kTexHeight, kTexDepth, | |
| kBorder, kFormat, kType, nullptr); | |
| gl_->TexSubImage3D(GL_TEXTURE_3D, kLevel, kTexSubXOffset, | |
| kTexSubYOffset, kTexSubZOffset, kSrcSubImageWidth, | |
| kSrcSubImageHeight, kSrcSubImageDepth, kFormat, | |
| kType, src_pixels.data()); | |
| texSubImageExpected.pixel_store_i[0].Init( | |
| GL_UNPACK_ALIGNMENT, alignment); | |
| texSubImageExpected.pixel_store_i[1].Init( | |
| GL_UNPACK_ROW_LENGTH, kSrcWidth); | |
| texSubImageExpected.pixel_store_i[2].Init( | |
| GL_UNPACK_IMAGE_HEIGHT, kSrcHeight); | |
| texSubImageExpected.tex_image_3d.Init( | |
| GL_TEXTURE_3D, kLevel, kFormat, kTexWidth, kTexHeight, kTexDepth, | |
| kFormat, kType, 0, 0); | |
| texSubImageExpected.tex_sub_image_3d.Init( | |
| GL_TEXTURE_3D, kLevel, | |
| kTexSubXOffset, kTexSubYOffset, kTexSubZOffset, | |
| kSrcSubImageWidth, kSrcSubImageHeight, kSrcSubImageDepth, | |
| kFormat, kType, mem.id, mem.offset, GL_FALSE); | |
| UNSAFE_TODO(EXPECT_EQ(0, memcmp(&texSubImageExpected, commands, | |
| sizeof(texSubImageExpected)))); | |
| } else { | |
| gl_->TexImage3D(GL_TEXTURE_3D, kLevel, kFormat, kSrcSubImageWidth, | |
| kSrcSubImageHeight, kSrcSubImageDepth, kBorder, kFormat, | |
| kType, src_pixels.data()); | |
| texImageExpected.pixel_store_i[0].Init(GL_UNPACK_ALIGNMENT, alignment); | |
| texImageExpected.pixel_store_i[1].Init( | |
| GL_UNPACK_ROW_LENGTH, kSrcWidth); | |
| texImageExpected.pixel_store_i[2].Init( | |
| GL_UNPACK_IMAGE_HEIGHT, kSrcHeight); | |
| texImageExpected.tex_image_3d.Init( | |
| GL_TEXTURE_3D, kLevel, kFormat, | |
| kSrcSubImageWidth, kSrcSubImageHeight, kSrcSubImageDepth, | |
| kFormat, kType, mem.id, mem.offset); | |
| UNSAFE_TODO(EXPECT_EQ( | |
| 0, memcmp(&texImageExpected, commands, sizeof(texImageExpected)))); | |
| } | |
| for (int z = 0; z < kSrcSubImageDepth; ++z) { | |
| for (int y = 0; y < kSrcSubImageHeight; ++y) { | |
| const uint8_t* src_row = | |
| UNSAFE_TODO(src_pixels.data() + client_skip_size + | |
| (kSrcHeight * z + y) * client_padded_row_size); | |
| const uint8_t* dst_row = UNSAFE_TODO( | |
| mem.ptr + (kSrcSubImageHeight * z + y) * service_padded_row_size); | |
| UNSAFE_TODO(EXPECT_EQ( | |
| 0, memcmp(src_row, dst_row, service_unpadded_row_size))); | |
| } | |
| } | |
| ClearCommands(); | |
| } | |
| } | |
| } | |
| // Test texture related calls with invalid arguments. | |
| TEST_F(GLES2ImplementationTest, TextureInvalidArguments) { | |
| struct Cmds { | |
| cmds::TexImage2D tex_image_2d; | |
| cmd::SetToken set_token; | |
| }; | |
| const GLenum kTarget = GL_TEXTURE_2D; | |
| const GLint kLevel = 0; | |
| const GLenum kFormat = GL_RGB; | |
| const GLsizei kWidth = 3; | |
| const GLsizei kHeight = 4; | |
| const GLint kBorder = 0; | |
| const GLint kInvalidBorder = 1; | |
| const GLenum kType = GL_UNSIGNED_BYTE; | |
| const GLint kPixelStoreUnpackAlignment = 4; | |
| static uint8_t pixels[] = { | |
| 11, 12, 13, 13, 14, 15, 15, 16, 17, 101, 102, 103, 21, 22, 23, | |
| 23, 24, 25, 25, 26, 27, 201, 202, 203, 31, 32, 33, 33, 34, 35, | |
| 35, 36, 37, 123, 124, 125, 41, 42, 43, 43, 44, 45, 45, 46, 47, | |
| }; | |
| // Verify that something works. | |
| ExpectedMemoryInfo mem1 = GetExpectedMemory(sizeof(pixels)); | |
| Cmds expected; | |
| expected.tex_image_2d.Init( | |
| kTarget, kLevel, kFormat, kWidth, kHeight, kFormat, kType, | |
| mem1.id, mem1.offset); | |
| expected.set_token.Init(GetNextToken()); | |
| gl_->TexImage2D( | |
| kTarget, kLevel, kFormat, kWidth, kHeight, kBorder, kFormat, kType, | |
| pixels); | |
| UNSAFE_TODO(EXPECT_EQ(0, memcmp(&expected, commands_, sizeof(expected)))); | |
| EXPECT_TRUE(CheckRect( | |
| kWidth, kHeight, kFormat, kType, kPixelStoreUnpackAlignment, | |
| pixels, mem1.ptr)); | |
| ClearCommands(); | |
| // Use invalid border. | |
| gl_->TexImage2D( | |
| kTarget, kLevel, kFormat, kWidth, kHeight, kInvalidBorder, kFormat, kType, | |
| pixels); | |
| EXPECT_TRUE(NoCommandsWritten()); | |
| EXPECT_EQ(GL_INVALID_VALUE, CheckError()); | |
| ClearCommands(); | |
| // Checking for CompressedTexImage2D argument validation is a bit tricky due | |
| // to (runtime-detected) compression formats. Try to infer the error with an | |
| // aux check. | |
| const GLenum kCompressedFormat = GL_ETC1_RGB8_OES; | |
| gl_->CompressedTexImage2D(kTarget, kLevel, kCompressedFormat, kWidth, kHeight, | |
| kBorder, std::size(pixels), pixels); | |
| // In the above, kCompressedFormat and std::size(pixels) are possibly wrong | |
| // values. First ensure that these do not cause failures at the client. If | |
| // this check ever fails, it probably means that client checks more than at | |
| // the time of writing of this test. In this case, more code needs to be | |
| // written for this test. | |
| EXPECT_FALSE(NoCommandsWritten()); | |
| ClearCommands(); | |
| // Changing border to invalid border should make the call fail at the client | |
| // checks. | |
| gl_->CompressedTexImage2D(kTarget, kLevel, kCompressedFormat, kWidth, kHeight, | |
| kInvalidBorder, std::size(pixels), pixels); | |
| EXPECT_TRUE(NoCommandsWritten()); | |
| EXPECT_EQ(GL_INVALID_VALUE, CheckError()); | |
| } | |
| TEST_F(GLES2ImplementationTest, TexImage3DSingleCommand) { | |
| struct Cmds { | |
| cmds::TexImage3D tex_image_3d; | |
| }; | |
| const GLenum kTarget = GL_TEXTURE_3D; | |
| const GLint kLevel = 0; | |
| const GLint kBorder = 0; | |
| const GLenum kFormat = GL_RGB; | |
| const GLenum kType = GL_UNSIGNED_BYTE; | |
| const GLint kPixelStoreUnpackAlignment = 4; | |
| const GLsizei kWidth = 3; | |
| const GLsizei kDepth = 2; | |
| uint32_t size = 0; | |
| uint32_t unpadded_row_size = 0; | |
| uint32_t padded_row_size = 0; | |
| ASSERT_TRUE(GLES2Util::ComputeImageDataSizes( | |
| kWidth, 2, kDepth, kFormat, kType, kPixelStoreUnpackAlignment, | |
| &size, &unpadded_row_size, &padded_row_size)); | |
| // Makes sure we can just send over the data in one command. | |
| const GLsizei kHeight = MaxTransferBufferSize() / padded_row_size / kDepth; | |
| ASSERT_TRUE(GLES2Util::ComputeImageDataSizes( | |
| kWidth, kHeight, kDepth, kFormat, kType, kPixelStoreUnpackAlignment, | |
| &size, nullptr, nullptr)); | |
| auto pixels = base::HeapArray<uint8_t>::Uninit(size); | |
| for (uint32_t ii = 0; ii < size; ++ii) { | |
| pixels[ii] = static_cast<uint8_t>(ii); | |
| } | |
| ExpectedMemoryInfo mem = GetExpectedMemory(size); | |
| Cmds expected; | |
| expected.tex_image_3d.Init( | |
| kTarget, kLevel, kFormat, kWidth, kHeight, kDepth, | |
| kFormat, kType, mem.id, mem.offset); | |
| gl_->TexImage3D(kTarget, kLevel, kFormat, kWidth, kHeight, kDepth, kBorder, | |
| kFormat, kType, pixels.data()); | |
| UNSAFE_TODO(EXPECT_EQ(0, memcmp(&expected, commands_, sizeof(expected)))); | |
| EXPECT_TRUE(CheckRect(kWidth, kHeight * kDepth, kFormat, kType, | |
| kPixelStoreUnpackAlignment, | |
| reinterpret_cast<uint8_t*>(pixels.data()), mem.ptr)); | |
| } | |
| TEST_F(GLES2ImplementationTest, TexImage3DViaMappedMem) { | |
| if (!AllowExtraTransferBufferSize()) { | |
| LOG(WARNING) << "Low memory device do not support MappedMem. Skipping test"; | |
| return; | |
| } | |
| struct Cmds { | |
| cmds::TexImage3D tex_image_3d; | |
| }; | |
| const GLenum kTarget = GL_TEXTURE_3D; | |
| const GLint kLevel = 0; | |
| const GLint kBorder = 0; | |
| const GLenum kFormat = GL_RGB; | |
| const GLenum kType = GL_UNSIGNED_BYTE; | |
| const GLint kPixelStoreUnpackAlignment = 4; | |
| const GLsizei kWidth = 3; | |
| const GLsizei kDepth = 2; | |
| uint32_t size = 0; | |
| uint32_t unpadded_row_size = 0; | |
| uint32_t padded_row_size = 0; | |
| ASSERT_TRUE(GLES2Util::ComputeImageDataSizes( | |
| kWidth, 2, kDepth, kFormat, kType, kPixelStoreUnpackAlignment, | |
| &size, &unpadded_row_size, &padded_row_size)); | |
| // Makes sure we can just send over the data in one command. | |
| const GLsizei kMaxHeight = MaxTransferBufferSize() / padded_row_size / kDepth; | |
| const GLsizei kHeight = kMaxHeight * 2; | |
| ASSERT_TRUE(GLES2Util::ComputeImageDataSizes( | |
| kWidth, kHeight, kDepth, kFormat, kType, kPixelStoreUnpackAlignment, | |
| &size, nullptr, nullptr)); | |
| auto pixels = base::HeapArray<uint8_t>::Uninit(size); | |
| for (uint32_t ii = 0; ii < size; ++ii) { | |
| pixels[ii] = static_cast<uint8_t>(ii); | |
| } | |
| ExpectedMemoryInfo mem = GetExpectedMappedMemory(size); | |
| Cmds expected; | |
| expected.tex_image_3d.Init( | |
| kTarget, kLevel, kFormat, kWidth, kHeight, kDepth, | |
| kFormat, kType, mem.id, mem.offset); | |
| gl_->TexImage3D(kTarget, kLevel, kFormat, kWidth, kHeight, kDepth, kBorder, | |
| kFormat, kType, pixels.data()); | |
| UNSAFE_TODO(EXPECT_EQ(0, memcmp(&expected, commands_, sizeof(expected)))); | |
| EXPECT_TRUE(CheckRect(kWidth, kHeight * kDepth, kFormat, kType, | |
| kPixelStoreUnpackAlignment, | |
| reinterpret_cast<uint8_t*>(pixels.data()), mem.ptr)); | |
| } | |
| TEST_F(GLES2ImplementationTest, TexImage3DViaTexSubImage3D) { | |
| // Set limit to 1 to effectively disable mapped memory. | |
| SetMappedMemoryLimit(1); | |
| struct Cmds { | |
| cmds::TexImage3D tex_image_3d; | |
| cmds::TexSubImage3D tex_sub_image_3d1; | |
| cmd::SetToken set_token; | |
| cmds::TexSubImage3D tex_sub_image_3d2; | |
| }; | |
| const GLenum kTarget = GL_TEXTURE_3D; | |
| const GLint kLevel = 0; | |
| const GLint kBorder = 0; | |
| const GLenum kFormat = GL_RGB; | |
| const GLenum kType = GL_UNSIGNED_BYTE; | |
| const GLint kPixelStoreUnpackAlignment = 4; | |
| const GLsizei kWidth = 3; | |
| uint32_t size = 0; | |
| uint32_t unpadded_row_size = 0; | |
| uint32_t padded_row_size = 0; | |
| ASSERT_TRUE(GLES2Util::ComputeImageDataSizes( | |
| kWidth, 2, 1, kFormat, kType, kPixelStoreUnpackAlignment, | |
| &size, &unpadded_row_size, &padded_row_size)); | |
| // Makes sure the data is more than one command can hold. | |
| const GLsizei kHeight = MaxTransferBufferSize() / padded_row_size + 3; | |
| ASSERT_TRUE(GLES2Util::ComputeImageDataSizes( | |
| kWidth, kHeight, 1, kFormat, kType, kPixelStoreUnpackAlignment, &size, | |
| nullptr, nullptr)); | |
| uint32_t first_size = padded_row_size * (kHeight - 3); | |
| uint32_t second_size = | |
| padded_row_size * 3 - (padded_row_size - unpadded_row_size); | |
| EXPECT_EQ(size, first_size + second_size); | |
| ExpectedMemoryInfo mem1 = GetExpectedMemory(first_size); | |
| ExpectedMemoryInfo mem2 = GetExpectedMemory(second_size); | |
| auto pixels = base::HeapArray<uint8_t>::Uninit(size); | |
| for (uint32_t ii = 0; ii < size; ++ii) { | |
| pixels[ii] = static_cast<uint8_t>(ii); | |
| } | |
| Cmds expected; | |
| expected.tex_image_3d.Init( | |
| kTarget, kLevel, kFormat, kWidth, kHeight, 1, kFormat, kType, 0, 0); | |
| expected.tex_sub_image_3d1.Init( | |
| kTarget, kLevel, 0, 0, 0, kWidth, kHeight - 3, 1, kFormat, kType, | |
| mem1.id, mem1.offset, GL_TRUE); | |
| expected.tex_sub_image_3d2.Init( | |
| kTarget, kLevel, 0, kHeight - 3, 0, kWidth, 3, 1, kFormat, kType, | |
| mem2.id, mem2.offset, GL_TRUE); | |
| expected.set_token.Init(GetNextToken()); | |
| gl_->TexImage3D(kTarget, kLevel, kFormat, kWidth, kHeight, 1, kBorder, | |
| kFormat, kType, pixels.data()); | |
| UNSAFE_TODO(EXPECT_EQ(0, memcmp(&expected, commands_, sizeof(expected)))); | |
| } | |
| // Test TexSubImage3D with 4 writes | |
| TEST_F(GLES2ImplementationTest, TexSubImage3D4Writes) { | |
| struct Cmds { | |
| cmds::TexSubImage3D tex_sub_image_3d1_1; | |
| cmd::SetToken set_token1; | |
| cmds::TexSubImage3D tex_sub_image_3d1_2; | |
| cmd::SetToken set_token2; | |
| cmds::TexSubImage3D tex_sub_image_3d2_1; | |
| cmd::SetToken set_token3; | |
| cmds::TexSubImage3D tex_sub_image_3d2_2; | |
| }; | |
| const GLenum kTarget = GL_TEXTURE_3D; | |
| const GLint kLevel = 0; | |
| const GLint kXOffset = 0; | |
| const GLint kYOffset = 0; | |
| const GLint kZOffset = 0; | |
| const GLenum kFormat = GL_RGB; | |
| const GLenum kType = GL_UNSIGNED_BYTE; | |
| const GLint kPixelStoreUnpackAlignment = 4; | |
| const GLsizei kWidth = 3; | |
| const GLsizei kDepth = 2; | |
| uint32_t size = 0; | |
| uint32_t unpadded_row_size = 0; | |
| uint32_t padded_row_size = 0; | |
| ASSERT_TRUE(GLES2Util::ComputeImageDataSizes( | |
| kWidth, 2, 1, kFormat, kType, kPixelStoreUnpackAlignment, | |
| &size, &unpadded_row_size, &padded_row_size)); | |
| const GLsizei kHeight = MaxTransferBufferSize() / padded_row_size + 2; | |
| ASSERT_TRUE(GLES2Util::ComputeImageDataSizes( | |
| kWidth, kHeight, kDepth, kFormat, kType, kPixelStoreUnpackAlignment, | |
| &size, nullptr, nullptr)); | |
| uint32_t first_size = (kHeight - 2) * padded_row_size; | |
| uint32_t second_size = 2 * padded_row_size; | |
| uint32_t third_size = first_size; | |
| uint32_t fourth_size = second_size - (padded_row_size - unpadded_row_size); | |
| EXPECT_EQ(size, first_size + second_size + third_size + fourth_size); | |
| auto pixels = base::HeapArray<uint8_t>::Uninit(size); | |
| for (uint32_t ii = 0; ii < size; ++ii) { | |
| pixels[ii] = static_cast<uint8_t>(ii); | |
| } | |
| ExpectedMemoryInfo mem1_1 = GetExpectedMemory(first_size); | |
| ExpectedMemoryInfo mem1_2 = GetExpectedMemory(second_size); | |
| ExpectedMemoryInfo mem2_1 = GetExpectedMemory(third_size); | |
| ExpectedMemoryInfo mem2_2 = GetExpectedMemory(fourth_size); | |
| Cmds expected; | |
| expected.tex_sub_image_3d1_1.Init( | |
| kTarget, kLevel, kXOffset, kYOffset, kZOffset, | |
| kWidth, kHeight - 2, 1, kFormat, kType, | |
| mem1_1.id, mem1_1.offset, GL_FALSE); | |
| expected.tex_sub_image_3d1_2.Init( | |
| kTarget, kLevel, kXOffset, kYOffset + kHeight - 2, kZOffset, | |
| kWidth, 2, 1, kFormat, kType, mem1_2.id, mem1_2.offset, GL_FALSE); | |
| expected.tex_sub_image_3d2_1.Init( | |
| kTarget, kLevel, kXOffset, kYOffset, kZOffset + 1, | |
| kWidth, kHeight - 2, 1, kFormat, kType, | |
| mem2_1.id, mem2_1.offset, GL_FALSE); | |
| expected.tex_sub_image_3d2_2.Init( | |
| kTarget, kLevel, kXOffset, kYOffset + kHeight - 2, kZOffset + 1, | |
| kWidth, 2, 1, kFormat, kType, mem2_2.id, mem2_2.offset, GL_FALSE); | |
| expected.set_token1.Init(GetNextToken()); | |
| expected.set_token2.Init(GetNextToken()); | |
| expected.set_token3.Init(GetNextToken()); | |
| gl_->TexSubImage3D(kTarget, kLevel, kXOffset, kYOffset, kZOffset, kWidth, | |
| kHeight, kDepth, kFormat, kType, pixels.data()); | |
| UNSAFE_TODO(EXPECT_EQ(0, memcmp(&expected, commands_, sizeof(expected)))); | |
| uint32_t offset_to_last = first_size + second_size + third_size; | |
| UNSAFE_TODO(EXPECT_TRUE(CheckRect( | |
| kWidth, 2, kFormat, kType, kPixelStoreUnpackAlignment, | |
| reinterpret_cast<uint8_t*>(pixels.data()) + offset_to_last, mem2_2.ptr))); | |
| } | |
| // glGen* Ids must not be reused until glDelete* commands have been | |
| // flushed by glFlush. | |
| TEST_F(GLES2ImplementationStrictSharedTest, FlushGenerationTestBuffers) { | |
| FlushGenerationTest<GenBuffersAPI>(); | |
| } | |
| TEST_F(GLES2ImplementationStrictSharedTest, FlushGenerationTestRenderbuffers) { | |
| FlushGenerationTest<GenRenderbuffersAPI>(); | |
| } | |
| TEST_F(GLES2ImplementationStrictSharedTest, FlushGenerationTestTextures) { | |
| FlushGenerationTest<GenTexturesAPI>(); | |
| } | |
| // glGen* Ids must not be reused cross-context until glDelete* commands are | |
| // flushed by glFlush, and the Ids are lazily freed after. | |
| TEST_F(GLES2ImplementationStrictSharedTest, CrossContextGenerationTestBuffers) { | |
| CrossContextGenerationTest<GenBuffersAPI>(); | |
| } | |
| TEST_F(GLES2ImplementationStrictSharedTest, | |
| CrossContextGenerationTestRenderbuffers) { | |
| CrossContextGenerationTest<GenRenderbuffersAPI>(); | |
| } | |
| TEST_F(GLES2ImplementationStrictSharedTest, | |
| CrossContextGenerationTestTextures) { | |
| CrossContextGenerationTest<GenTexturesAPI>(); | |
| } | |
| // Test Delete which causes auto flush. Tests a regression case that occurred | |
| // in testing. | |
| TEST_F(GLES2ImplementationStrictSharedTest, | |
| CrossContextGenerationAutoFlushTestBuffers) { | |
| CrossContextGenerationAutoFlushTest<GenBuffersAPI>(); | |
| } | |
| TEST_F(GLES2ImplementationStrictSharedTest, | |
| CrossContextGenerationAutoFlushTestRenderbuffers) { | |
| CrossContextGenerationAutoFlushTest<GenRenderbuffersAPI>(); | |
| } | |
| TEST_F(GLES2ImplementationStrictSharedTest, | |
| CrossContextGenerationAutoFlushTestTextures) { | |
| CrossContextGenerationAutoFlushTest<GenTexturesAPI>(); | |
| } | |
| // Test deleting an invalid ID. | |
| TEST_F(GLES2ImplementationStrictSharedTest, | |
| DeletingInvalidIdGeneratesErrorBuffers) { | |
| DeletingInvalidIdGeneratesError<GenBuffersAPI>(); | |
| } | |
| TEST_F(GLES2ImplementationStrictSharedTest, | |
| DeletingInvalidIdGeneratesErrorRenderbuffers) { | |
| DeletingInvalidIdGeneratesError<GenRenderbuffersAPI>(); | |
| } | |
| TEST_F(GLES2ImplementationStrictSharedTest, | |
| DeletingInvalidIdGeneratesErrorTextures) { | |
| DeletingInvalidIdGeneratesError<GenTexturesAPI>(); | |
| } | |
| // Test double-deleting the same ID. | |
| TEST_F(GLES2ImplementationStrictSharedTest, | |
| DoubleDeletingIdGeneratesErrorBuffers) { | |
| DoubleDeletingIdGeneratesError<GenBuffersAPI>(); | |
| } | |
| TEST_F(GLES2ImplementationStrictSharedTest, | |
| DoubleDeletingIdGeneratesErrorRenderbuffers) { | |
| DoubleDeletingIdGeneratesError<GenRenderbuffersAPI>(); | |
| } | |
| TEST_F(GLES2ImplementationStrictSharedTest, | |
| DoubleDeletingIdGeneratesErrorTextures) { | |
| DoubleDeletingIdGeneratesError<GenTexturesAPI>(); | |
| } | |
| TEST_F(GLES2ImplementationTest, GetString) { | |
| const uint32_t kBucketId = GLES2Implementation::kResultBucketId; | |
| const Str7 kString = {"foobar"}; | |
| // GL_CHROMIUM_map_sub is hard coded into GLES2Implementation. | |
| const char* expected_str = | |
| "foobar " | |
| "GL_CHROMIUM_map_sub " | |
| "GL_CHROMIUM_ordering_barrier " | |
| "GL_CHROMIUM_sync_point " | |
| "GL_EXT_unpack_subimage"; | |
| const char kBad = 0x12; | |
| struct Cmds { | |
| cmd::SetBucketSize set_bucket_size1; | |
| cmds::GetString get_string; | |
| cmd::GetBucketStart get_bucket_start; | |
| cmd::SetToken set_token1; | |
| cmd::SetBucketSize set_bucket_size2; | |
| }; | |
| ExpectedMemoryInfo mem1 = GetExpectedMemory(MaxTransferBufferSize()); | |
| ExpectedMemoryInfo result1 = | |
| GetExpectedResultMemory(sizeof(cmd::GetBucketStart::Result)); | |
| Cmds expected; | |
| expected.set_bucket_size1.Init(kBucketId, 0); | |
| expected.get_string.Init(GL_EXTENSIONS, kBucketId); | |
| expected.get_bucket_start.Init( | |
| kBucketId, result1.id, result1.offset, | |
| MaxTransferBufferSize(), mem1.id, mem1.offset); | |
| expected.set_token1.Init(GetNextToken()); | |
| expected.set_bucket_size2.Init(kBucketId, 0); | |
| char buf[sizeof(kString) + 1]; | |
| UNSAFE_TODO(memset(buf, kBad, sizeof(buf))); | |
| EXPECT_CALL(*command_buffer(), OnFlush()) | |
| .WillOnce(DoAll(SetMemory(result1.ptr, uint32_t(sizeof(kString))), | |
| SetMemory(mem1.ptr, kString))) | |
| .RetiresOnSaturation(); | |
| const GLubyte* result = gl_->GetString(GL_EXTENSIONS); | |
| UNSAFE_TODO(EXPECT_EQ(0, memcmp(&expected, commands_, sizeof(expected)))); | |
| EXPECT_STREQ(expected_str, reinterpret_cast<const char*>(result)); | |
| } | |
| TEST_F(GLES2ImplementationTest, CreateProgram) { | |
| struct Cmds { | |
| cmds::CreateProgram cmd; | |
| }; | |
| Cmds expected; | |
| expected.cmd.Init(kProgramsAndShadersStartId); | |
| GLuint id = gl_->CreateProgram(); | |
| UNSAFE_TODO(EXPECT_EQ(0, memcmp(&expected, commands_, sizeof(expected)))); | |
| EXPECT_EQ(kProgramsAndShadersStartId, id); | |
| } | |
| TEST_F(GLES2ImplementationTest, BufferDataLargerThanTransferBuffer) { | |
| struct Cmds { | |
| cmds::BufferData set_size; | |
| cmds::BufferSubData copy_data1; | |
| cmd::SetToken set_token1; | |
| cmds::BufferSubData copy_data2; | |
| cmd::SetToken set_token2; | |
| }; | |
| const unsigned kUsableSize = | |
| kTransferBufferSize - GLES2Implementation::kStartingOffset; | |
| uint8_t buf[kUsableSize * 2] = { | |
| 0, | |
| }; | |
| ExpectedMemoryInfo mem1 = GetExpectedMemory(kUsableSize); | |
| ExpectedMemoryInfo mem2 = GetExpectedMemory(kUsableSize); | |
| Cmds expected; | |
| expected.set_size.Init(GL_ARRAY_BUFFER, std::size(buf), 0, 0, | |
| GL_DYNAMIC_DRAW); | |
| expected.copy_data1.Init( | |
| GL_ARRAY_BUFFER, 0, kUsableSize, mem1.id, mem1.offset); | |
| expected.set_token1.Init(GetNextToken()); | |
| expected.copy_data2.Init( | |
| GL_ARRAY_BUFFER, kUsableSize, kUsableSize, mem2.id, mem2.offset); | |
| expected.set_token2.Init(GetNextToken()); | |
| gl_->BufferData(GL_ARRAY_BUFFER, std::size(buf), buf, GL_DYNAMIC_DRAW); | |
| UNSAFE_TODO(EXPECT_EQ(0, memcmp(&expected, commands_, sizeof(expected)))); | |
| } | |
| TEST_F(GLES2ImplementationTest, MultiDrawArraysWEBGLLargerThanTransferBuffer) { | |
| struct Cmds { | |
| cmds::MultiDrawBeginCHROMIUM begin; | |
| cmds::MultiDrawArraysCHROMIUM draw1; | |
| cmd::SetToken set_token1; | |
| cmds::MultiDrawArraysCHROMIUM draw2; | |
| cmd::SetToken set_token2; | |
| cmds::MultiDrawEndCHROMIUM end; | |
| }; | |
| const unsigned kUsableSize = | |
| kTransferBufferSize - GLES2Implementation::kStartingOffset; | |
| const unsigned kDrawCount = kUsableSize / sizeof(int); | |
| const unsigned kChunkDrawCount = kDrawCount / 2; | |
| const unsigned kCountsOffset = kChunkDrawCount * sizeof(int); | |
| GLint firsts[kDrawCount] = {}; | |
| GLsizei counts[kDrawCount] = {}; | |
| ExpectedMemoryInfo mem1 = GetExpectedMemory(kUsableSize); | |
| ExpectedMemoryInfo mem2 = GetExpectedMemory(kUsableSize); | |
| Cmds expected; | |
| expected.begin.Init(kDrawCount); | |
| expected.draw1.Init(GL_TRIANGLES, mem1.id, mem1.offset, mem1.id, | |
| mem1.offset + kCountsOffset, kChunkDrawCount); | |
| expected.set_token1.Init(GetNextToken()); | |
| expected.draw2.Init(GL_TRIANGLES, mem2.id, mem2.offset, mem2.id, | |
| mem2.offset + kCountsOffset, kChunkDrawCount); | |
| expected.set_token2.Init(GetNextToken()); | |
| expected.end.Init(); | |
| gl_->MultiDrawArraysWEBGL(GL_TRIANGLES, firsts, counts, kDrawCount); | |
| UNSAFE_TODO(EXPECT_EQ(0, memcmp(&expected, commands_, sizeof(expected)))); | |
| } | |
| TEST_F(GLES2ImplementationTest, CapabilitiesAreCached) { | |
| static const auto kStates = std::to_array<GLenum>({ | |
| GL_DITHER, | |
| GL_BLEND, | |
| GL_CULL_FACE, | |
| GL_DEPTH_TEST, | |
| GL_POLYGON_OFFSET_FILL, | |
| GL_SAMPLE_ALPHA_TO_COVERAGE, | |
| GL_SAMPLE_COVERAGE, | |
| GL_SCISSOR_TEST, | |
| GL_STENCIL_TEST, | |
| }); | |
| struct Cmds { | |
| cmds::Enable enable_cmd; | |
| }; | |
| Cmds expected; | |
| for (size_t ii = 0; ii < std::size(kStates); ++ii) { | |
| GLenum state = kStates[ii]; | |
| expected.enable_cmd.Init(state); | |
| GLboolean result = gl_->IsEnabled(state); | |
| EXPECT_EQ(static_cast<GLboolean>(ii == 0), result); | |
| EXPECT_TRUE(NoCommandsWritten()); | |
| const void* commands = GetPut(); | |
| if (!result) { | |
| gl_->Enable(state); | |
| UNSAFE_TODO(EXPECT_EQ(0, memcmp(&expected, commands, sizeof(expected)))); | |
| } | |
| ClearCommands(); | |
| result = gl_->IsEnabled(state); | |
| EXPECT_TRUE(result); | |
| EXPECT_TRUE(NoCommandsWritten()); | |
| } | |
| } | |
| TEST_F(GLES2ImplementationTest, BindVertexArrayOES) { | |
| GLuint id = 0; | |
| gl_->GenVertexArraysOES(1, &id); | |
| ClearCommands(); | |
| struct Cmds { | |
| cmds::BindVertexArrayOES cmd; | |
| }; | |
| Cmds expected; | |
| expected.cmd.Init(id); | |
| const void* commands = GetPut(); | |
| gl_->BindVertexArrayOES(id); | |
| UNSAFE_TODO(EXPECT_EQ(0, memcmp(&expected, commands, sizeof(expected)))); | |
| ClearCommands(); | |
| gl_->BindVertexArrayOES(id); | |
| EXPECT_TRUE(NoCommandsWritten()); | |
| } | |
| TEST_F(GLES2ImplementationTest, BeginEndQueryEXT) { | |
| // Test GetQueryivEXT returns 0 if no current query. | |
| GLint param = -1; | |
| gl_->GetQueryivEXT(GL_ANY_SAMPLES_PASSED_EXT, GL_CURRENT_QUERY_EXT, ¶m); | |
| EXPECT_EQ(0, param); | |
| GLuint expected_ids[2] = { 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]); | |
| GLuint ids[std::size(expected_ids)] = { | |
| 0, | |
| }; | |
| 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_ANY_SAMPLES_PASSED_EXT, 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_ANY_SAMPLES_PASSED_EXT, id1); | |
| QueryTracker::Query* query = GetQuery(id1); | |
| ASSERT_TRUE(query != nullptr); | |
| expected_begin_cmds.begin_query.Init( | |
| GL_ANY_SAMPLES_PASSED_EXT, id1, query->shm_id(), query->shm_offset()); | |
| UNSAFE_TODO(EXPECT_EQ( | |
| 0, memcmp(&expected_begin_cmds, commands, sizeof(expected_begin_cmds)))); | |
| ClearCommands(); | |
| // Test GetQueryivEXT returns id. | |
| param = -1; | |
| gl_->GetQueryivEXT(GL_ANY_SAMPLES_PASSED_EXT, GL_CURRENT_QUERY_EXT, ¶m); | |
| EXPECT_EQ(id1, static_cast<GLuint>(param)); | |
| gl_->GetQueryivEXT( | |
| GL_ANY_SAMPLES_PASSED_CONSERVATIVE_EXT, GL_CURRENT_QUERY_EXT, ¶m); | |
| EXPECT_EQ(0, param); | |
| // Test BeginQueryEXT fails if between Begin/End. | |
| gl_->BeginQueryEXT(GL_ANY_SAMPLES_PASSED_EXT, id2); | |
| EXPECT_TRUE(NoCommandsWritten()); | |
| EXPECT_EQ(GL_INVALID_OPERATION, CheckError()); | |
| // Test EndQueryEXT fails if target not same as current query. | |
| ClearCommands(); | |
| gl_->EndQueryEXT(GL_ANY_SAMPLES_PASSED_CONSERVATIVE_EXT); | |
| EXPECT_TRUE(NoCommandsWritten()); | |
| EXPECT_EQ(GL_INVALID_OPERATION, CheckError()); | |
| // Test EndQueryEXT sends command | |
| struct EndCmds { | |
| cmds::EndQueryEXT end_query; | |
| }; | |
| commands = GetPut(); | |
| gl_->EndQueryEXT(GL_ANY_SAMPLES_PASSED_EXT); | |
| EndCmds expected_end_cmds; | |
| expected_end_cmds.end_query.Init( | |
| GL_ANY_SAMPLES_PASSED_EXT, 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_ANY_SAMPLES_PASSED_EXT); | |
| 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_ANY_SAMPLES_PASSED_EXT, id1); | |
| EXPECT_EQ(old_submit_count, query->submit_count()); | |
| commands = GetPut(); | |
| gl_->EndQueryEXT(GL_ANY_SAMPLES_PASSED_EXT); | |
| EXPECT_NE(old_submit_count, query->submit_count()); | |
| expected_end_cmds.end_query.Init( | |
| GL_ANY_SAMPLES_PASSED_EXT, query->submit_count()); | |
| UNSAFE_TODO(EXPECT_EQ( | |
| 0, memcmp(&expected_end_cmds, commands, sizeof(expected_end_cmds)))); | |
| // Test BeginQueryEXT fails if target changed. | |
| ClearCommands(); | |
| gl_->BeginQueryEXT(GL_ANY_SAMPLES_PASSED_CONSERVATIVE_EXT, id1); | |
| EXPECT_TRUE(NoCommandsWritten()); | |
| EXPECT_EQ(GL_INVALID_OPERATION, CheckError()); | |
| // 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); | |
| // Test GetQueryObjectui64vEXT fails if unused id | |
| GLuint64 available2 = 0xBDu; | |
| ClearCommands(); | |
| gl_->GetQueryObjectui64vEXT(id2, GL_QUERY_RESULT_AVAILABLE_EXT, &available2); | |
| EXPECT_TRUE(NoCommandsWritten()); | |
| EXPECT_EQ(0xBDu, available2); | |
| EXPECT_EQ(GL_INVALID_OPERATION, CheckError()); | |
| // Test GetQueryObjectui64vEXT fails if bad id | |
| ClearCommands(); | |
| gl_->GetQueryObjectui64vEXT(4567, GL_QUERY_RESULT_AVAILABLE_EXT, &available2); | |
| EXPECT_TRUE(NoCommandsWritten()); | |
| EXPECT_EQ(0xBDu, available2); | |
| EXPECT_EQ(GL_INVALID_OPERATION, CheckError()); | |
| // Test GetQueryObjectui64vEXT CheckResultsAvailable | |
| ClearCommands(); | |
| gl_->GetQueryObjectui64vEXT(id1, GL_QUERY_RESULT_AVAILABLE_EXT, &available2); | |
| EXPECT_EQ(0u, available2); | |
| } | |
| TEST_F(GLES2ImplementationManualInitTest, BadQueryTargets) { | |
| ContextInitOptions init_options; | |
| init_options.sync_query = false; | |
| init_options.occlusion_query_boolean = false; | |
| init_options.timer_queries = 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(GL_ANY_SAMPLES_PASSED, id); | |
| EXPECT_EQ(GL_INVALID_OPERATION, CheckError()); | |
| EXPECT_EQ(nullptr, GetQuery(id)); | |
| gl_->BeginQueryEXT(GL_ANY_SAMPLES_PASSED_CONSERVATIVE, id); | |
| EXPECT_EQ(GL_INVALID_OPERATION, CheckError()); | |
| EXPECT_EQ(nullptr, GetQuery(id)); | |
| gl_->BeginQueryEXT(GL_TIME_ELAPSED_EXT, 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)); | |
| gl_->QueryCounterEXT(id, GL_TIMESTAMP_EXT); | |
| EXPECT_EQ(GL_INVALID_OPERATION, CheckError()); | |
| EXPECT_EQ(nullptr, GetQuery(id)); | |
| gl_->QueryCounterEXT(id, 0x123); | |
| EXPECT_EQ(GL_INVALID_ENUM, CheckError()); | |
| EXPECT_EQ(nullptr, GetQuery(id)); | |
| } | |
| TEST_F(GLES2ImplementationTest, SetDisjointSync) { | |
| struct SetDisjointSyncCmd { | |
| cmds::SetDisjointValueSyncCHROMIUM disjoint_sync; | |
| }; | |
| SetDisjointSyncCmd expected_disjoint_sync_cmd; | |
| const void* commands = GetPut(); | |
| gl_->SetDisjointValueSyncCHROMIUM(); | |
| expected_disjoint_sync_cmd.disjoint_sync.Init( | |
| GetQueryTracker()->DisjointCountSyncShmID(), | |
| GetQueryTracker()->DisjointCountSyncShmOffset()); | |
| UNSAFE_TODO(EXPECT_EQ(0, memcmp(&expected_disjoint_sync_cmd, commands, | |
| sizeof(expected_disjoint_sync_cmd)))); | |
| } | |
| TEST_F(GLES2ImplementationTest, QueryCounterEXT) { | |
| // These must match what's actually genned. | |
| GLuint expected_ids[2] = {1, 2}; | |
| struct GenCmds { | |
| cmds::GenQueriesEXTImmediate gen; | |
| GLuint data[2]; | |
| }; | |
| GenCmds expected_gen_cmds; | |
| expected_gen_cmds.gen.Init(std::size(expected_ids), &expected_ids[0]); | |
| GLuint ids[std::size(expected_ids)] = { | |
| 0, | |
| }; | |
| 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(); | |
| // Make sure disjoint value is synchronized already. | |
| gl_->SetDisjointValueSyncCHROMIUM(); | |
| ClearCommands(); | |
| // Test QueryCounterEXT fails if id = 0. | |
| gl_->QueryCounterEXT(0, GL_TIMESTAMP_EXT); | |
| EXPECT_TRUE(NoCommandsWritten()); | |
| EXPECT_EQ(GL_INVALID_OPERATION, CheckError()); | |
| // Test QueryCounterEXT fails if target is unknown. | |
| ClearCommands(); | |
| gl_->QueryCounterEXT(id1, GL_TIME_ELAPSED_EXT); | |
| EXPECT_TRUE(NoCommandsWritten()); | |
| EXPECT_EQ(GL_INVALID_ENUM, CheckError()); | |
| // Test QueryCounterEXT inserts command. | |
| struct QueryCounterCmds { | |
| cmds::QueryCounterEXT query_counter; | |
| }; | |
| QueryCounterCmds expected_query_counter_cmds; | |
| const void* commands = GetPut(); | |
| gl_->QueryCounterEXT(id1, GL_TIMESTAMP_EXT); | |
| EXPECT_EQ(GL_NO_ERROR, CheckError()); | |
| QueryTracker::Query* query = GetQuery(id1); | |
| ASSERT_TRUE(query != nullptr); | |
| expected_query_counter_cmds.query_counter.Init( | |
| id1, GL_TIMESTAMP_EXT, query->shm_id(), query->shm_offset(), | |
| query->submit_count()); | |
| UNSAFE_TODO(EXPECT_EQ(0, memcmp(&expected_query_counter_cmds, commands, | |
| sizeof(expected_query_counter_cmds)))); | |
| // Test 2nd QueryCounterEXT succeeds. | |
| commands = GetPut(); | |
| gl_->QueryCounterEXT(id2, GL_TIMESTAMP_EXT); | |
| EXPECT_EQ(GL_NO_ERROR, CheckError()); | |
| QueryTracker::Query* query2 = GetQuery(id2); | |
| ASSERT_TRUE(query2 != nullptr); | |
| expected_query_counter_cmds.query_counter.Init( | |
| id2, GL_TIMESTAMP_EXT, query2->shm_id(), query2->shm_offset(), | |
| query2->submit_count()); | |
| UNSAFE_TODO(EXPECT_EQ(0, memcmp(&expected_query_counter_cmds, commands, | |
| sizeof(expected_query_counter_cmds)))); | |
| ClearCommands(); | |
| // Test QueryCounterEXT increments count. | |
| base::subtle::Atomic32 old_submit_count = query->submit_count(); | |
| commands = GetPut(); | |
| gl_->QueryCounterEXT(id1, GL_TIMESTAMP_EXT); | |
| EXPECT_EQ(GL_NO_ERROR, CheckError()); | |
| EXPECT_NE(old_submit_count, query->submit_count()); | |
| expected_query_counter_cmds.query_counter.Init( | |
| id1, GL_TIMESTAMP_EXT, query->shm_id(), query->shm_offset(), | |
| query->submit_count()); | |
| UNSAFE_TODO(EXPECT_EQ(0, memcmp(&expected_query_counter_cmds, commands, | |
| sizeof(expected_query_counter_cmds)))); | |
| ClearCommands(); | |
| // Test GetQueryObjectuivEXT CheckResultsAvailable. | |
| GLuint available = 0xBDu; | |
| ClearCommands(); | |
| gl_->GetQueryObjectuivEXT(id1, GL_QUERY_RESULT_AVAILABLE_EXT, &available); | |
| EXPECT_EQ(0u, available); | |
| // Test GetQueryObjectui64vEXT CheckResultsAvailable. | |
| GLuint64 available2 = 0xBDu; | |
| ClearCommands(); | |
| gl_->GetQueryObjectui64vEXT(id1, GL_QUERY_RESULT_AVAILABLE_EXT, &available2); | |
| EXPECT_EQ(0u, available2); | |
| } | |
| TEST_F(GLES2ImplementationTest, ErrorQuery) { | |
| GLuint id = 0; | |
| gl_->GenQueriesEXT(1, &id); | |
| ClearCommands(); | |
| // Test BeginQueryEXT does NOT insert commands. | |
| gl_->BeginQueryEXT(GL_GET_ERROR_QUERY_CHROMIUM, id); | |
| EXPECT_TRUE(NoCommandsWritten()); | |
| QueryTracker::Query* query = GetQuery(id); | |
| ASSERT_TRUE(query != nullptr); | |
| // Test EndQueryEXT sends both begin and end command | |
| struct EndCmds { | |
| cmds::BeginQueryEXT begin_query; | |
| cmds::EndQueryEXT end_query; | |
| }; | |
| const void* commands = GetPut(); | |
| gl_->EndQueryEXT(GL_GET_ERROR_QUERY_CHROMIUM); | |
| EndCmds expected_end_cmds; | |
| expected_end_cmds.begin_query.Init( | |
| GL_GET_ERROR_QUERY_CHROMIUM, id, query->shm_id(), query->shm_offset()); | |
| expected_end_cmds.end_query.Init( | |
| GL_GET_ERROR_QUERY_CHROMIUM, query->submit_count()); | |
| UNSAFE_TODO(EXPECT_EQ( | |
| 0, memcmp(&expected_end_cmds, commands, sizeof(expected_end_cmds)))); | |
| ClearCommands(); | |
| // Check result is not yet available. | |
| GLuint available = 0xBDu; | |
| gl_->GetQueryObjectuivEXT(id, GL_QUERY_RESULT_AVAILABLE_EXT, &available); | |
| EXPECT_TRUE(NoCommandsWritten()); | |
| EXPECT_EQ(0u, available); | |
| // Test no commands are sent if there is a client side error. | |
| // Generate a client side error | |
| gl_->ActiveTexture(GL_TEXTURE0 - 1); | |
| gl_->BeginQueryEXT(GL_GET_ERROR_QUERY_CHROMIUM, id); | |
| gl_->EndQueryEXT(GL_GET_ERROR_QUERY_CHROMIUM); | |
| EXPECT_TRUE(NoCommandsWritten()); | |
| // Check result is available. | |
| gl_->GetQueryObjectuivEXT(id, GL_QUERY_RESULT_AVAILABLE_EXT, &available); | |
| EXPECT_TRUE(NoCommandsWritten()); | |
| EXPECT_NE(0u, available); | |
| // Check result. | |
| GLuint result = 0xBDu; | |
| gl_->GetQueryObjectuivEXT(id, GL_QUERY_RESULT_EXT, &result); | |
| EXPECT_TRUE(NoCommandsWritten()); | |
| EXPECT_EQ(static_cast<GLuint>(GL_INVALID_ENUM), result); | |
| } | |
| TEST_F(GLES2ImplementationTest, VertexArrays) { | |
| const GLuint kAttribIndex1 = 1; | |
| const GLint kNumComponents1 = 3; | |
| const GLsizei kClientStride = 12; | |
| GLuint id = 0; | |
| gl_->GenVertexArraysOES(1, &id); | |
| ClearCommands(); | |
| gl_->BindVertexArrayOES(id); | |
| // Test that VertexAttribPointer cannot be called with a bound buffer of 0 | |
| // unless the offset is nullptr | |
| gl_->BindBuffer(GL_ARRAY_BUFFER, 0); | |
| gl_->VertexAttribPointer( | |
| kAttribIndex1, kNumComponents1, GL_FLOAT, GL_FALSE, kClientStride, | |
| reinterpret_cast<const void*>(4)); | |
| EXPECT_EQ(GL_INVALID_OPERATION, CheckError()); | |
| gl_->VertexAttribPointer(kAttribIndex1, kNumComponents1, GL_FLOAT, GL_FALSE, | |
| kClientStride, nullptr); | |
| EXPECT_EQ(GL_NO_ERROR, CheckError()); | |
| } | |
| TEST_F(GLES2ImplementationTest, Disable) { | |
| struct Cmds { | |
| cmds::Disable cmd; | |
| }; | |
| Cmds expected; | |
| expected.cmd.Init(GL_DITHER); // Note: DITHER defaults to enabled. | |
| gl_->Disable(GL_DITHER); | |
| UNSAFE_TODO(EXPECT_EQ(0, memcmp(&expected, commands_, sizeof(expected)))); | |
| // Check it's cached and not called again. | |
| ClearCommands(); | |
| gl_->Disable(GL_DITHER); | |
| EXPECT_TRUE(NoCommandsWritten()); | |
| } | |
| TEST_F(GLES2ImplementationTest, Enable) { | |
| struct Cmds { | |
| cmds::Enable cmd; | |
| }; | |
| Cmds expected; | |
| expected.cmd.Init(GL_BLEND); // Note: BLEND defaults to disabled. | |
| gl_->Enable(GL_BLEND); | |
| UNSAFE_TODO(EXPECT_EQ(0, memcmp(&expected, commands_, sizeof(expected)))); | |
| // Check it's cached and not called again. | |
| ClearCommands(); | |
| gl_->Enable(GL_BLEND); | |
| EXPECT_TRUE(NoCommandsWritten()); | |
| } | |
| TEST_F(GLES2ImplementationTest, CreateAndTexStorage2DSharedImageCHROMIUM) { | |
| struct Cmds { | |
| cmds::CreateAndTexStorage2DSharedImageINTERNALImmediate cmd; | |
| GLbyte data[GL_MAILBOX_SIZE_CHROMIUM]; | |
| }; | |
| Mailbox mailbox = Mailbox::Generate(); | |
| Cmds expected; | |
| expected.cmd.Init(kTexturesStartId, mailbox.name); | |
| GLuint id = gl_->CreateAndTexStorage2DSharedImageCHROMIUM(mailbox.name); | |
| UNSAFE_TODO(EXPECT_EQ(0, memcmp(&expected, commands_, sizeof(expected)))); | |
| EXPECT_EQ(kTexturesStartId, id); | |
| } | |
| TEST_F(GLES2ImplementationTest, LimitSizeAndOffsetTo32Bit) { | |
| GLsizeiptr size; | |
| GLintptr offset; | |
| if (sizeof(size) <= 4 || sizeof(offset) <= 4) | |
| return; | |
| // The below two casts should be no-op, as we return early if | |
| // it's 32-bit system. | |
| int64_t value64 = 0x100000000; | |
| size = static_cast<GLsizeiptr>(value64); | |
| offset = static_cast<GLintptr>(value64); | |
| const char kSizeOverflowMessage[] = "size more than 32-bit"; | |
| const char kOffsetOverflowMessage[] = "offset more than 32-bit"; | |
| const GLfloat buf[] = { 1.0, 1.0, 1.0, 1.0 }; | |
| const GLubyte indices[] = { 0 }; | |
| GLuint buffer_ids[2]; | |
| gl_->GenBuffers(2, buffer_ids); | |
| gl_->BindBuffer(GL_ARRAY_BUFFER, buffer_ids[0]); | |
| gl_->BindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffer_ids[1]); | |
| EXPECT_EQ(GL_NO_ERROR, CheckError()); | |
| // Call BufferData() should succeed with legal paramaters. | |
| gl_->BufferData(GL_ARRAY_BUFFER, sizeof(buf), buf, GL_DYNAMIC_DRAW); | |
| gl_->BufferData( | |
| GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_DYNAMIC_DRAW); | |
| EXPECT_EQ(GL_NO_ERROR, CheckError()); | |
| // BufferData: size | |
| gl_->BufferData(GL_ARRAY_BUFFER, size, buf, GL_DYNAMIC_DRAW); | |
| EXPECT_EQ(GL_INVALID_OPERATION, CheckError()); | |
| EXPECT_STREQ(kSizeOverflowMessage, GetLastError().c_str()); | |
| // Call BufferSubData() should succeed with legal paramaters. | |
| gl_->BufferSubData(GL_ARRAY_BUFFER, 0, sizeof(buf[0]), buf); | |
| EXPECT_EQ(GL_NO_ERROR, CheckError()); | |
| // BufferSubData: offset | |
| gl_->BufferSubData(GL_ARRAY_BUFFER, offset, 1, buf); | |
| EXPECT_EQ(GL_INVALID_OPERATION, CheckError()); | |
| EXPECT_STREQ(kOffsetOverflowMessage, GetLastError().c_str()); | |
| // BufferSubData: size | |
| EXPECT_EQ(GL_NO_ERROR, CheckError()); | |
| gl_->BufferSubData(GL_ARRAY_BUFFER, 0, size, buf); | |
| EXPECT_EQ(GL_INVALID_OPERATION, CheckError()); | |
| EXPECT_STREQ(kSizeOverflowMessage, GetLastError().c_str()); | |
| // Call MapBufferSubDataCHROMIUM() should succeed with legal paramaters. | |
| void* mem = | |
| gl_->MapBufferSubDataCHROMIUM(GL_ARRAY_BUFFER, 0, 1, GL_WRITE_ONLY); | |
| EXPECT_TRUE(nullptr != mem); | |
| EXPECT_EQ(GL_NO_ERROR, CheckError()); | |
| gl_->UnmapBufferSubDataCHROMIUM(mem); | |
| // MapBufferSubDataCHROMIUM: offset | |
| EXPECT_TRUE(nullptr == gl_->MapBufferSubDataCHROMIUM(GL_ARRAY_BUFFER, offset, | |
| 1, GL_WRITE_ONLY)); | |
| EXPECT_EQ(GL_INVALID_OPERATION, CheckError()); | |
| EXPECT_STREQ(kOffsetOverflowMessage, GetLastError().c_str()); | |
| // MapBufferSubDataCHROMIUM: size | |
| EXPECT_EQ(GL_NO_ERROR, CheckError()); | |
| EXPECT_TRUE(nullptr == gl_->MapBufferSubDataCHROMIUM(GL_ARRAY_BUFFER, 0, size, | |
| GL_WRITE_ONLY)); | |
| EXPECT_EQ(GL_INVALID_OPERATION, CheckError()); | |
| EXPECT_STREQ(kSizeOverflowMessage, GetLastError().c_str()); | |
| // Call DrawElements() should succeed with legal paramaters. | |
| gl_->DrawElements(GL_POINTS, 1, GL_UNSIGNED_BYTE, nullptr); | |
| EXPECT_EQ(GL_NO_ERROR, CheckError()); | |
| // DrawElements: offset | |
| gl_->DrawElements( | |
| GL_POINTS, 1, GL_UNSIGNED_BYTE, reinterpret_cast<void*>(offset)); | |
| EXPECT_EQ(GL_INVALID_OPERATION, CheckError()); | |
| EXPECT_STREQ(kOffsetOverflowMessage, GetLastError().c_str()); | |
| // Call DrawElementsInstancedANGLE() should succeed with legal paramaters. | |
| gl_->DrawElementsInstancedANGLE(GL_POINTS, 1, GL_UNSIGNED_BYTE, nullptr, 1); | |
| EXPECT_EQ(GL_NO_ERROR, CheckError()); | |
| // DrawElementsInstancedANGLE: offset | |
| gl_->DrawElementsInstancedANGLE( | |
| GL_POINTS, 1, GL_UNSIGNED_BYTE, reinterpret_cast<void*>(offset), 1); | |
| EXPECT_EQ(GL_INVALID_OPERATION, CheckError()); | |
| EXPECT_STREQ(kOffsetOverflowMessage, GetLastError().c_str()); | |
| // Call VertexAttribPointer() should succeed with legal paramaters. | |
| const GLuint kAttribIndex = 1; | |
| const GLsizei kStride = 4; | |
| gl_->VertexAttribPointer(kAttribIndex, 1, GL_FLOAT, GL_FALSE, kStride, | |
| nullptr); | |
| EXPECT_EQ(GL_NO_ERROR, CheckError()); | |
| // VertexAttribPointer: offset | |
| gl_->VertexAttribPointer( | |
| kAttribIndex, 1, GL_FLOAT, GL_FALSE, kStride, | |
| reinterpret_cast<void*>(offset)); | |
| EXPECT_EQ(GL_INVALID_OPERATION, CheckError()); | |
| EXPECT_STREQ(kOffsetOverflowMessage, GetLastError().c_str()); | |
| } | |
| TEST_F(GLES2ImplementationTest, TraceBeginCHROMIUM) { | |
| const uint32_t kCategoryBucketId = GLES2Implementation::kResultBucketId; | |
| const uint32_t kNameBucketId = GLES2Implementation::kResultBucketId + 1; | |
| const std::string category_name = "test category"; | |
| const std::string trace_name = "test trace"; | |
| const size_t kPaddedString1Size = | |
| transfer_buffer_->RoundToAlignment(category_name.size() + 1); | |
| const size_t kPaddedString2Size = | |
| transfer_buffer_->RoundToAlignment(trace_name.size() + 1); | |
| gl_->TraceBeginCHROMIUM(category_name.c_str(), trace_name.c_str()); | |
| EXPECT_EQ(GL_NO_ERROR, CheckError()); | |
| struct Cmds { | |
| cmd::SetBucketSize category_size1; | |
| cmd::SetBucketData category_data; | |
| cmd::SetToken set_token1; | |
| cmd::SetBucketSize name_size1; | |
| cmd::SetBucketData name_data; | |
| cmd::SetToken set_token2; | |
| cmds::TraceBeginCHROMIUM trace_call_begin; | |
| cmd::SetBucketSize category_size2; | |
| cmd::SetBucketSize name_size2; | |
| }; | |
| ExpectedMemoryInfo mem1 = GetExpectedMemory(kPaddedString1Size); | |
| ExpectedMemoryInfo mem2 = GetExpectedMemory(kPaddedString2Size); | |
| ASSERT_STREQ(category_name.c_str(), reinterpret_cast<char*>(mem1.ptr)); | |
| ASSERT_STREQ(trace_name.c_str(), reinterpret_cast<char*>(mem2.ptr)); | |
| Cmds expected; | |
| expected.category_size1.Init(kCategoryBucketId, category_name.size() + 1); | |
| expected.category_data.Init( | |
| kCategoryBucketId, 0, category_name.size() + 1, mem1.id, mem1.offset); | |
| expected.set_token1.Init(GetNextToken()); | |
| expected.name_size1.Init(kNameBucketId, trace_name.size() + 1); | |
| expected.name_data.Init( | |
| kNameBucketId, 0, trace_name.size() + 1, mem2.id, mem2.offset); | |
| expected.set_token2.Init(GetNextToken()); | |
| expected.trace_call_begin.Init(kCategoryBucketId, kNameBucketId); | |
| expected.category_size2.Init(kCategoryBucketId, 0); | |
| expected.name_size2.Init(kNameBucketId, 0); | |
| UNSAFE_TODO(EXPECT_EQ(0, memcmp(&expected, commands_, sizeof(expected)))); | |
| } | |
| TEST_F(GLES2ImplementationTest, SetActiveURLCHROMIUM) { | |
| const uint32_t kURLBucketId = GLES2Implementation::kResultBucketId; | |
| const std::string url = "chrome://test"; | |
| const size_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()); | |
| } | |
| TEST_F(GLES2ImplementationTest, AllowNestedTracesCHROMIUM) { | |
| const std::string category1_name = "test category 1"; | |
| const std::string trace1_name = "test trace 1"; | |
| const std::string category2_name = "test category 2"; | |
| const std::string trace2_name = "test trace 2"; | |
| gl_->TraceBeginCHROMIUM(category1_name.c_str(), trace1_name.c_str()); | |
| EXPECT_EQ(GL_NO_ERROR, CheckError()); | |
| gl_->TraceBeginCHROMIUM(category2_name.c_str(), trace2_name.c_str()); | |
| EXPECT_EQ(GL_NO_ERROR, CheckError()); | |
| gl_->TraceEndCHROMIUM(); | |
| EXPECT_EQ(GL_NO_ERROR, CheckError()); | |
| gl_->TraceEndCHROMIUM(); | |
| EXPECT_EQ(GL_NO_ERROR, CheckError()); | |
| // No more corresponding begin tracer marker should error. | |
| gl_->TraceEndCHROMIUM(); | |
| EXPECT_EQ(GL_INVALID_OPERATION, CheckError()); | |
| } | |
| TEST_F(GLES2ImplementationTest, GenSyncTokenCHROMIUM) { | |
| 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_->GenSyncTokenCHROMIUM(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)); | |
| EXPECT_CALL(*gpu_control_, EnsureWorkVisible()); | |
| gl_->GenSyncTokenCHROMIUM(sync_token.GetData()); | |
| UNSAFE_TODO(EXPECT_EQ( | |
| 0, memcmp(&insert_fence_sync, commands, sizeof(insert_fence_sync)))); | |
| EXPECT_EQ(GL_NO_ERROR, CheckError()); | |
| EXPECT_TRUE(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(GLES2ImplementationTest, 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(GLES2ImplementationTest, 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(GLES2ImplementationTest, 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(GLES2ImplementationTest, 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(GLES2ImplementationTest, 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)); | |
| EXPECT_CALL(*gpu_control_, EnsureWorkVisible()); | |
| gl_->GenSyncTokenCHROMIUM(sync_token_data); | |
| EXPECT_CALL(*gpu_control_, WaitSyncToken(sync_token)); | |
| gl_->WaitSyncTokenCHROMIUM(sync_token_data); | |
| UNSAFE_TODO(EXPECT_EQ(0, memcmp(&expected, commands_, sizeof(expected)))); | |
| } | |
| TEST_F(GLES2ImplementationTest, 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()); | |
| } | |
| TEST_F(GLES2ImplementationTest, IsEnabled) { | |
| // If we use a valid enum, its state is cached on client side, so no command | |
| // is actually generated, and this test will fail. | |
| // TODO(zmo): it seems we never need the command. Maybe remove it. | |
| GLenum kCap = 1; | |
| struct Cmds { | |
| cmds::IsEnabled cmd; | |
| }; | |
| Cmds expected; | |
| ExpectedMemoryInfo result1 = | |
| GetExpectedResultMemory(sizeof(cmds::IsEnabled::Result)); | |
| expected.cmd.Init(kCap, result1.id, result1.offset); | |
| EXPECT_CALL(*command_buffer(), OnFlush()) | |
| .WillOnce(SetMemory(result1.ptr, uint32_t(GL_TRUE))) | |
| .RetiresOnSaturation(); | |
| GLboolean result = gl_->IsEnabled(kCap); | |
| UNSAFE_TODO(EXPECT_EQ(0, memcmp(&expected, commands_, sizeof(expected)))); | |
| EXPECT_TRUE(result); | |
| } | |
| TEST_F(GLES2ImplementationTest, ClientWaitSync) { | |
| const GLuint client_sync_id = 36; | |
| struct Cmds { | |
| cmds::ClientWaitSync cmd; | |
| }; | |
| Cmds expected; | |
| ExpectedMemoryInfo result1 = | |
| GetExpectedResultMemory(sizeof(cmds::ClientWaitSync::Result)); | |
| const GLuint64 kTimeout = 0xABCDEF0123456789; | |
| expected.cmd.Init(client_sync_id, GL_SYNC_FLUSH_COMMANDS_BIT, | |
| kTimeout, result1.id, result1.offset); | |
| EXPECT_CALL(*command_buffer(), OnFlush()) | |
| .WillOnce(SetMemory(result1.ptr, uint32_t(GL_CONDITION_SATISFIED))) | |
| .RetiresOnSaturation(); | |
| GLenum result = gl_->ClientWaitSync( | |
| reinterpret_cast<GLsync>(client_sync_id), GL_SYNC_FLUSH_COMMANDS_BIT, | |
| kTimeout); | |
| UNSAFE_TODO(EXPECT_EQ(0, memcmp(&expected, commands_, sizeof(expected)))); | |
| EXPECT_EQ(static_cast<GLenum>(GL_CONDITION_SATISFIED), result); | |
| } | |
| TEST_F(GLES2ImplementationTest, WaitSync) { | |
| const GLuint kClientSyncId = 36; | |
| struct Cmds { | |
| cmds::WaitSync cmd; | |
| }; | |
| Cmds expected; | |
| const GLuint64 kTimeout = GL_TIMEOUT_IGNORED; | |
| expected.cmd.Init(kClientSyncId, 0, kTimeout); | |
| gl_->WaitSync(reinterpret_cast<GLsync>(kClientSyncId), 0, kTimeout); | |
| UNSAFE_TODO(EXPECT_EQ(0, memcmp(&expected, commands_, sizeof(expected)))); | |
| } | |
| TEST_F(GLES2ImplementationTest, GetInternalformativ) { | |
| const GLint kNumSampleCounts = 8; | |
| struct Cmds { | |
| cmds::GetInternalformativ cmd; | |
| }; | |
| typedef cmds::GetInternalformativ::Result::Type ResultType; | |
| ResultType result = 0; | |
| Cmds expected; | |
| ExpectedMemoryInfo result1 = | |
| GetExpectedResultMemory(sizeof(uint32_t) + sizeof(ResultType)); | |
| expected.cmd.Init(123, GL_RGBA8, GL_NUM_SAMPLE_COUNTS, | |
| result1.id, result1.offset); | |
| EXPECT_CALL(*command_buffer(), OnFlush()) | |
| .WillOnce(SetMemory(result1.ptr, | |
| SizedResultHelper<ResultType>(kNumSampleCounts))) | |
| .RetiresOnSaturation(); | |
| gl_->GetInternalformativ(123, GL_RGBA8, GL_NUM_SAMPLE_COUNTS, 1, &result); | |
| UNSAFE_TODO(EXPECT_EQ(0, memcmp(&expected, commands_, sizeof(expected)))); | |
| EXPECT_EQ(static_cast<ResultType>(kNumSampleCounts), result); | |
| } | |
| static void CountCallback(int* count) { | |
| (*count)++; | |
| } | |
| TEST_F(GLES2ImplementationTest, 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)); | |
| EXPECT_CALL(*gpu_control_, EnsureWorkVisible()); | |
| gpu::SyncToken sync_token; | |
| gl_->GenSyncTokenCHROMIUM(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); | |
| }); | |
| 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 | |
| // GLES2Implementation is run. | |
| std::move(signal_closure).Run(); | |
| EXPECT_EQ(1, signaled_count); | |
| } | |
| TEST_F(GLES2ImplementationTest, 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)); | |
| EXPECT_CALL(*gpu_control_, EnsureWorkVisible()); | |
| gpu::SyncToken sync_token; | |
| gl_->GenSyncTokenCHROMIUM(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); | |
| }); | |
| gl_->SignalSyncToken(sync_token, | |
| base::BindOnce(&CountCallback, &signaled_count)); | |
| EXPECT_EQ(0, signaled_count); | |
| // Inform the GLES2Implementation 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 | |
| // GLES2Implementation 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(GLES2ImplementationTest, 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 GLES2Implementation is | |
| // notified of the loss. | |
| EXPECT_EQ(1, lost_count); | |
| } | |
| TEST_F(GLES2ImplementationTest, 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 GLES2Implementation. | |
| EXPECT_EQ(0, lost_count); | |
| } | |
| TEST_F(GLES2ImplementationManualInitTest, FailInitOnTransferBufferFail) { | |
| ContextInitOptions init_options; | |
| init_options.transfer_buffer_initialize_fail = true; | |
| EXPECT_FALSE(Initialize(init_options)); | |
| } | |
| struct ErrorMessageCounter { | |
| explicit ErrorMessageCounter(GLES2Implementation* gl) : gl(gl) {} | |
| void Callback(const char* message, int32_t id) { | |
| if (++num_calls == 1) | |
| gl->ShaderBinary(-1, nullptr, 0, nullptr, 0); | |
| } | |
| raw_ptr<GLES2Implementation> gl; | |
| int32_t num_calls = 0; | |
| }; | |
| TEST_F(GLES2ImplementationTest, ReentrantErrorCallbacksShouldNotCrash) { | |
| ErrorMessageCounter counter(gl_); | |
| gl_->SetErrorMessageCallback(base::BindRepeating( | |
| &ErrorMessageCounter::Callback, base::Unretained(&counter))); | |
| // Call any function which can easily provoke an error. See also the | |
| // callback above. | |
| gl_->ShaderBinary(-1, nullptr, 0, nullptr, 0); | |
| EXPECT_EQ(2, counter.num_calls); | |
| ResetErrorMessageCallback(); | |
| } | |
| TEST_F(GLES2ImplementationTest, DeleteZero) { | |
| gl_->DeleteProgram(0); | |
| EXPECT_EQ(GL_NO_ERROR, CheckError()); | |
| gl_->DeleteShader(0); | |
| EXPECT_EQ(GL_NO_ERROR, CheckError()); | |
| gl_->DeleteSync(0); | |
| EXPECT_EQ(GL_NO_ERROR, CheckError()); | |
| } | |
| TEST_F(GLES2ImplementationTest, BindBuffer) { | |
| struct Cmds { | |
| cmds::GenBuffersImmediate gen; | |
| GLuint id; | |
| cmds::BindBuffer cmd; | |
| }; | |
| Cmds expected; | |
| expected.gen.Init(1, &expected.id); | |
| expected.id = kBuffersStartId; | |
| expected.cmd.Init(GL_ARRAY_BUFFER, kBuffersStartId); | |
| gl_->GenBuffers(1, &expected.id); | |
| gl_->BindBuffer(GL_ARRAY_BUFFER, expected.id); | |
| UNSAFE_TODO(EXPECT_EQ(0, memcmp(&expected, commands_, sizeof(expected)))); | |
| ClearCommands(); | |
| gl_->BindBuffer(GL_ARRAY_BUFFER, expected.id); | |
| EXPECT_TRUE(NoCommandsWritten()); | |
| } | |
| TEST_F(GLES2ImplementationTest, BindBufferBase) { | |
| struct Cmds { | |
| cmds::GenBuffersImmediate gen; | |
| GLuint id; | |
| cmds::BindBufferBase cmd; | |
| }; | |
| Cmds expected; | |
| expected.gen.Init(1, &expected.id); | |
| expected.id = kBuffersStartId; | |
| expected.cmd.Init(GL_TRANSFORM_FEEDBACK_BUFFER, 2, kBuffersStartId); | |
| gl_->GenBuffers(1, &expected.id); | |
| gl_->BindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 2, expected.id); | |
| UNSAFE_TODO(EXPECT_EQ(0, memcmp(&expected, commands_, sizeof(expected)))); | |
| } | |
| TEST_F(GLES2ImplementationTest, BindBufferRange) { | |
| struct Cmds { | |
| cmds::GenBuffersImmediate gen; | |
| GLuint id; | |
| cmds::BindBufferRange cmd; | |
| }; | |
| Cmds expected; | |
| expected.gen.Init(1, &expected.id); | |
| expected.id = kBuffersStartId; | |
| expected.cmd.Init(GL_TRANSFORM_FEEDBACK_BUFFER, 2, kBuffersStartId, 4, 4); | |
| gl_->GenBuffers(1, &expected.id); | |
| gl_->BindBufferRange(GL_TRANSFORM_FEEDBACK_BUFFER, 2, expected.id, 4, 4); | |
| UNSAFE_TODO(EXPECT_EQ(0, memcmp(&expected, commands_, sizeof(expected)))); | |
| } | |
| TEST_F(GLES2ImplementationTest, BindFramebuffer) { | |
| struct Cmds { | |
| cmds::GenFramebuffersImmediate gen; | |
| GLuint id; | |
| cmds::BindFramebuffer cmd; | |
| }; | |
| Cmds expected; | |
| expected.gen.Init(1, &expected.id); | |
| expected.id = kFramebuffersStartId; | |
| expected.cmd.Init(GL_FRAMEBUFFER, kFramebuffersStartId); | |
| gl_->GenFramebuffers(1, &expected.id); | |
| gl_->BindFramebuffer(GL_FRAMEBUFFER, expected.id); | |
| UNSAFE_TODO(EXPECT_EQ(0, memcmp(&expected, commands_, sizeof(expected)))); | |
| ClearCommands(); | |
| gl_->BindFramebuffer(GL_FRAMEBUFFER, expected.id); | |
| EXPECT_TRUE(NoCommandsWritten()); | |
| } | |
| TEST_F(GLES2ImplementationTest, BindRenderbuffer) { | |
| struct Cmds { | |
| cmds::GenRenderbuffersImmediate gen; | |
| GLuint id; | |
| cmds::BindRenderbuffer cmd; | |
| }; | |
| Cmds expected; | |
| expected.gen.Init(1, &expected.id); | |
| expected.id = kFramebuffersStartId; | |
| expected.cmd.Init(GL_RENDERBUFFER, kFramebuffersStartId); | |
| gl_->GenRenderbuffers(1, &expected.id); | |
| gl_->BindRenderbuffer(GL_RENDERBUFFER, expected.id); | |
| UNSAFE_TODO(EXPECT_EQ(0, memcmp(&expected, commands_, sizeof(expected)))); | |
| ClearCommands(); | |
| gl_->BindRenderbuffer(GL_RENDERBUFFER, expected.id); | |
| EXPECT_TRUE(NoCommandsWritten()); | |
| } | |
| TEST_F(GLES2ImplementationTest, BindSampler) { | |
| struct Cmds { | |
| cmds::GenSamplersImmediate gen; | |
| GLuint id; | |
| cmds::BindSampler cmd; | |
| }; | |
| Cmds expected; | |
| expected.gen.Init(1, &expected.id); | |
| expected.id = kSamplersStartId; | |
| expected.cmd.Init(1, kSamplersStartId); | |
| gl_->GenSamplers(1, &expected.id); | |
| gl_->BindSampler(1, expected.id); | |
| UNSAFE_TODO(EXPECT_EQ(0, memcmp(&expected, commands_, sizeof(expected)))); | |
| } | |
| // Test that ClearMappedBufferMap correctly cleans up buffers mapped via | |
| // MapBufferSubDataCHROMIUM. | |
| TEST_F(GLES2ImplementationTest, ClearMappedBufferMap) { | |
| GLuint buffer; | |
| gl_->GenBuffers(1, &buffer); | |
| gl_->BindBuffer(GL_ARRAY_BUFFER, buffer); | |
| gl_->BufferData(GL_ARRAY_BUFFER, 64, nullptr, GL_STATIC_DRAW); | |
| EXPECT_CALL(*command_buffer(), OnFlush()).Times(testing::AnyNumber()); | |
| void* addr = | |
| gl_->MapBufferSubDataCHROMIUM(GL_ARRAY_BUFFER, 0, 1, GL_WRITE_ONLY); | |
| ASSERT_TRUE(addr); | |
| } | |
| // Test that AllocateShadowCopiesForReadback skips buffers that have been | |
| // deleted while in the unfenced list. | |
| TEST_F(GLES2ImplementationTest, AllocateShadowCopiesForReadbackNullBuffer) { | |
| GLuint buffer; | |
| gl_->GenBuffers(1, &buffer); | |
| gl_->BindBuffer(GL_ARRAY_BUFFER, buffer); | |
| gl_->BufferData(GL_ARRAY_BUFFER, 64, nullptr, GL_STREAM_READ); | |
| gl_->DeleteBuffers(1, &buffer); | |
| GLuint query; | |
| gl_->GenQueriesEXT(1, &query); | |
| EXPECT_CALL(*command_buffer(), OnFlush()).Times(testing::AnyNumber()); | |
| gl_->BeginQueryEXT(GL_READBACK_SHADOW_COPIES_UPDATED_CHROMIUM, query); | |
| // Prior to the fix, this would crash in AllocateShadowCopiesForReadback | |
| // because it would dereference a null WeakPtr for the deleted buffer. | |
| } | |
| // Test that AllocateShadowCopiesForReadback correctly handles shadow buffer | |
| // allocation failures. | |
| TEST_F(GLES2ImplementationTest, AllocateShadowCopiesForReadbackAllocFail) { | |
| GLuint buffer; | |
| gl_->GenBuffers(1, &buffer); | |
| gl_->BindBuffer(GL_ARRAY_BUFFER, buffer); | |
| gl_->BufferData(GL_ARRAY_BUFFER, 64, nullptr, GL_STREAM_READ); | |
| mapped_memory()->set_max_allocated_bytes(0); | |
| GLuint query; | |
| gl_->GenQueriesEXT(1, &query); | |
| EXPECT_CALL(*command_buffer(), OnFlush()).Times(testing::AnyNumber()); | |
| gl_->BeginQueryEXT(GL_READBACK_SHADOW_COPIES_UPDATED_CHROMIUM, query); | |
| // Prior to the fix, this would incorrectly attempt to issue a shadow | |
| // allocation command with an invalid shared memory ID (-1). | |
| } | |
| // Test that AllocateShadowCopiesForReadback issues a performance warning if a | |
| // READ-usage buffer is written to again while a shadow copy is already | |
| // allocated. | |
| TEST_F(GLES2ImplementationTest, | |
| AllocateShadowCopiesForReadbackAlreadyAllocated) { | |
| GLuint buffer; | |
| gl_->GenBuffers(1, &buffer); | |
| gl_->BindBuffer(GL_ARRAY_BUFFER, buffer); | |
| gl_->BufferData(GL_ARRAY_BUFFER, 64, nullptr, GL_STREAM_READ); | |
| // Trigger first allocation | |
| GLuint query1; | |
| gl_->GenQueriesEXT(1, &query1); | |
| EXPECT_CALL(*command_buffer(), OnFlush()).Times(testing::AnyNumber()); | |
| gl_->BeginQueryEXT(GL_READBACK_SHADOW_COPIES_UPDATED_CHROMIUM, query1); | |
| gl_->EndQueryEXT(GL_READBACK_SHADOW_COPIES_UPDATED_CHROMIUM); | |
| // Write again, adding back to unfenced list | |
| const char data = 'a'; | |
| gl_->BufferSubData(GL_ARRAY_BUFFER, 0, 1, &data); | |
| // Capture warning | |
| std::string last_error; | |
| gl_->SetErrorMessageCallback( | |
| base::BindRepeating([](std::string* error, const char* message, | |
| int32_t id) { *error = message; }, | |
| &last_error)); | |
| // Trigger second allocation | |
| GLuint query2; | |
| gl_->GenQueriesEXT(1, &query2); | |
| gl_->BeginQueryEXT(GL_READBACK_SHADOW_COPIES_UPDATED_CHROMIUM, query2); | |
| EXPECT_THAT(last_error, | |
| testing::HasSubstr("READ-usage buffer was written, then fenced, " | |
| "but written again")); | |
| } | |
| TEST_F(GLES2ImplementationTest, ReadbackARGBImagePixelsINTERNALPadding) { | |
| gpu::Mailbox mailbox = gpu::Mailbox::Generate(); | |
| GLuint dst_width = 2; | |
| GLuint dst_height = 2; | |
| GLuint dst_sk_color_type = 4; // kRGBA_8888_SkColorType | |
| GLuint dst_sk_alpha_type = 1; // kPremul_SkAlphaType | |
| GLuint dst_row_bytes = | |
| 12; // 2 pixels * 4 bytes/pixel = 8 bytes. Row padding = 4 bytes. | |
| GLuint dst_size = dst_height * dst_row_bytes; | |
| GLuint color_space_offset = base::bits::AlignUp( | |
| sizeof(cmds::ReadbackARGBImagePixelsINTERNAL::Result), sizeof(uint64_t)); | |
| GLuint mailbox_offset = color_space_offset; | |
| GLuint pixels_offset = base::bits::AlignUp( | |
| mailbox_offset + sizeof(gpu::Mailbox), sizeof(uint64_t)); | |
| 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_size, 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::ReadbackARGBImagePixelsINTERNAL::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(); | |
| GLboolean success = gl_->ReadbackARGBImagePixelsINTERNAL( | |
| mailbox.name, /*dst_color_space=*/nullptr, | |
| /*dst_color_space_size=*/0, dst_size, dst_width, dst_height, | |
| dst_sk_color_type, dst_sk_alpha_type, 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_size, 0xAA); | |
| size_t min_row_bytes = 8; // 2 pixels * 4 bytes/pixel = 8. | |
| for (size_t y = 0; y < dst_height; ++y) { | |
| for (size_t x = 0; x < min_row_bytes; ++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); | |
| } | |
| #include "gpu/command_buffer/client/gles2_implementation_unittest_autogen.h" | |
| } // namespace gles2 | |
| } // namespace gpu |