| // Copyright 2026 The Chromium Authors | |
| // Use of this source code is governed by a BSD-style license that can be | |
| // found in the LICENSE file. | |
| #include <stddef.h> | |
| #include <stdint.h> | |
| #include <array> | |
| #include <memory> | |
| #include "base/memory/scoped_refptr.h" | |
| #include "base/test/task_environment.h" | |
| #include "gpu/command_buffer/client/client_test_helper.h" | |
| #include "gpu/command_buffer/service/context_group.h" | |
| #include "gpu/command_buffer/service/feature_info.h" | |
| #include "gpu/command_buffer/service/framebuffer_completeness_cache.h" | |
| #include "gpu/command_buffer/service/gles2_cmd_decoder.h" | |
| #include "gpu/command_buffer/service/gpu_tracer.h" | |
| #include "gpu/command_buffer/service/memory_tracking.h" | |
| #include "gpu/command_buffer/service/shader_translator_cache.h" | |
| #include "gpu/command_buffer/service/shared_image/shared_image_manager.h" | |
| #include "gpu/config/gpu_driver_bug_workarounds.h" | |
| #include "gpu/config/gpu_feature_info.h" | |
| #include "gpu/config/gpu_preferences.h" | |
| #include "testing/gmock/include/gmock/gmock.h" | |
| #include "testing/gtest/include/gtest/gtest.h" | |
| #include "ui/gl/gl_context_stub.h" | |
| #include "ui/gl/gl_mock.h" | |
| #include "ui/gl/gl_surface_stub.h" | |
| #include "ui/gl/test/gl_surface_test_support.h" | |
| using ::gl::MockGLInterface; | |
| using ::testing::_; | |
| using ::testing::AnyNumber; | |
| using ::testing::NiceMock; | |
| using ::testing::NotNull; | |
| using ::testing::Return; | |
| using ::testing::SetArgPointee; | |
| using ::testing::SetArrayArgument; | |
| namespace gpu { | |
| namespace gles2 { | |
| namespace { | |
| constexpr GLuint kServiceTextureId = 101; | |
| constexpr GLuint kServiceFramebufferId = 102; | |
| constexpr GLint kMaxSize = 2048; | |
| // Tests for GLES2DecoderImpl initialized with |offscreen| = true. The shared | |
| // GLES2DecoderTestBase scripts a strict, ordered set of GL calls for onscreen | |
| // initialization only, so this fixture uses a NiceMock with reasonable | |
| // defaults and only sets expectations on the calls under test. | |
| class GLES2DecoderOffscreenTest : public testing::Test { | |
| protected: | |
| void SetUp() override { | |
| gl::SetGLGetProcAddressProc(MockGLInterface::GetGLProcAddress); | |
| display_ = gl::GLSurfaceTestSupport::InitializeOneOffWithMockBindings(); | |
| gl_ = std::make_unique<NiceMock<MockGLInterface>>(); | |
| MockGLInterface::SetGLInterface(gl_.get()); | |
| ON_CALL(*gl_, GetString(_)) | |
| .WillByDefault(Return(reinterpret_cast<const GLubyte*>(""))); | |
| ON_CALL(*gl_, GetString(GL_VERSION)) | |
| .WillByDefault( | |
| Return(reinterpret_cast<const GLubyte*>("OpenGL ES 2.0"))); | |
| ON_CALL(*gl_, GetIntegerv(_, _)) | |
| .WillByDefault([](GLenum pname, GLint* params) { | |
| switch (pname) { | |
| case GL_MAX_TEXTURE_SIZE: | |
| case GL_MAX_CUBE_MAP_TEXTURE_SIZE: | |
| case GL_MAX_RENDERBUFFER_SIZE: | |
| *params = kMaxSize; | |
| break; | |
| case GL_MAX_VERTEX_UNIFORM_VECTORS: | |
| *params = 256; | |
| break; | |
| case GL_MAX_VERTEX_ATTRIBS: | |
| case GL_MAX_TEXTURE_IMAGE_UNITS: | |
| case GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS: | |
| case GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS: | |
| case GL_MAX_FRAGMENT_UNIFORM_VECTORS: | |
| case GL_MAX_VARYING_VECTORS: | |
| *params = 16; | |
| break; | |
| default: | |
| // Bindings and anything else not needed by these tests. | |
| *params = 0; | |
| break; | |
| } | |
| }); | |
| // GL_MAX_VIEWPORT_DIMS returns two values. Declared after the generic | |
| // default above so that it takes precedence. | |
| static constexpr std::array<GLint, 2> kMaxViewportDims = {kMaxSize, | |
| kMaxSize}; | |
| ON_CALL(*gl_, GetIntegerv(GL_MAX_VIEWPORT_DIMS, _)) | |
| .WillByDefault(SetArrayArgument<1>(kMaxViewportDims.begin(), | |
| kMaxViewportDims.end())); | |
| ON_CALL(*gl_, GetError()).WillByDefault(Return(GL_NO_ERROR)); | |
| ON_CALL(*gl_, GenTextures(1, _)) | |
| .WillByDefault(SetArgPointee<1>(kServiceTextureId)); | |
| ON_CALL(*gl_, GenFramebuffersEXT(1, _)) | |
| .WillByDefault(SetArgPointee<1>(kServiceFramebufferId)); | |
| ON_CALL(*gl_, CheckFramebufferStatusEXT(_)) | |
| .WillByDefault(Return(GL_FRAMEBUFFER_COMPLETE)); | |
| surface_ = base::MakeRefCounted<gl::GLSurfaceStub>(); | |
| context_ = base::MakeRefCounted<gl::GLContextStub>(); | |
| context_->SetGLVersionString("OpenGL ES 2.0"); | |
| context_->SetExtensionsString(""); | |
| context_->MakeCurrent(surface_.get()); | |
| } | |
| void TearDown() override { | |
| if (decoder_) { | |
| decoder_->Destroy(/*have_context=*/true); | |
| decoder_.reset(); | |
| } | |
| group_ = nullptr; | |
| context_ = nullptr; | |
| surface_ = nullptr; | |
| MockGLInterface::SetGLInterface(nullptr); | |
| gl_.reset(); | |
| gl::GLSurfaceTestSupport::ShutdownGL(display_); | |
| } | |
| ContextResult InitializeOffscreenDecoder( | |
| const GpuDriverBugWorkarounds& workarounds) { | |
| auto feature_info = | |
| base::MakeRefCounted<FeatureInfo>(workarounds, GpuFeatureInfo()); | |
| group_ = base::MakeRefCounted<ContextGroup>( | |
| gpu_preferences_, /*memory_tracker=*/nullptr, &shader_translator_cache_, | |
| &framebuffer_completeness_cache_, feature_info, | |
| /*progress_reporter=*/nullptr, GpuFeatureInfo(), | |
| &shared_image_manager_); | |
| decoder_ = GLES2Decoder::Create(&client_, &command_buffer_service_, | |
| &outputter_, group_.get()); | |
| return decoder_->Initialize(surface_, context_, /*offscreen=*/true, | |
| CONTEXT_TYPE_OPENGLES2, | |
| /*lose_context_when_out_of_memory=*/false); | |
| } | |
| base::test::SingleThreadTaskEnvironment task_environment_; | |
| raw_ptr<gl::GLDisplay> display_ = nullptr; | |
| std::unique_ptr<NiceMock<MockGLInterface>> gl_; | |
| scoped_refptr<gl::GLSurfaceStub> surface_; | |
| scoped_refptr<gl::GLContextStub> context_; | |
| GpuPreferences gpu_preferences_; | |
| ShaderTranslatorCache shader_translator_cache_{gpu_preferences_}; | |
| FramebufferCompletenessCache framebuffer_completeness_cache_; | |
| SharedImageManager shared_image_manager_; | |
| FakeCommandBufferServiceBase command_buffer_service_; | |
| FakeDecoderClient client_; | |
| TraceOutputter outputter_; | |
| scoped_refptr<ContextGroup> group_; | |
| std::unique_ptr<GLES2Decoder> decoder_; | |
| }; | |
| // The offscreen backbuffer must be allocated with defined (zeroed) contents | |
| // rather than relying on glClear, which is unreliable on drivers with the | |
| // gl_clear_broken workaround. See crbug.com/562221093. | |
| TEST_F(GLES2DecoderOffscreenTest, BackbufferStorageIsZeroInitialized) { | |
| GpuDriverBugWorkarounds workarounds; | |
| // With gl_clear_broken the backbuffer can't rely on the native glClear in | |
| // ResizeOffscreenFramebuffer to initialize its contents. | |
| workarounds.gl_clear_broken = true; | |
| // Initialization issues other TexImage2D calls (e.g. default textures). | |
| EXPECT_CALL(*gl_, TexImage2D(_, _, _, _, _, _, _, _, _)).Times(AnyNumber()); | |
| // The offscreen backbuffer is created at 64x64 in GL_RGB. It must be uploaded | |
| // with data (BackTexture::AllocateStorage zero-fills it via | |
| // base::HeapArray::WithSize) rather than nullptr, which leaves the storage | |
| // uninitialized. | |
| constexpr GLsizei kWidth = 64; | |
| constexpr GLsizei kHeight = 64; | |
| EXPECT_CALL(*gl_, TexImage2D(GL_TEXTURE_2D, 0, GL_RGB, kWidth, kHeight, 0, | |
| GL_RGB, GL_UNSIGNED_BYTE, NotNull())) | |
| .Times(1); | |
| ASSERT_EQ(InitializeOffscreenDecoder(workarounds), ContextResult::kSuccess); | |
| } | |
| } // namespace | |
| } // namespace gles2 | |
| } // namespace gpu |