| // Copyright 2022 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "gpu/command_buffer/service/gles2_external_framebuffer.h" |
| |
| #include "base/bits.h" |
| #include "base/command_line.h" |
| #include "build/build_config.h" |
| #include "gpu/command_buffer/common/mailbox.h" |
| #include "gpu/command_buffer/common/shared_image_usage.h" |
| #include "gpu/command_buffer/service/service_utils.h" |
| #include "gpu/command_buffer/service/shared_context_state.h" |
| #include "gpu/command_buffer/service/shared_image/shared_image_backing.h" |
| #include "gpu/command_buffer/service/shared_image/shared_image_factory.h" |
| #include "gpu/command_buffer/service/shared_image/shared_image_manager.h" |
| #include "gpu/command_buffer/service/shared_image/shared_image_representation.h" |
| #include "gpu/command_buffer/service/shared_image/test_utils.h" |
| #include "gpu/config/gpu_driver_bug_workarounds.h" |
| #include "gpu/config/gpu_feature_info.h" |
| #include "gpu/config/gpu_preferences.h" |
| #include "gpu/config/gpu_test_config.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "ui/gfx/color_space.h" |
| #include "ui/gl/gl_surface.h" |
| #include "ui/gl/gl_utils.h" |
| #include "ui/gl/gl_version_info.h" |
| #include "ui/gl/init/gl_factory.h" |
| |
| using testing::AtLeast; |
| |
| namespace gpu { |
| namespace { |
| |
| void CreateSharedContext(const GpuPreferences& preferences, |
| const GpuDriverBugWorkarounds& workarounds, |
| scoped_refptr<gl::GLSurface>& surface, |
| scoped_refptr<gl::GLContext>& context, |
| scoped_refptr<SharedContextState>& context_state, |
| scoped_refptr<gles2::FeatureInfo>& feature_info) { |
| surface = |
| gl::init::CreateOffscreenGLSurface(gl::GetDefaultDisplay(), gfx::Size()); |
| ASSERT_TRUE(surface); |
| context = |
| gl::init::CreateGLContext(nullptr, surface.get(), gl::GLContextAttribs()); |
| ASSERT_TRUE(context); |
| bool result = context->MakeCurrent(surface.get()); |
| ASSERT_TRUE(result); |
| |
| scoped_refptr<gl::GLShareGroup> share_group = |
| base::MakeRefCounted<gl::GLShareGroup>(); |
| feature_info = |
| base::MakeRefCounted<gles2::FeatureInfo>(workarounds, GpuFeatureInfo()); |
| context_state = base::MakeRefCounted<SharedContextState>( |
| std::move(share_group), surface, context, |
| /*use_virtualized_gl_contexts=*/false, base::DoNothing(), |
| GrContextType::kGL); |
| context_state->InitializeSkia(GpuPreferences(), workarounds); |
| context_state->InitializeGL(GpuPreferences(), feature_info); |
| } |
| |
| using TestParamsTuple = |
| testing::tuple<viz::SharedImageFormat, int, bool, bool, bool>; |
| class GLES2ExternalFrameBufferTest |
| : public testing::TestWithParam<TestParamsTuple> { |
| public: |
| explicit GLES2ExternalFrameBufferTest() |
| : shared_image_manager_( |
| std::make_unique<SharedImageManager>(/*is_thread_safe=*/false)) {} |
| ~GLES2ExternalFrameBufferTest() override { |
| // |context_state_| must be destroyed on its own context. |
| bool have_context = |
| context_state_->MakeCurrent(surface_.get(), true /* needs_gl */); |
| gles2_external_framebuffer_->Destroy(have_context); |
| backing_factory_->DestroyAllSharedImages(have_context); |
| } |
| |
| void SetUp() override { |
| scoped_refptr<gles2::FeatureInfo> feature_info; |
| GpuDriverBugWorkarounds workarounds; |
| GpuPreferences preferences; |
| preferences.use_passthrough_cmd_decoder = use_passthrough(); |
| CreateSharedContext(preferences, workarounds, surface_, context_, |
| context_state_, feature_info); |
| |
| backing_factory_ = std::make_unique<SharedImageFactory>( |
| preferences, workarounds, GpuFeatureInfo(), context_state_.get(), |
| shared_image_manager_.get(), context_state_->memory_tracker(), |
| /*is_for_display_compositor=*/false); |
| |
| memory_type_tracker_ = std::make_unique<MemoryTypeTracker>(nullptr); |
| shared_image_representation_factory_ = |
| std::make_unique<SharedImageRepresentationFactory>( |
| shared_image_manager_.get(), nullptr); |
| |
| gles2_external_framebuffer_ = |
| std::make_unique<gles2::GLES2ExternalFramebuffer>( |
| use_passthrough(), *feature_info, |
| shared_image_representation_factory_.get()); |
| |
| const bool multisampled_framebuffers_supported = |
| feature_info->feature_flags().chromium_framebuffer_multisample; |
| const bool rgb8_supported = feature_info->feature_flags().oes_rgb8_rgba8; |
| // The only available default render buffer formats in GLES2 have very |
| // little precision. Don't enable multisampling unless 8-bit render |
| // buffer formats are available--instead fall back to 8-bit textures. |
| |
| if (multisampled_framebuffers_supported && rgb8_supported) { |
| glGetIntegerv(GL_MAX_SAMPLES_EXT, &max_sample_count_); |
| } |
| } |
| |
| bool use_passthrough() { |
| return gles2::UsePassthroughCommandDecoder( |
| base::CommandLine::ForCurrentProcess()) && |
| gles2::PassthroughCommandDecoderSupported(); |
| } |
| |
| protected: |
| std::unique_ptr<GLTextureImageRepresentationBase> ProduceGLTextureBase( |
| const Mailbox& mailbox) { |
| if (use_passthrough()) |
| return shared_image_representation_factory_->ProduceGLTexturePassthrough( |
| mailbox); |
| else |
| return shared_image_representation_factory_->ProduceGLTexture(mailbox); |
| } |
| |
| Mailbox CreateSharedImage(const viz::SharedImageFormat& format) { |
| auto mailbox = Mailbox::GenerateForSharedImage(); |
| backing_factory_->CreateSharedImage( |
| mailbox, format, gfx::Size(64, 64), gfx::ColorSpace::CreateSRGB(), |
| kTopLeft_GrSurfaceOrigin, kPremul_SkAlphaType, SurfaceHandle(), |
| SHARED_IMAGE_USAGE_GLES2, "TestLabel"); |
| return mailbox; |
| } |
| |
| void ReadColors(gl::GLApi* const api, |
| const Mailbox& mailbox, |
| std::array<SkColor, 4>& quadrants) { |
| auto rep = ProduceGLTextureBase(mailbox); |
| auto access = rep->BeginScopedAccess( |
| GLTextureImageRepresentationBase::kReadAccessMode, |
| GLTextureImageRepresentationBase::AllowUnclearedAccess::kYes); |
| EXPECT_TRUE(rep->IsCleared()); |
| |
| GLuint fbo; |
| api->glGenFramebuffersEXTFn(1, &fbo); |
| api->glBindFramebufferEXTFn(GL_FRAMEBUFFER, fbo); |
| api->glFramebufferTexture2DEXTFn(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, |
| GL_TEXTURE_2D, |
| rep->GetTextureBase()->service_id(), 0); |
| |
| api->glDisableFn(GL_SCISSOR_TEST); |
| |
| // Initialize to gray in case read pixels will fail. |
| for (auto& color : quadrants) |
| color = SK_ColorGRAY; |
| |
| api->glReadPixelsFn(16, 16, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, &quadrants[0]); |
| api->glReadPixelsFn(48, 16, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, &quadrants[1]); |
| api->glReadPixelsFn(16, 46, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, &quadrants[2]); |
| api->glReadPixelsFn(48, 48, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, &quadrants[3]); |
| |
| // SkColor is BGRA and GL is RGBA, so we need to swap bytes. |
| for (auto& color : quadrants) { |
| color = SkColorSetARGB(SkColorGetA(color), SkColorGetB(color), |
| SkColorGetG(color), SkColorGetR(color)); |
| } |
| |
| api->glDeleteFramebuffersEXTFn(1, &fbo); |
| } |
| |
| scoped_refptr<gl::GLSurface> surface_; |
| scoped_refptr<gl::GLContext> context_; |
| scoped_refptr<SharedContextState> context_state_; |
| std::unique_ptr<SharedImageFactory> backing_factory_; |
| std::unique_ptr<SharedImageManager> shared_image_manager_; |
| std::unique_ptr<MemoryTypeTracker> memory_type_tracker_; |
| std::unique_ptr<SharedImageRepresentationFactory> |
| shared_image_representation_factory_; |
| std::unique_ptr<gles2::GLES2ExternalFramebuffer> gles2_external_framebuffer_; |
| GLint max_sample_count_ = 0; |
| }; |
| |
| struct TestParams { |
| explicit TestParams(const TestParamsTuple& params) { |
| format = testing::get<0>(params); |
| samples = testing::get<1>(params); |
| preserve = testing::get<2>(params); |
| need_depth = testing::get<3>(params); |
| need_stencil = testing::get<4>(params); |
| } |
| |
| viz::SharedImageFormat format; |
| int samples; |
| bool preserve; |
| bool need_depth; |
| bool need_stencil; |
| }; |
| |
| std::string TestParamToString( |
| const testing::TestParamInfo<TestParamsTuple>& param_info) { |
| auto params = TestParams(param_info.param); |
| |
| std::string result; |
| result += params.format.ToString(); |
| |
| if (params.samples) |
| result += "Multisampling"; |
| |
| if (params.preserve) |
| result += "Preserve"; |
| if (params.need_depth) |
| result += "Depth"; |
| if (params.need_stencil) |
| result += "Stencil"; |
| |
| return result; |
| } |
| |
| INSTANTIATE_TEST_SUITE_P( |
| , |
| GLES2ExternalFrameBufferTest, |
| ::testing::Combine(::testing::Values(viz::SinglePlaneFormat::kRGBA_8888, |
| viz::SinglePlaneFormat::kRGBX_8888), |
| ::testing::Values(0, 8), |
| ::testing::Bool(), |
| ::testing::Bool(), |
| ::testing::Bool()), |
| TestParamToString); |
| |
| TEST_P(GLES2ExternalFrameBufferTest, Test) { |
| auto params = TestParams(GetParam()); |
| auto mailbox1 = CreateSharedImage(params.format); |
| |
| gl::GLApi* const api = gl::g_current_gl_context; |
| |
| { |
| GLint prev_fbo; |
| api->glGetIntegervFn(GL_FRAMEBUFFER_BINDING, &prev_fbo); |
| EXPECT_TRUE(gles2_external_framebuffer_->AttachSharedImage( |
| mailbox1, params.samples, params.preserve, params.need_depth, |
| params.need_stencil)); |
| |
| GLint new_fbo; |
| api->glGetIntegervFn(GL_FRAMEBUFFER_BINDING, &new_fbo); |
| |
| // Verify that it doesn't update FBO binding |
| EXPECT_EQ(new_fbo, prev_fbo); |
| } |
| |
| api->glBindFramebufferEXTFn(GL_FRAMEBUFFER, |
| gles2_external_framebuffer_->GetFramebufferId()); |
| api->glViewportFn(0, 0, 64, 64); |
| |
| GLint depth_bits = 0; |
| GLint stencil_bits = 0; |
| GLint alpha_bits = 0; |
| |
| if (context_state_->feature_info() |
| ->gl_version_info() |
| .is_desktop_core_profile) { |
| api->glGetFramebufferAttachmentParameterivEXTFn( |
| GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, |
| GL_FRAMEBUFFER_ATTACHMENT_ALPHA_SIZE, &alpha_bits); |
| api->glGetFramebufferAttachmentParameterivEXTFn( |
| GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, |
| GL_FRAMEBUFFER_ATTACHMENT_DEPTH_SIZE, &depth_bits); |
| api->glGetFramebufferAttachmentParameterivEXTFn( |
| GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, |
| GL_FRAMEBUFFER_ATTACHMENT_STENCIL_SIZE, &stencil_bits); |
| } else { |
| api->glGetIntegervFn(GL_ALPHA_BITS, &alpha_bits); |
| api->glGetIntegervFn(GL_DEPTH_BITS, &depth_bits); |
| api->glGetIntegervFn(GL_STENCIL_BITS, &stencil_bits); |
| } |
| |
| // If we requested depth, expect it to be there. |
| if (params.need_depth) |
| EXPECT_GT(depth_bits, 0); |
| |
| // If we requested depth, expect it to be there. |
| if (params.need_stencil) |
| EXPECT_GT(stencil_bits, 0); |
| |
| // If we didn't request neither depth nor stencil. Note, that we prefer using |
| // packed depth-stencil, so requesting one of them might have both. |
| if (!params.need_depth && !params.need_stencil) { |
| EXPECT_EQ(depth_bits, 0); |
| EXPECT_EQ(stencil_bits, 0); |
| } |
| |
| EXPECT_EQ(params.format.HasAlpha(), alpha_bits > 0); |
| |
| const bool draw_direct = |
| !params.preserve && (std::min(max_sample_count_, params.samples) == 0); |
| |
| if (draw_direct) { |
| auto rep = ProduceGLTextureBase(mailbox1); |
| // AttachSharedImage should have cleared image if we draw directly. |
| EXPECT_TRUE(rep->IsCleared()); |
| } |
| |
| api->glScissorFn(0, 0, 32, 32); |
| api->glEnableFn(GL_SCISSOR_TEST); |
| api->glClearColorFn(0.0, 1.0, 0.0, 1.0); // Green |
| api->glClearFn(GL_COLOR_BUFFER_BIT); |
| |
| // Detach and resolve mailbox1 from the framebuffer. This always expected to |
| // succeed. |
| EXPECT_TRUE(gles2_external_framebuffer_->AttachSharedImage( |
| Mailbox(), 0, false, false, false)); |
| |
| const SkColor clear_color = |
| params.format.HasAlpha() ? SK_ColorTRANSPARENT : SK_ColorBLACK; |
| { |
| std::array<SkColor, 4> colors; |
| ReadColors(api, mailbox1, colors); |
| // We draw this one. |
| EXPECT_EQ(colors[0], SK_ColorGREEN); |
| |
| // We didn't draw those, so it should have been cleared to zero. |
| EXPECT_EQ(colors[1], clear_color); |
| EXPECT_EQ(colors[2], clear_color); |
| EXPECT_EQ(colors[3], clear_color); |
| } |
| |
| auto mailbox2 = CreateSharedImage(params.format); |
| |
| EXPECT_TRUE(gles2_external_framebuffer_->AttachSharedImage( |
| mailbox2, params.samples, params.preserve, params.need_depth, |
| params.need_stencil)); |
| api->glBindFramebufferEXTFn(GL_FRAMEBUFFER, |
| gles2_external_framebuffer_->GetFramebufferId()); |
| api->glViewportFn(0, 0, 64, 64); |
| |
| // Clear bottom right portion. |
| api->glScissorFn(32, 32, 32, 32); |
| api->glEnableFn(GL_SCISSOR_TEST); |
| api->glClearColorFn(0.0, 0.0, 1.0, 1.0); // Blue |
| api->glClearFn(GL_COLOR_BUFFER_BIT); |
| |
| // Detach and resolve mailbox1 from the framebuffer. This always expected to |
| // succeed. |
| EXPECT_TRUE(gles2_external_framebuffer_->AttachSharedImage( |
| Mailbox(), 0, false, false, false)); |
| |
| std::array<SkColor, 4> colors; |
| ReadColors(api, mailbox2, colors); |
| |
| // If we requested to preserve content or if there is multisampling, the |
| // contents of the back buffer is expected to stay, so top-left quadrant |
| // should be green from the first draw. |
| if (!draw_direct) { |
| EXPECT_EQ(colors[0], SK_ColorGREEN); |
| } else { |
| EXPECT_EQ(colors[0], clear_color); |
| } |
| |
| // We didn't draw those, so it should have been cleared to zero. |
| EXPECT_EQ(colors[1], clear_color); |
| EXPECT_EQ(colors[2], clear_color); |
| |
| // We just draw this one as blue. |
| EXPECT_EQ(colors[3], SK_ColorBLUE); |
| } |
| |
| } // namespace |
| } // namespace gpu |