| // Copyright 2012 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "components/viz/service/display/gl_renderer.h" |
| |
| #include <stdint.h> |
| |
| #include <memory> |
| #include <set> |
| #include <tuple> |
| #include <utility> |
| #include <vector> |
| |
| #include "base/bind.h" |
| #include "base/callback_helpers.h" |
| #include "base/containers/cxx20_erase.h" |
| #include "base/location.h" |
| #include "base/single_thread_task_runner.h" |
| #include "base/strings/stringprintf.h" |
| #include "base/test/scoped_feature_list.h" |
| #include "base/threading/thread_task_runner_handle.h" |
| #include "build/build_config.h" |
| #include "cc/base/math_util.h" |
| #include "cc/test/fake_impl_task_runner_provider.h" |
| #include "cc/test/fake_layer_tree_host_impl.h" |
| #include "cc/test/fake_output_surface_client.h" |
| #include "cc/test/pixel_test.h" |
| #include "cc/test/render_pass_test_utils.h" |
| #include "cc/test/resource_provider_test_utils.h" |
| #include "components/viz/client/client_resource_provider.h" |
| #include "components/viz/common/display/renderer_settings.h" |
| #include "components/viz/common/features.h" |
| #include "components/viz/common/frame_sinks/copy_output_request.h" |
| #include "components/viz/common/frame_sinks/copy_output_result.h" |
| #include "components/viz/common/quads/texture_draw_quad.h" |
| #include "components/viz/common/resources/platform_color.h" |
| #include "components/viz/common/resources/transferable_resource.h" |
| #include "components/viz/service/display/display_resource_provider_gl.h" |
| #include "components/viz/test/fake_output_surface.h" |
| #include "components/viz/test/test_gles2_interface.h" |
| #include "components/viz/test/viz_test_suite.h" |
| #include "gpu/GLES2/gl2extchromium.h" |
| #include "gpu/command_buffer/client/context_support.h" |
| #include "gpu/config/gpu_finch_features.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "third_party/skia/include/core/SkMatrix.h" |
| #include "third_party/skia/include/effects/SkColorMatrixFilter.h" |
| #include "ui/base/ui_base_features.h" |
| #include "ui/gfx/color_transform.h" |
| #include "ui/gfx/transform.h" |
| #include "ui/latency/latency_info.h" |
| |
| #if defined(OS_WIN) |
| #include "components/viz/service/display/overlay_processor_win.h" |
| #elif defined(OS_APPLE) |
| #include "components/viz/service/display/overlay_processor_mac.h" |
| #elif defined(OS_ANDROID) || defined(USE_OZONE) |
| #include "components/viz/service/display/overlay_processor_using_strategy.h" |
| #include "components/viz/service/display/overlay_strategy_single_on_top.h" |
| #include "components/viz/service/display/overlay_strategy_underlay.h" |
| #else // Default |
| #include "components/viz/service/display/overlay_processor_stub.h" |
| #endif |
| |
| using testing::_; |
| using testing::AnyNumber; |
| using testing::Args; |
| using testing::AtLeast; |
| using testing::Contains; |
| using testing::ElementsAre; |
| using testing::Expectation; |
| using testing::InSequence; |
| using testing::Invoke; |
| using testing::Mock; |
| using testing::Not; |
| using testing::Pointee; |
| using testing::Return; |
| using testing::StrictMock; |
| |
| namespace viz { |
| |
| MATCHER_P(MatchesSyncToken, sync_token, "") { |
| gpu::SyncToken other; |
| memcpy(&other, arg, sizeof(other)); |
| return other == sync_token; |
| } |
| |
| class GLRendererTest : public testing::Test { |
| protected: |
| ~GLRendererTest() override { |
| // Some tests create CopyOutputRequests which will PostTask ensure |
| // they are all cleaned up and completed before destroying the test. |
| VizTestSuite::RunUntilIdle(); |
| } |
| AggregatedRenderPass* root_render_pass() { |
| return render_passes_in_draw_order_.back().get(); |
| } |
| void DrawFrame(GLRenderer* renderer, |
| const gfx::Size& viewport_size, |
| const gfx::DisplayColorSpaces& display_color_spaces = |
| gfx::DisplayColorSpaces()) { |
| SurfaceDamageRectList surface_damage_rect_list; |
| renderer->DrawFrame(&render_passes_in_draw_order_, 1.f, viewport_size, |
| display_color_spaces, |
| std::move(surface_damage_rect_list)); |
| } |
| |
| static const Program* current_program(GLRenderer* renderer) { |
| return renderer->current_program_; |
| } |
| |
| static TexCoordPrecision get_cached_tex_coord_precision( |
| GLRenderer* renderer) { |
| return renderer->draw_cache_.program_key.tex_coord_precision(); |
| } |
| |
| DebugRendererSettings debug_settings_; |
| AggregatedRenderPassList render_passes_in_draw_order_; |
| }; |
| |
| #define EXPECT_PROGRAM_VALID(program_binding) \ |
| do { \ |
| ASSERT_TRUE(program_binding); \ |
| EXPECT_TRUE((program_binding)->program()); \ |
| EXPECT_TRUE((program_binding)->initialized()); \ |
| } while (false) |
| |
| static inline SkBlendMode BlendModeToSkXfermode(BlendMode blend_mode) { |
| switch (blend_mode) { |
| case BLEND_MODE_NONE: |
| case BLEND_MODE_NORMAL: |
| return SkBlendMode::kSrcOver; |
| case BLEND_MODE_DESTINATION_IN: |
| return SkBlendMode::kDstIn; |
| case BLEND_MODE_SCREEN: |
| return SkBlendMode::kScreen; |
| case BLEND_MODE_OVERLAY: |
| return SkBlendMode::kOverlay; |
| case BLEND_MODE_DARKEN: |
| return SkBlendMode::kDarken; |
| case BLEND_MODE_LIGHTEN: |
| return SkBlendMode::kLighten; |
| case BLEND_MODE_COLOR_DODGE: |
| return SkBlendMode::kColorDodge; |
| case BLEND_MODE_COLOR_BURN: |
| return SkBlendMode::kColorBurn; |
| case BLEND_MODE_HARD_LIGHT: |
| return SkBlendMode::kHardLight; |
| case BLEND_MODE_SOFT_LIGHT: |
| return SkBlendMode::kSoftLight; |
| case BLEND_MODE_DIFFERENCE: |
| return SkBlendMode::kDifference; |
| case BLEND_MODE_EXCLUSION: |
| return SkBlendMode::kExclusion; |
| case BLEND_MODE_MULTIPLY: |
| return SkBlendMode::kMultiply; |
| case BLEND_MODE_HUE: |
| return SkBlendMode::kHue; |
| case BLEND_MODE_SATURATION: |
| return SkBlendMode::kSaturation; |
| case BLEND_MODE_COLOR: |
| return SkBlendMode::kColor; |
| case BLEND_MODE_LUMINOSITY: |
| return SkBlendMode::kLuminosity; |
| } |
| return SkBlendMode::kSrcOver; |
| } |
| |
| // Explicitly named to be a friend in GLRenderer for shader access. |
| class GLRendererShaderPixelTest : public cc::PixelTest { |
| public: |
| void SetUp() override { |
| SetUpGLRenderer(gfx::SurfaceOrigin::kBottomLeft); |
| ASSERT_FALSE(renderer()->IsContextLost()); |
| } |
| |
| void TearDown() override { |
| cc::PixelTest::TearDown(); |
| ASSERT_FALSE(renderer()); |
| } |
| |
| GLRenderer* renderer() { return static_cast<GLRenderer*>(renderer_.get()); } |
| |
| void TestShaderWithDrawingFrame( |
| const ProgramKey& program_key, |
| const DirectRenderer::DrawingFrame& drawing_frame, |
| bool validate_output_color_matrix) { |
| renderer()->SetCurrentFrameForTesting(drawing_frame); |
| const gfx::ColorSpace kSrcColorSpaces[] = { |
| gfx::ColorSpace::CreateSRGB(), |
| gfx::ColorSpace(gfx::ColorSpace::PrimaryID::ADOBE_RGB, |
| gfx::ColorSpace::TransferID::GAMMA28), |
| gfx::ColorSpace::CreateREC709(), |
| gfx::ColorSpace::CreateExtendedSRGB(), |
| // This will be adjusted to the display's SDR white level, because no |
| // level was specified. |
| gfx::ColorSpace::CreateSCRGBLinear(), |
| // This won't be, because it has a set SDR white level. |
| gfx::ColorSpace::CreateSCRGBLinear(123.0f), |
| // This will be adjusted to the display's SDR white level, because no |
| // level was specified. |
| gfx::ColorSpace::CreateHDR10(), |
| // This won't be, because it has a set SDR white level. |
| gfx::ColorSpace::CreateHDR10(123.0f), |
| }; |
| const gfx::ColorSpace kDstColorSpaces[] = { |
| gfx::ColorSpace::CreateSRGB(), |
| gfx::ColorSpace(gfx::ColorSpace::PrimaryID::ADOBE_RGB, |
| gfx::ColorSpace::TransferID::GAMMA18), |
| gfx::ColorSpace::CreateExtendedSRGB(), |
| gfx::ColorSpace::CreateSCRGBLinear(), |
| }; |
| // Note: Use ASSERT_XXX() and not EXPECT_XXX() below since the size of the |
| // loop will lead to useless timeout failures on the bots otherwise. |
| for (const auto& src_color_space : kSrcColorSpaces) { |
| for (const auto& dst_color_space : kDstColorSpaces) { |
| renderer()->SetUseProgram(program_key, src_color_space, dst_color_space, |
| /*adjust_src_white_level=*/true); |
| ASSERT_TRUE(renderer()->current_program_->initialized()); |
| |
| if (src_color_space != dst_color_space) { |
| auto adjusted_color_space = src_color_space; |
| if (src_color_space.IsHDR()) { |
| adjusted_color_space = src_color_space.GetWithSDRWhiteLevel( |
| drawing_frame.display_color_spaces.GetSDRWhiteLevel()); |
| } |
| SCOPED_TRACE( |
| base::StringPrintf("adjusted_color_space=%s, dst_color_space=%s", |
| adjusted_color_space.ToString().c_str(), |
| dst_color_space.ToString().c_str())); |
| |
| auto color_transform = gfx::ColorTransform::NewColorTransform( |
| adjusted_color_space, dst_color_space); |
| |
| ASSERT_EQ(color_transform->GetShaderSource(), |
| renderer() |
| ->current_program_->color_transform_for_testing() |
| ->GetShaderSource()); |
| } |
| |
| if (validate_output_color_matrix) { |
| if (program_key.type() == ProgramType::PROGRAM_TYPE_SOLID_COLOR) { |
| ASSERT_EQ( |
| -1, |
| renderer()->current_program_->output_color_matrix_location()); |
| } else { |
| ASSERT_NE( |
| -1, |
| renderer()->current_program_->output_color_matrix_location()); |
| } |
| } |
| } |
| } |
| } |
| |
| void TestShader(const ProgramKey& program_key) { |
| TestShaderWithDrawingFrame(program_key, GLRenderer::DrawingFrame(), false); |
| } |
| |
| void TestShadersWithOutputColorMatrix(const ProgramKey& program_key) { |
| GLRenderer::DrawingFrame frame; |
| |
| AggregatedRenderPassList render_passes_in_draw_order; |
| gfx::Size viewport_size(100, 100); |
| AggregatedRenderPassId root_pass_id{1}; |
| auto* root_pass = cc::AddRenderPass( |
| &render_passes_in_draw_order, root_pass_id, gfx::Rect(viewport_size), |
| gfx::Transform(), cc::FilterOperations()); |
| root_pass->damage_rect = gfx::Rect(0, 0, 25, 25); |
| |
| frame.root_render_pass = root_pass; |
| frame.current_render_pass = root_pass; |
| frame.render_passes_in_draw_order = &render_passes_in_draw_order; |
| |
| // Set a non-identity color matrix on the output surface. |
| skia::Matrix44 color_matrix(skia::Matrix44::kIdentity_Constructor); |
| color_matrix.set(0, 0, 0.7f); |
| color_matrix.set(1, 1, 0.4f); |
| color_matrix.set(2, 2, 0.5f); |
| renderer()->output_surface_->set_color_matrix(color_matrix); |
| |
| TestShaderWithDrawingFrame(program_key, frame, true); |
| } |
| |
| void TestShadersWithSDRWhiteLevel(const ProgramKey& program_key, |
| float sdr_white_level) { |
| GLRenderer::DrawingFrame frame; |
| frame.display_color_spaces.SetSDRWhiteLevel(sdr_white_level); |
| TestShaderWithDrawingFrame(program_key, frame, false); |
| } |
| |
| void TestBasicShaders() { |
| TestShader(ProgramKey::DebugBorder()); |
| TestShader(ProgramKey::SolidColor(NO_AA, false, false)); |
| TestShader(ProgramKey::SolidColor(USE_AA, false, false)); |
| TestShader(ProgramKey::SolidColor(NO_AA, true, false)); |
| |
| TestShadersWithOutputColorMatrix(ProgramKey::DebugBorder()); |
| TestShadersWithOutputColorMatrix( |
| ProgramKey::SolidColor(NO_AA, false, false)); |
| TestShadersWithOutputColorMatrix( |
| ProgramKey::SolidColor(USE_AA, false, false)); |
| TestShadersWithOutputColorMatrix( |
| ProgramKey::SolidColor(NO_AA, true, false)); |
| |
| TestShadersWithSDRWhiteLevel(ProgramKey::DebugBorder(), 200.f); |
| TestShadersWithSDRWhiteLevel(ProgramKey::SolidColor(NO_AA, false, false), |
| 200.f); |
| TestShadersWithSDRWhiteLevel(ProgramKey::SolidColor(USE_AA, false, false), |
| 200.f); |
| TestShadersWithSDRWhiteLevel(ProgramKey::SolidColor(NO_AA, true, false), |
| 200.f); |
| } |
| |
| void TestColorShaders() { |
| const size_t kNumTransferFns = 7; |
| skcms_TransferFunction transfer_fns[kNumTransferFns] = { |
| // The identity. |
| {1.f, 1.f, 0.f, 1.f, 0.f, 0.f, 0.f}, |
| // The identity, with an if statement. |
| {1.f, 1.f, 0.f, 1.f, 0.5f, 0.f, 0.f}, |
| // Just the power function. |
| {1.1f, 1.f, 0.f, 1.f, 0.f, 0.f, 0.f}, |
| // Everything but the power function, nonlinear only. |
| {1.f, 0.9f, 0.1f, 0.9f, 0.f, 0.1f, 0.1f}, |
| // Everything, nonlinear only. |
| {1.1f, 0.9f, 0.1f, 0.9f, 0.f, 0.1f, 0.1f}, |
| // Everything but the power function. |
| {1.f, 0.9f, 0.1f, 0.9f, 0.5f, 0.1f, 0.1f}, |
| // Everything. |
| {1.1f, 0.9f, 0.1f, 0.9f, 0.5f, 0.1f, 0.1f}, |
| }; |
| |
| for (size_t i = 0; i < kNumTransferFns; ++i) { |
| skcms_Matrix3x3 primaries; |
| gfx::ColorSpace::CreateSRGB().GetPrimaryMatrix(&primaries); |
| gfx::ColorSpace src = |
| gfx::ColorSpace::CreateCustom(primaries, transfer_fns[i]); |
| |
| renderer()->SetCurrentFrameForTesting(GLRenderer::DrawingFrame()); |
| renderer()->SetUseProgram(ProgramKey::SolidColor(NO_AA, false, false), |
| src, gfx::ColorSpace::CreateXYZD50()); |
| EXPECT_TRUE(renderer()->current_program_->initialized()); |
| } |
| } |
| |
| void TestShadersWithPrecision(TexCoordPrecision precision) { |
| // This program uses external textures and sampler, so it won't compile |
| // everywhere. |
| if (context_provider()->ContextCapabilities().egl_image_external) { |
| TestShader(ProgramKey::VideoStream(precision, false)); |
| } |
| } |
| |
| void TestShadersWithPrecisionAndBlend(TexCoordPrecision precision, |
| BlendMode blend_mode) { |
| TestShader(ProgramKey::RenderPass(precision, SAMPLER_TYPE_2D, blend_mode, |
| NO_AA, NO_MASK, false, false, false, |
| false)); |
| TestShader(ProgramKey::RenderPass(precision, SAMPLER_TYPE_2D, blend_mode, |
| USE_AA, NO_MASK, false, false, false, |
| false)); |
| } |
| |
| void TestShadersWithPrecisionAndSampler( |
| TexCoordPrecision precision, |
| SamplerType sampler, |
| PremultipliedAlphaMode premultipliedAlpha, |
| bool has_background_color, |
| bool has_tex_clamp_rect) { |
| TestShader(ProgramKey::Texture(precision, sampler, premultipliedAlpha, |
| has_background_color, has_tex_clamp_rect, |
| false, false)); |
| } |
| |
| void TestShadersWithPrecisionAndSamplerTiledAA( |
| TexCoordPrecision precision, |
| SamplerType sampler, |
| PremultipliedAlphaMode premultipliedAlpha) { |
| TestShader(ProgramKey::Tile(precision, sampler, USE_AA, premultipliedAlpha, |
| false, false, false, false)); |
| } |
| |
| void TestShadersWithPrecisionAndSamplerTiled( |
| TexCoordPrecision precision, |
| SamplerType sampler, |
| PremultipliedAlphaMode premultipliedAlpha, |
| bool is_opaque, |
| bool has_tex_clamp_rect) { |
| TestShader(ProgramKey::Tile(precision, sampler, NO_AA, premultipliedAlpha, |
| is_opaque, has_tex_clamp_rect, false, false)); |
| } |
| |
| void TestYUVShadersWithPrecisionAndSampler(TexCoordPrecision precision, |
| SamplerType sampler) { |
| // Iterate over alpha plane and nv12 parameters. |
| UVTextureMode uv_modes[2] = {UV_TEXTURE_MODE_UV, UV_TEXTURE_MODE_U_V}; |
| YUVAlphaTextureMode a_modes[2] = {YUV_NO_ALPHA_TEXTURE, |
| YUV_HAS_ALPHA_TEXTURE}; |
| for (auto uv_mode : uv_modes) { |
| SCOPED_TRACE(uv_mode); |
| for (auto a_mode : a_modes) { |
| SCOPED_TRACE(a_mode); |
| TestShader(ProgramKey::YUVVideo(precision, sampler, a_mode, uv_mode, |
| false, false)); |
| } |
| } |
| } |
| |
| void TestShadersWithMasks(TexCoordPrecision precision, |
| SamplerType sampler, |
| BlendMode blend_mode, |
| bool mask_for_background) { |
| TestShader(ProgramKey::RenderPass(precision, sampler, blend_mode, NO_AA, |
| HAS_MASK, mask_for_background, false, |
| false, false)); |
| TestShader(ProgramKey::RenderPass(precision, sampler, blend_mode, NO_AA, |
| HAS_MASK, mask_for_background, true, |
| false, false)); |
| TestShader(ProgramKey::RenderPass(precision, sampler, blend_mode, USE_AA, |
| HAS_MASK, mask_for_background, false, |
| false, false)); |
| TestShader(ProgramKey::RenderPass(precision, sampler, blend_mode, USE_AA, |
| HAS_MASK, mask_for_background, true, |
| false, false)); |
| } |
| }; |
| |
| namespace { |
| |
| #if !defined(OS_ANDROID) |
| static const TexCoordPrecision kPrecisionList[] = {TEX_COORD_PRECISION_MEDIUM, |
| TEX_COORD_PRECISION_HIGH}; |
| |
| static const BlendMode kBlendModeList[LAST_BLEND_MODE + 1] = { |
| BLEND_MODE_NONE, BLEND_MODE_NORMAL, BLEND_MODE_DESTINATION_IN, |
| BLEND_MODE_SCREEN, BLEND_MODE_OVERLAY, BLEND_MODE_DARKEN, |
| BLEND_MODE_LIGHTEN, BLEND_MODE_COLOR_DODGE, BLEND_MODE_COLOR_BURN, |
| BLEND_MODE_HARD_LIGHT, BLEND_MODE_SOFT_LIGHT, BLEND_MODE_DIFFERENCE, |
| BLEND_MODE_EXCLUSION, BLEND_MODE_MULTIPLY, BLEND_MODE_HUE, |
| BLEND_MODE_SATURATION, BLEND_MODE_COLOR, BLEND_MODE_LUMINOSITY, |
| }; |
| |
| static const SamplerType kSamplerList[] = { |
| SAMPLER_TYPE_2D, SAMPLER_TYPE_2D_RECT, SAMPLER_TYPE_EXTERNAL_OES, |
| }; |
| |
| static const PremultipliedAlphaMode kPremultipliedAlphaModeList[] = { |
| PREMULTIPLIED_ALPHA, NON_PREMULTIPLIED_ALPHA}; |
| |
| TEST_F(GLRendererShaderPixelTest, BasicShadersCompile) { |
| TestBasicShaders(); |
| } |
| |
| TEST_F(GLRendererShaderPixelTest, TestColorShadersCompile) { |
| TestColorShaders(); |
| } |
| |
| class PrecisionShaderPixelTest |
| : public GLRendererShaderPixelTest, |
| public ::testing::WithParamInterface<TexCoordPrecision> {}; |
| |
| TEST_P(PrecisionShaderPixelTest, ShadersCompile) { |
| TestShadersWithPrecision(GetParam()); |
| } |
| |
| INSTANTIATE_TEST_SUITE_P(PrecisionShadersCompile, |
| PrecisionShaderPixelTest, |
| ::testing::ValuesIn(kPrecisionList)); |
| |
| class PrecisionBlendShaderPixelTest |
| : public GLRendererShaderPixelTest, |
| public ::testing::WithParamInterface< |
| std::tuple<TexCoordPrecision, BlendMode>> {}; |
| |
| TEST_P(PrecisionBlendShaderPixelTest, ShadersCompile) { |
| TestShadersWithPrecisionAndBlend(std::get<0>(GetParam()), |
| std::get<1>(GetParam())); |
| } |
| |
| INSTANTIATE_TEST_SUITE_P( |
| PrecisionBlendShadersCompile, |
| PrecisionBlendShaderPixelTest, |
| ::testing::Combine(::testing::ValuesIn(kPrecisionList), |
| ::testing::ValuesIn(kBlendModeList))); |
| |
| class PrecisionSamplerShaderPixelTest |
| : public GLRendererShaderPixelTest, |
| public ::testing::WithParamInterface< |
| std::tuple<TexCoordPrecision, |
| SamplerType, |
| PremultipliedAlphaMode, |
| bool, // has_background_color |
| bool>> {}; // has_tex_clamp_rect |
| |
| TEST_P(PrecisionSamplerShaderPixelTest, ShadersCompile) { |
| SamplerType sampler = std::get<1>(GetParam()); |
| if (sampler != SAMPLER_TYPE_2D_RECT || |
| context_provider()->ContextCapabilities().texture_rectangle) { |
| TestShadersWithPrecisionAndSampler( |
| std::get<0>(GetParam()), // TexCoordPrecision |
| sampler, |
| std::get<2>(GetParam()), // PremultipliedAlphaMode |
| std::get<3>(GetParam()), // has_background_color |
| std::get<4>(GetParam())); // has_tex_clamp_rect |
| } |
| } |
| |
| INSTANTIATE_TEST_SUITE_P( |
| PrecisionSamplerShadersCompile, |
| PrecisionSamplerShaderPixelTest, |
| ::testing::Combine(::testing::ValuesIn(kPrecisionList), |
| ::testing::ValuesIn(kSamplerList), |
| ::testing::ValuesIn(kPremultipliedAlphaModeList), |
| ::testing::Bool(), // has_background_color |
| ::testing::Bool())); // has_tex_clamp_rect |
| |
| class PrecisionSamplerShaderPixelTestTiled |
| : public GLRendererShaderPixelTest, |
| public ::testing::WithParamInterface< |
| std::tuple<TexCoordPrecision, |
| SamplerType, |
| PremultipliedAlphaMode, |
| bool, // is_opaque |
| bool>> // has_tex_clamp_rect |
| {}; |
| |
| TEST_P(PrecisionSamplerShaderPixelTestTiled, ShadersCompile) { |
| SamplerType sampler = std::get<1>(GetParam()); |
| if (sampler != SAMPLER_TYPE_2D_RECT || |
| context_provider()->ContextCapabilities().texture_rectangle) { |
| TestShadersWithPrecisionAndSamplerTiled( |
| std::get<0>(GetParam()), // TexCoordPrecision |
| sampler, |
| std::get<2>(GetParam()), // PremultipliedAlphaMode |
| std::get<3>(GetParam()), // is_opaque |
| std::get<4>(GetParam())); // has_tex_clamp_rect |
| } |
| } |
| |
| INSTANTIATE_TEST_SUITE_P( |
| PrecisionSamplerShadersCompile, |
| PrecisionSamplerShaderPixelTestTiled, |
| ::testing::Combine(::testing::ValuesIn(kPrecisionList), |
| ::testing::ValuesIn(kSamplerList), |
| ::testing::ValuesIn(kPremultipliedAlphaModeList), |
| ::testing::Bool(), // is_opaque |
| ::testing::Bool())); // has_tex_clamp_rect |
| |
| class PrecisionSamplerShaderPixelTestTiledAA |
| : public GLRendererShaderPixelTest, |
| public ::testing::WithParamInterface< |
| std::tuple<TexCoordPrecision, SamplerType, PremultipliedAlphaMode>> { |
| }; |
| |
| TEST_P(PrecisionSamplerShaderPixelTestTiledAA, ShadersCompile) { |
| SamplerType sampler = std::get<1>(GetParam()); |
| if (sampler != SAMPLER_TYPE_2D_RECT || |
| context_provider()->ContextCapabilities().texture_rectangle) { |
| TestShadersWithPrecisionAndSamplerTiledAA( |
| std::get<0>(GetParam()), // TexCoordPrecision |
| sampler, |
| std::get<2>(GetParam())); // PremultipliedAlphaMode |
| } |
| } |
| |
| INSTANTIATE_TEST_SUITE_P( |
| PrecisionSamplerShadersCompile, |
| PrecisionSamplerShaderPixelTestTiledAA, |
| ::testing::Combine(::testing::ValuesIn(kPrecisionList), |
| ::testing::ValuesIn(kSamplerList), |
| ::testing::ValuesIn(kPremultipliedAlphaModeList))); |
| |
| class PrecisionSamplerYUVShaderPixelTest |
| : public GLRendererShaderPixelTest, |
| public ::testing::WithParamInterface< |
| std::tuple<TexCoordPrecision, SamplerType>> {}; |
| |
| TEST_P(PrecisionSamplerYUVShaderPixelTest, ShadersCompile) { |
| SamplerType sampler = std::get<1>(GetParam()); |
| if (sampler != SAMPLER_TYPE_2D_RECT || |
| context_provider()->ContextCapabilities().texture_rectangle) { |
| TestYUVShadersWithPrecisionAndSampler( |
| std::get<0>(GetParam()), // TexCoordPrecision |
| sampler); |
| } |
| } |
| |
| INSTANTIATE_TEST_SUITE_P(PrecisionSamplerShadersCompile, |
| PrecisionSamplerYUVShaderPixelTest, |
| ::testing::Combine(::testing::ValuesIn(kPrecisionList), |
| ::testing::ValuesIn(kSamplerList))); |
| |
| class MaskShaderPixelTest |
| : public GLRendererShaderPixelTest, |
| public ::testing::WithParamInterface< |
| std::tuple<TexCoordPrecision, SamplerType, BlendMode, bool>> {}; |
| |
| TEST_P(MaskShaderPixelTest, ShadersCompile) { |
| SamplerType sampler = std::get<1>(GetParam()); |
| if (sampler != SAMPLER_TYPE_2D_RECT || |
| context_provider()->ContextCapabilities().texture_rectangle) { |
| TestShadersWithMasks(std::get<0>(GetParam()), sampler, |
| std::get<2>(GetParam()), std::get<3>(GetParam())); |
| } |
| } |
| |
| INSTANTIATE_TEST_SUITE_P(MaskShadersCompile, |
| MaskShaderPixelTest, |
| ::testing::Combine(::testing::ValuesIn(kPrecisionList), |
| ::testing::ValuesIn(kSamplerList), |
| ::testing::ValuesIn(kBlendModeList), |
| ::testing::Bool())); |
| |
| #endif |
| |
| class FakeRendererGL : public GLRenderer { |
| public: |
| FakeRendererGL(const RendererSettings* settings, |
| const DebugRendererSettings* debug_settings, |
| OutputSurface* output_surface, |
| DisplayResourceProviderGL* resource_provider) |
| : GLRenderer(settings, |
| debug_settings, |
| output_surface, |
| resource_provider, |
| nullptr, |
| nullptr) {} |
| |
| FakeRendererGL(const RendererSettings* settings, |
| const DebugRendererSettings* debug_settings, |
| OutputSurface* output_surface, |
| DisplayResourceProviderGL* resource_provider, |
| OverlayProcessorInterface* overlay_processor) |
| : GLRenderer(settings, |
| debug_settings, |
| output_surface, |
| resource_provider, |
| overlay_processor, |
| nullptr) {} |
| |
| FakeRendererGL( |
| const RendererSettings* settings, |
| const DebugRendererSettings* debug_settings, |
| OutputSurface* output_surface, |
| DisplayResourceProviderGL* resource_provider, |
| OverlayProcessorInterface* overlay_processor, |
| scoped_refptr<base::SingleThreadTaskRunner> current_task_runner) |
| : GLRenderer(settings, |
| debug_settings, |
| output_surface, |
| resource_provider, |
| overlay_processor, |
| std::move(current_task_runner)) {} |
| |
| // GLRenderer methods. |
| |
| // Changing visibility to public. |
| using GLRenderer::stencil_enabled; |
| }; |
| |
| class GLRendererWithDefaultHarnessTest : public GLRendererTest { |
| protected: |
| GLRendererWithDefaultHarnessTest() { |
| output_surface_ = FakeOutputSurface::Create3d(); |
| output_surface_->BindToClient(&output_surface_client_); |
| |
| resource_provider_ = std::make_unique<DisplayResourceProviderGL>( |
| output_surface_->context_provider()); |
| renderer_ = std::make_unique<FakeRendererGL>(&settings_, &debug_settings_, |
| output_surface_.get(), |
| resource_provider_.get()); |
| renderer_->Initialize(); |
| renderer_->SetVisible(true); |
| } |
| |
| void SwapBuffers() { renderer_->SwapBuffers({}); } |
| |
| RendererSettings settings_; |
| cc::FakeOutputSurfaceClient output_surface_client_; |
| std::unique_ptr<FakeOutputSurface> output_surface_; |
| std::unique_ptr<DisplayResourceProviderGL> resource_provider_; |
| std::unique_ptr<FakeRendererGL> renderer_; |
| }; |
| |
| // Closing the namespace here so that GLRendererShaderTest can take advantage |
| // of the friend relationship with GLRenderer and all of the mock classes |
| // declared above it. |
| } // namespace |
| |
| class GLRendererShaderTest : public GLRendererTest { |
| protected: |
| GLRendererShaderTest() { |
| output_surface_ = FakeOutputSurface::Create3d(); |
| output_surface_->BindToClient(&output_surface_client_); |
| |
| resource_provider_ = std::make_unique<DisplayResourceProviderGL>( |
| output_surface_->context_provider()); |
| renderer_ = std::make_unique<FakeRendererGL>( |
| &settings_, &debug_settings_, output_surface_.get(), |
| resource_provider_.get(), nullptr); |
| renderer_->Initialize(); |
| renderer_->SetVisible(true); |
| |
| child_context_provider_ = TestContextProvider::Create(); |
| child_context_provider_->BindToCurrentThread(); |
| child_resource_provider_ = std::make_unique<ClientResourceProvider>(); |
| } |
| |
| ~GLRendererShaderTest() override { |
| child_resource_provider_->ShutdownAndReleaseAllResources(); |
| } |
| |
| void TestRenderPassProgram(TexCoordPrecision precision, |
| BlendMode blend_mode) { |
| const Program* program = renderer_->GetProgramIfInitialized( |
| ProgramKey::RenderPass(precision, SAMPLER_TYPE_2D, blend_mode, NO_AA, |
| NO_MASK, false, false, false, false)); |
| EXPECT_PROGRAM_VALID(program); |
| EXPECT_EQ(program, renderer_->current_program_); |
| } |
| |
| void TestRenderPassColorMatrixProgram(TexCoordPrecision precision, |
| BlendMode blend_mode) { |
| const Program* program = renderer_->GetProgramIfInitialized( |
| ProgramKey::RenderPass(precision, SAMPLER_TYPE_2D, blend_mode, NO_AA, |
| NO_MASK, false, true, false, false)); |
| EXPECT_PROGRAM_VALID(program); |
| EXPECT_EQ(program, renderer_->current_program_); |
| } |
| |
| void TestRenderPassMaskProgram(TexCoordPrecision precision, |
| SamplerType sampler, |
| BlendMode blend_mode) { |
| const Program* program = renderer_->GetProgramIfInitialized( |
| ProgramKey::RenderPass(precision, sampler, blend_mode, NO_AA, HAS_MASK, |
| false, false, false, false)); |
| EXPECT_PROGRAM_VALID(program); |
| EXPECT_EQ(program, renderer_->current_program_); |
| } |
| |
| void TestRenderPassMaskColorMatrixProgram(TexCoordPrecision precision, |
| SamplerType sampler, |
| BlendMode blend_mode) { |
| const Program* program = renderer_->GetProgramIfInitialized( |
| ProgramKey::RenderPass(precision, sampler, blend_mode, NO_AA, HAS_MASK, |
| false, true, false, false)); |
| EXPECT_PROGRAM_VALID(program); |
| EXPECT_EQ(program, renderer_->current_program_); |
| } |
| |
| void TestRenderPassProgramAA(TexCoordPrecision precision, |
| BlendMode blend_mode) { |
| const Program* program = renderer_->GetProgramIfInitialized( |
| ProgramKey::RenderPass(precision, SAMPLER_TYPE_2D, blend_mode, USE_AA, |
| NO_MASK, false, false, false, false)); |
| EXPECT_PROGRAM_VALID(program); |
| EXPECT_EQ(program, renderer_->current_program_); |
| } |
| |
| void TestRenderPassColorMatrixProgramAA(TexCoordPrecision precision, |
| BlendMode blend_mode) { |
| const Program* program = renderer_->GetProgramIfInitialized( |
| ProgramKey::RenderPass(precision, SAMPLER_TYPE_2D, blend_mode, USE_AA, |
| NO_MASK, false, true, false, false)); |
| EXPECT_PROGRAM_VALID(program); |
| EXPECT_EQ(program, renderer_->current_program_); |
| } |
| |
| void TestRenderPassMaskProgramAA(TexCoordPrecision precision, |
| SamplerType sampler, |
| BlendMode blend_mode) { |
| const Program* program = renderer_->GetProgramIfInitialized( |
| ProgramKey::RenderPass(precision, sampler, blend_mode, USE_AA, HAS_MASK, |
| false, false, false, false)); |
| EXPECT_PROGRAM_VALID(program); |
| EXPECT_EQ(program, renderer_->current_program_); |
| } |
| |
| void TestRenderPassMaskColorMatrixProgramAA(TexCoordPrecision precision, |
| SamplerType sampler, |
| BlendMode blend_mode) { |
| const Program* program = renderer_->GetProgramIfInitialized( |
| ProgramKey::RenderPass(precision, sampler, blend_mode, USE_AA, HAS_MASK, |
| false, true, false, false)); |
| EXPECT_PROGRAM_VALID(program); |
| EXPECT_EQ(program, renderer_->current_program_); |
| } |
| |
| void TestSolidColorProgramAA() { |
| const Program* program = renderer_->GetProgramIfInitialized( |
| ProgramKey::SolidColor(USE_AA, false, false)); |
| EXPECT_PROGRAM_VALID(program); |
| EXPECT_EQ(program, renderer_->current_program_); |
| } |
| |
| RendererSettings settings_; |
| cc::FakeOutputSurfaceClient output_surface_client_; |
| std::unique_ptr<FakeOutputSurface> output_surface_; |
| std::unique_ptr<DisplayResourceProviderGL> resource_provider_; |
| scoped_refptr<TestContextProvider> child_context_provider_; |
| std::unique_ptr<ClientResourceProvider> child_resource_provider_; |
| std::unique_ptr<FakeRendererGL> renderer_; |
| }; |
| |
| namespace { |
| |
| TEST_F(GLRendererWithDefaultHarnessTest, ExternalStencil) { |
| gfx::Size viewport_size(1, 1); |
| EXPECT_FALSE(renderer_->stencil_enabled()); |
| |
| output_surface_->set_has_external_stencil_test(true); |
| |
| auto* root_pass = cc::AddRenderPass( |
| &render_passes_in_draw_order_, AggregatedRenderPassId{1}, |
| gfx::Rect(viewport_size), gfx::Transform(), cc::FilterOperations()); |
| root_pass->has_transparent_background = false; |
| |
| DrawFrame(renderer_.get(), viewport_size); |
| EXPECT_TRUE(renderer_->stencil_enabled()); |
| } |
| |
| TEST_F(GLRendererWithDefaultHarnessTest, TextureDrawQuadShaderPrecisionHigh) { |
| // TestContextProvider, used inside FakeOuputSurfaceClient, redefines |
| // GetShaderPrecisionFormat() and sets the resolution of mediump with |
| // 10-bits (1024). So any value higher than 1024 should use highp. |
| // The goal is to make sure the fragment shaders used in DoDrawQuad() use |
| // the correct precision qualifier. |
| |
| const gfx::Size viewport_size(1, 1); |
| auto* root_pass = cc::AddRenderPass( |
| &render_passes_in_draw_order_, AggregatedRenderPassId{1}, |
| gfx::Rect(viewport_size), gfx::Transform(), cc::FilterOperations()); |
| |
| const bool needs_blending = false; |
| const bool premultiplied_alpha = false; |
| const bool flipped = false; |
| const bool nearest_neighbor = false; |
| const float vertex_opacity[4] = {1.0f, 1.0f, 1.0f, 1.0f}; |
| const gfx::PointF uv_top_left(0, 0); |
| const gfx::PointF uv_bottom_right(1, 1); |
| |
| auto child_context_provider = TestContextProvider::Create(); |
| child_context_provider->BindToCurrentThread(); |
| |
| auto child_resource_provider = std::make_unique<ClientResourceProvider>(); |
| |
| // Here is where the texture is created. Any value bigger than 1024 should use |
| // a highp. |
| auto transfer_resource = TransferableResource::MakeGL( |
| gpu::Mailbox::Generate(), GL_LINEAR, GL_TEXTURE_2D, gpu::SyncToken(), |
| gfx::Size(1025, 1025), true); |
| ResourceId client_resource_id = child_resource_provider->ImportResource( |
| transfer_resource, base::DoNothing()); |
| |
| std::unordered_map<ResourceId, ResourceId, ResourceIdHasher> resource_map = |
| cc::SendResourceAndGetChildToParentMap( |
| {client_resource_id}, resource_provider_.get(), |
| child_resource_provider.get(), child_context_provider.get()); |
| ResourceId resource_id = resource_map[client_resource_id]; |
| |
| // The values defined here should not alter the size of the already created |
| // texture. |
| TextureDrawQuad* overlay_quad = |
| root_pass->CreateAndAppendDrawQuad<TextureDrawQuad>(); |
| SharedQuadState* shared_state = root_pass->CreateAndAppendSharedQuadState(); |
| shared_state->SetAll(gfx::Transform(), gfx::Rect(viewport_size), |
| gfx::Rect(1023, 1023), gfx::MaskFilterInfo(), |
| absl::nullopt, false, 1, SkBlendMode::kSrcOver, 0); |
| overlay_quad->SetNew(shared_state, gfx::Rect(1023, 1023), |
| gfx::Rect(1023, 1023), needs_blending, resource_id, |
| premultiplied_alpha, uv_top_left, uv_bottom_right, |
| SK_ColorTRANSPARENT, vertex_opacity, flipped, |
| nearest_neighbor, /*secure_output_only=*/false, |
| gfx::ProtectedVideoType::kClear); |
| |
| DrawFrame(renderer_.get(), viewport_size); |
| |
| TexCoordPrecision precision = get_cached_tex_coord_precision(renderer_.get()); |
| EXPECT_EQ(precision, TEX_COORD_PRECISION_HIGH); |
| |
| child_resource_provider->ShutdownAndReleaseAllResources(); |
| } |
| |
| TEST_F(GLRendererWithDefaultHarnessTest, TextureDrawQuadShaderPrecisionMedium) { |
| // TestContextProvider, used inside FakeOuputSurfaceClient, redefines |
| // GetShaderPrecisionFormat() and sets the resolution of mediump with |
| // 10-bits (1024). So any value higher than 1024 should use highp. |
| // The goal is to make sure the fragment shaders used in DoDrawQuad() use |
| // the correct precision qualifier. |
| |
| const gfx::Size viewport_size(1, 1); |
| auto* root_pass = cc::AddRenderPass( |
| &render_passes_in_draw_order_, AggregatedRenderPassId{1}, |
| gfx::Rect(viewport_size), gfx::Transform(), cc::FilterOperations()); |
| |
| const bool needs_blending = false; |
| const bool premultiplied_alpha = false; |
| const bool flipped = false; |
| const bool nearest_neighbor = false; |
| const float vertex_opacity[4] = {1.0f, 1.0f, 1.0f, 1.0f}; |
| const gfx::PointF uv_top_left(0, 0); |
| const gfx::PointF uv_bottom_right(1, 1); |
| |
| auto child_context_provider = TestContextProvider::Create(); |
| child_context_provider->BindToCurrentThread(); |
| |
| auto child_resource_provider = std::make_unique<ClientResourceProvider>(); |
| |
| // Here is where the texture is created. Any value smaller than 1024 should |
| // use a mediump. |
| auto transfer_resource = TransferableResource::MakeGL( |
| gpu::Mailbox::Generate(), GL_LINEAR, GL_TEXTURE_2D, gpu::SyncToken(), |
| gfx::Size(1023, 1023), true); |
| ResourceId client_resource_id = child_resource_provider->ImportResource( |
| transfer_resource, base::DoNothing()); |
| |
| std::unordered_map<ResourceId, ResourceId, ResourceIdHasher> resource_map = |
| cc::SendResourceAndGetChildToParentMap( |
| {client_resource_id}, resource_provider_.get(), |
| child_resource_provider.get(), child_context_provider.get()); |
| ResourceId resource_id = resource_map[client_resource_id]; |
| |
| // The values defined here should not alter the size of the already created |
| // texture. |
| TextureDrawQuad* overlay_quad = |
| root_pass->CreateAndAppendDrawQuad<TextureDrawQuad>(); |
| SharedQuadState* shared_state = root_pass->CreateAndAppendSharedQuadState(); |
| shared_state->SetAll(gfx::Transform(), gfx::Rect(viewport_size), |
| gfx::Rect(1025, 1025), gfx::MaskFilterInfo(), |
| absl::nullopt, false, 1, SkBlendMode::kSrcOver, 0); |
| overlay_quad->SetNew(shared_state, gfx::Rect(1025, 1025), |
| gfx::Rect(1025, 1025), needs_blending, resource_id, |
| premultiplied_alpha, uv_top_left, uv_bottom_right, |
| SK_ColorTRANSPARENT, vertex_opacity, flipped, |
| nearest_neighbor, /*secure_output_only=*/false, |
| gfx::ProtectedVideoType::kClear); |
| |
| DrawFrame(renderer_.get(), viewport_size); |
| |
| TexCoordPrecision precision = get_cached_tex_coord_precision(renderer_.get()); |
| EXPECT_EQ(precision, TEX_COORD_PRECISION_MEDIUM); |
| |
| child_resource_provider->ShutdownAndReleaseAllResources(); |
| } |
| |
| class GLRendererTextureDrawQuadHDRTest |
| : public GLRendererWithDefaultHarnessTest { |
| protected: |
| void RunTest(bool is_video_frame) { |
| const gfx::Size viewport_size(10, 10); |
| auto* root_pass = cc::AddRenderPass( |
| &render_passes_in_draw_order_, AggregatedRenderPassId{1}, |
| gfx::Rect(viewport_size), gfx::Transform(), cc::FilterOperations()); |
| |
| const bool needs_blending = false; |
| const bool premultiplied_alpha = false; |
| const bool flipped = false; |
| const bool nearest_neighbor = false; |
| const float vertex_opacity[4] = {1.0f, 1.0f, 1.0f, 1.0f}; |
| const gfx::PointF uv_top_left(0, 0); |
| const gfx::PointF uv_bottom_right(1, 1); |
| |
| auto child_context_provider = TestContextProvider::Create(); |
| child_context_provider->BindToCurrentThread(); |
| |
| auto child_resource_provider = std::make_unique<ClientResourceProvider>(); |
| |
| constexpr gfx::Size kTextureSize = gfx::Size(10, 10); |
| auto transfer_resource = TransferableResource::MakeGL( |
| gpu::Mailbox::Generate(), GL_LINEAR, GL_TEXTURE_2D, gpu::SyncToken(), |
| kTextureSize, true); |
| transfer_resource.color_space = gfx::ColorSpace::CreateSCRGBLinear(); |
| ResourceId client_resource_id = child_resource_provider->ImportResource( |
| transfer_resource, base::DoNothing()); |
| |
| std::unordered_map<ResourceId, ResourceId, ResourceIdHasher> resource_map = |
| cc::SendResourceAndGetChildToParentMap( |
| {client_resource_id}, resource_provider_.get(), |
| child_resource_provider.get(), child_context_provider.get()); |
| ResourceId resource_id = resource_map[client_resource_id]; |
| |
| TextureDrawQuad* overlay_quad = |
| root_pass->CreateAndAppendDrawQuad<TextureDrawQuad>(); |
| SharedQuadState* shared_state = root_pass->CreateAndAppendSharedQuadState(); |
| shared_state->SetAll(gfx::Transform(), gfx::Rect(viewport_size), |
| gfx::Rect(kTextureSize), gfx::MaskFilterInfo(), |
| absl::nullopt, false, 1, SkBlendMode::kSrcOver, 0); |
| overlay_quad->SetNew(shared_state, gfx::Rect(kTextureSize), |
| gfx::Rect(kTextureSize), needs_blending, resource_id, |
| premultiplied_alpha, uv_top_left, uv_bottom_right, |
| SK_ColorTRANSPARENT, vertex_opacity, flipped, |
| nearest_neighbor, /*secure_output_only=*/false, |
| gfx::ProtectedVideoType::kClear); |
| overlay_quad->is_video_frame = is_video_frame; |
| |
| constexpr float kSDRWhiteLevel = 123.0f; |
| gfx::DisplayColorSpaces display_color_spaces; |
| display_color_spaces.SetSDRWhiteLevel(kSDRWhiteLevel); |
| |
| DrawFrame(renderer_.get(), viewport_size, display_color_spaces); |
| |
| const Program* program = current_program(renderer_.get()); |
| DCHECK(program); |
| DCHECK(program->color_transform_for_testing()) |
| << program->fragment_shader().GetShaderString(); |
| |
| const gfx::ColorSpace expected_src_color_space = |
| is_video_frame |
| ? gfx::ColorSpace::CreateSCRGBLinear().GetWithSDRWhiteLevel( |
| kSDRWhiteLevel) |
| : gfx::ColorSpace::CreateSCRGBLinear(); |
| EXPECT_EQ(program->color_transform_for_testing()->GetSrcColorSpace(), |
| expected_src_color_space); |
| |
| child_resource_provider->ShutdownAndReleaseAllResources(); |
| } |
| }; |
| |
| TEST_F(GLRendererTextureDrawQuadHDRTest, VideoFrame) { |
| RunTest(/*is_video_frame=*/true); |
| } |
| |
| TEST_F(GLRendererTextureDrawQuadHDRTest, NotVideoFrame) { |
| RunTest(/*is_video_frame=*/false); |
| } |
| |
| class ForbidSynchronousCallGLES2Interface : public TestGLES2Interface { |
| public: |
| ForbidSynchronousCallGLES2Interface() = default; |
| |
| void GetAttachedShaders(GLuint program, |
| GLsizei max_count, |
| GLsizei* count, |
| GLuint* shaders) override { |
| ADD_FAILURE(); |
| } |
| |
| GLint GetAttribLocation(GLuint program, const GLchar* name) override { |
| ADD_FAILURE(); |
| return 0; |
| } |
| |
| void GetBooleanv(GLenum pname, GLboolean* value) override { ADD_FAILURE(); } |
| |
| void GetBufferParameteriv(GLenum target, |
| GLenum pname, |
| GLint* value) override { |
| ADD_FAILURE(); |
| } |
| |
| GLenum GetError() override { |
| ADD_FAILURE(); |
| return GL_NO_ERROR; |
| } |
| |
| void GetFloatv(GLenum pname, GLfloat* value) override { ADD_FAILURE(); } |
| |
| void GetFramebufferAttachmentParameteriv(GLenum target, |
| GLenum attachment, |
| GLenum pname, |
| GLint* value) override { |
| ADD_FAILURE(); |
| } |
| |
| void GetIntegerv(GLenum pname, GLint* value) override { |
| if (pname == GL_MAX_TEXTURE_SIZE) { |
| // MAX_TEXTURE_SIZE is cached client side, so it's OK to query. |
| *value = 1024; |
| } else { |
| ADD_FAILURE(); |
| } |
| } |
| |
| // We allow querying the shader compilation and program link status in debug |
| // mode, but not release. |
| void GetProgramiv(GLuint program, GLenum pname, GLint* value) override { |
| ADD_FAILURE(); |
| } |
| |
| void GetShaderiv(GLuint shader, GLenum pname, GLint* value) override { |
| ADD_FAILURE(); |
| } |
| |
| void GetRenderbufferParameteriv(GLenum target, |
| GLenum pname, |
| GLint* value) override { |
| ADD_FAILURE(); |
| } |
| |
| void GetShaderPrecisionFormat(GLenum shadertype, |
| GLenum precisiontype, |
| GLint* range, |
| GLint* precision) override { |
| ADD_FAILURE(); |
| } |
| |
| void GetTexParameterfv(GLenum target, GLenum pname, GLfloat* value) override { |
| ADD_FAILURE(); |
| } |
| |
| void GetTexParameteriv(GLenum target, GLenum pname, GLint* value) override { |
| ADD_FAILURE(); |
| } |
| |
| void GetUniformfv(GLuint program, GLint location, GLfloat* value) override { |
| ADD_FAILURE(); |
| } |
| |
| void GetUniformiv(GLuint program, GLint location, GLint* value) override { |
| ADD_FAILURE(); |
| } |
| |
| GLint GetUniformLocation(GLuint program, const GLchar* name) override { |
| ADD_FAILURE(); |
| return 0; |
| } |
| |
| void GetVertexAttribfv(GLuint index, GLenum pname, GLfloat* value) override { |
| ADD_FAILURE(); |
| } |
| |
| void GetVertexAttribiv(GLuint index, GLenum pname, GLint* value) override { |
| ADD_FAILURE(); |
| } |
| |
| void GetVertexAttribPointerv(GLuint index, |
| GLenum pname, |
| void** pointer) override { |
| ADD_FAILURE(); |
| } |
| }; |
| |
| TEST_F(GLRendererTest, InitializationDoesNotMakeSynchronousCalls) { |
| auto gl_owned = std::make_unique<ForbidSynchronousCallGLES2Interface>(); |
| auto provider = TestContextProvider::Create(std::move(gl_owned)); |
| provider->BindToCurrentThread(); |
| |
| cc::FakeOutputSurfaceClient output_surface_client; |
| std::unique_ptr<OutputSurface> output_surface( |
| FakeOutputSurface::Create3d(std::move(provider))); |
| output_surface->BindToClient(&output_surface_client); |
| |
| auto resource_provider = std::make_unique<DisplayResourceProviderGL>( |
| output_surface->context_provider()); |
| |
| RendererSettings settings; |
| FakeRendererGL renderer(&settings, &debug_settings_, output_surface.get(), |
| resource_provider.get()); |
| } |
| |
| class LoseContextOnFirstGetGLES2Interface : public TestGLES2Interface { |
| public: |
| LoseContextOnFirstGetGLES2Interface() {} |
| |
| void GetProgramiv(GLuint program, GLenum pname, GLint* value) override { |
| LoseContextCHROMIUM(GL_GUILTY_CONTEXT_RESET_ARB, |
| GL_INNOCENT_CONTEXT_RESET_ARB); |
| *value = 0; |
| } |
| |
| void GetShaderiv(GLuint shader, GLenum pname, GLint* value) override { |
| LoseContextCHROMIUM(GL_GUILTY_CONTEXT_RESET_ARB, |
| GL_INNOCENT_CONTEXT_RESET_ARB); |
| *value = 0; |
| } |
| }; |
| |
| TEST_F(GLRendererTest, InitializationWithQuicklyLostContextDoesNotAssert) { |
| auto gl_owned = std::make_unique<LoseContextOnFirstGetGLES2Interface>(); |
| auto provider = TestContextProvider::Create(std::move(gl_owned)); |
| provider->BindToCurrentThread(); |
| |
| cc::FakeOutputSurfaceClient output_surface_client; |
| std::unique_ptr<OutputSurface> output_surface( |
| FakeOutputSurface::Create3d(std::move(provider))); |
| output_surface->BindToClient(&output_surface_client); |
| |
| auto resource_provider = std::make_unique<DisplayResourceProviderGL>( |
| output_surface->context_provider()); |
| |
| RendererSettings settings; |
| FakeRendererGL renderer(&settings, &debug_settings_, output_surface.get(), |
| resource_provider.get()); |
| } |
| |
| class ClearCountingGLES2Interface : public TestGLES2Interface { |
| public: |
| ClearCountingGLES2Interface() = default; |
| |
| MOCK_METHOD3(DiscardFramebufferEXT, |
| void(GLenum target, |
| GLsizei numAttachments, |
| const GLenum* attachments)); |
| MOCK_METHOD1(Clear, void(GLbitfield mask)); |
| }; |
| |
| TEST_F(GLRendererTest, OpaqueBackground) { |
| auto gl_owned = std::make_unique<ClearCountingGLES2Interface>(); |
| gl_owned->set_have_discard_framebuffer(true); |
| |
| auto* gl = gl_owned.get(); |
| |
| auto provider = TestContextProvider::Create(std::move(gl_owned)); |
| provider->BindToCurrentThread(); |
| |
| cc::FakeOutputSurfaceClient output_surface_client; |
| std::unique_ptr<OutputSurface> output_surface( |
| FakeOutputSurface::Create3d(std::move(provider))); |
| output_surface->BindToClient(&output_surface_client); |
| |
| auto resource_provider = std::make_unique<DisplayResourceProviderGL>( |
| output_surface->context_provider()); |
| |
| RendererSettings settings; |
| FakeRendererGL renderer(&settings, &debug_settings_, output_surface.get(), |
| resource_provider.get()); |
| renderer.Initialize(); |
| renderer.SetVisible(true); |
| |
| gfx::Size viewport_size(1, 1); |
| AggregatedRenderPass* root_pass = cc::AddRenderPass( |
| &render_passes_in_draw_order_, AggregatedRenderPassId{1}, |
| gfx::Rect(viewport_size), gfx::Transform(), cc::FilterOperations()); |
| root_pass->has_transparent_background = false; |
| |
| // On DEBUG builds, render passes with opaque background clear to blue to |
| // easily see regions that were not drawn on the screen. |
| EXPECT_CALL(*gl, DiscardFramebufferEXT(GL_FRAMEBUFFER, _, _)) |
| .With(Args<2, 1>(ElementsAre(GL_COLOR_EXT))) |
| .Times(1); |
| #ifdef NDEBUG |
| EXPECT_CALL(*gl, Clear(_)).Times(0); |
| #else |
| EXPECT_CALL(*gl, Clear(_)).Times(1); |
| #endif |
| DrawFrame(&renderer, viewport_size); |
| Mock::VerifyAndClearExpectations(gl); |
| } |
| |
| TEST_F(GLRendererTest, TransparentBackground) { |
| auto gl_owned = std::make_unique<ClearCountingGLES2Interface>(); |
| auto* gl = gl_owned.get(); |
| gl_owned->set_have_discard_framebuffer(true); |
| |
| auto provider = TestContextProvider::Create(std::move(gl_owned)); |
| provider->BindToCurrentThread(); |
| |
| cc::FakeOutputSurfaceClient output_surface_client; |
| std::unique_ptr<OutputSurface> output_surface( |
| FakeOutputSurface::Create3d(std::move(provider))); |
| output_surface->BindToClient(&output_surface_client); |
| |
| auto resource_provider = std::make_unique<DisplayResourceProviderGL>( |
| output_surface->context_provider()); |
| |
| RendererSettings settings; |
| FakeRendererGL renderer(&settings, &debug_settings_, output_surface.get(), |
| resource_provider.get()); |
| renderer.Initialize(); |
| renderer.SetVisible(true); |
| |
| gfx::Size viewport_size(1, 1); |
| AggregatedRenderPass* root_pass = cc::AddRenderPass( |
| &render_passes_in_draw_order_, AggregatedRenderPassId{1}, |
| gfx::Rect(viewport_size), gfx::Transform(), cc::FilterOperations()); |
| root_pass->has_transparent_background = true; |
| |
| EXPECT_CALL(*gl, DiscardFramebufferEXT(GL_FRAMEBUFFER, 1, _)).Times(1); |
| EXPECT_CALL(*gl, Clear(_)).Times(1); |
| DrawFrame(&renderer, viewport_size); |
| |
| Mock::VerifyAndClearExpectations(gl); |
| } |
| |
| TEST_F(GLRendererTest, OffscreenOutputSurface) { |
| auto gl_owned = std::make_unique<ClearCountingGLES2Interface>(); |
| auto* gl = gl_owned.get(); |
| gl_owned->set_have_discard_framebuffer(true); |
| |
| auto provider = TestContextProvider::Create(std::move(gl_owned)); |
| provider->BindToCurrentThread(); |
| |
| cc::FakeOutputSurfaceClient output_surface_client; |
| std::unique_ptr<OutputSurface> output_surface( |
| FakeOutputSurface::CreateOffscreen(std::move(provider))); |
| output_surface->BindToClient(&output_surface_client); |
| |
| auto resource_provider = std::make_unique<DisplayResourceProviderGL>( |
| output_surface->context_provider()); |
| |
| RendererSettings settings; |
| FakeRendererGL renderer(&settings, &debug_settings_, output_surface.get(), |
| resource_provider.get()); |
| renderer.Initialize(); |
| renderer.SetVisible(true); |
| |
| gfx::Size viewport_size(1, 1); |
| cc::AddRenderPass(&render_passes_in_draw_order_, AggregatedRenderPassId{1}, |
| gfx::Rect(viewport_size), gfx::Transform(), |
| cc::FilterOperations()); |
| |
| EXPECT_CALL(*gl, DiscardFramebufferEXT(GL_FRAMEBUFFER, _, _)) |
| .With(Args<2, 1>(ElementsAre(GL_COLOR_ATTACHMENT0))) |
| .Times(1); |
| EXPECT_CALL(*gl, Clear(_)).Times(AnyNumber()); |
| DrawFrame(&renderer, viewport_size); |
| Mock::VerifyAndClearExpectations(gl); |
| } |
| |
| class TextureStateTrackingGLES2Interface : public TestGLES2Interface { |
| public: |
| TextureStateTrackingGLES2Interface() : active_texture_(GL_INVALID_ENUM) {} |
| |
| MOCK_METHOD1(WaitSyncTokenCHROMIUM, void(const GLbyte* sync_token)); |
| MOCK_METHOD3(TexParameteri, void(GLenum target, GLenum pname, GLint param)); |
| MOCK_METHOD4( |
| DrawElements, |
| void(GLenum mode, GLsizei count, GLenum type, const void* indices)); |
| |
| void ActiveTexture(GLenum texture) override { |
| EXPECT_NE(texture, active_texture_); |
| active_texture_ = texture; |
| } |
| |
| GLenum active_texture() const { return active_texture_; } |
| |
| private: |
| GLenum active_texture_; |
| }; |
| |
| #define EXPECT_FILTER_CALL(filter) \ |
| EXPECT_CALL(*gl, \ |
| TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filter)); \ |
| EXPECT_CALL(*gl, TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filter)); |
| |
| TEST_F(GLRendererTest, ActiveTextureState) { |
| auto child_gl_owned = std::make_unique<TextureStateTrackingGLES2Interface>(); |
| auto child_context_provider = |
| TestContextProvider::Create(std::move(child_gl_owned)); |
| child_context_provider->BindToCurrentThread(); |
| auto child_resource_provider = std::make_unique<ClientResourceProvider>(); |
| |
| auto gl_owned = std::make_unique<TextureStateTrackingGLES2Interface>(); |
| gl_owned->set_have_extension_egl_image(true); |
| auto* gl = gl_owned.get(); |
| |
| auto provider = TestContextProvider::Create(std::move(gl_owned)); |
| provider->BindToCurrentThread(); |
| |
| cc::FakeOutputSurfaceClient output_surface_client; |
| std::unique_ptr<OutputSurface> output_surface( |
| FakeOutputSurface::Create3d(std::move(provider))); |
| output_surface->BindToClient(&output_surface_client); |
| |
| auto resource_provider = std::make_unique<DisplayResourceProviderGL>( |
| output_surface->context_provider()); |
| |
| RendererSettings settings; |
| FakeRendererGL renderer(&settings, &debug_settings_, output_surface.get(), |
| resource_provider.get()); |
| renderer.Initialize(); |
| renderer.SetVisible(true); |
| |
| // During initialization we are allowed to set any texture parameters. |
| EXPECT_CALL(*gl, TexParameteri(_, _, _)).Times(AnyNumber()); |
| |
| AggregatedRenderPass* root_pass = cc::AddRenderPass( |
| &render_passes_in_draw_order_, AggregatedRenderPassId{1}, |
| gfx::Rect(100, 100), gfx::Transform(), cc::FilterOperations()); |
| gpu::SyncToken mailbox_sync_token; |
| cc::AddOneOfEveryQuadTypeInDisplayResourceProvider( |
| root_pass, resource_provider.get(), child_resource_provider.get(), |
| child_context_provider.get(), AggregatedRenderPassId{0}, |
| &mailbox_sync_token); |
| |
| EXPECT_EQ(12u, resource_provider->num_resources()); |
| renderer.DecideRenderPassAllocationsForFrame(render_passes_in_draw_order_); |
| |
| // Set up expected texture filter state transitions that match the quads |
| // created in AppendOneOfEveryQuadType(). |
| Mock::VerifyAndClearExpectations(gl); |
| { |
| InSequence sequence; |
| // The verified flush flag will be set by |
| // ClientResourceProvider::PrepareSendToParent. Before checking if |
| // the gpu::SyncToken matches, set this flag first. |
| mailbox_sync_token.SetVerifyFlush(); |
| // In AddOneOfEveryQuadTypeInDisplayResourceProvider, resources are added |
| // into RenderPass with the below order: resource6, resource1, resource8 |
| // (with mailbox), resource2, resource3, resource4, resource9, resource10, |
| // resource11, resource12. resource8 has its own mailbox mailbox_sync_token. |
| // The rest resources share a common default sync token. |
| EXPECT_CALL(*gl, WaitSyncTokenCHROMIUM(_)).Times(2); |
| EXPECT_CALL(*gl, |
| WaitSyncTokenCHROMIUM(MatchesSyncToken(mailbox_sync_token))) |
| .Times(1); |
| EXPECT_CALL(*gl, WaitSyncTokenCHROMIUM(_)).Times(7); |
| |
| // yuv_quad is drawn with the default linear filter. |
| for (int i = 0; i < 4; ++i) { |
| EXPECT_FILTER_CALL(GL_LINEAR); |
| } |
| EXPECT_CALL(*gl, DrawElements(_, _, _, _)); |
| |
| // tile_quad is drawn with GL_NEAREST because it is not transformed or |
| // scaled. |
| EXPECT_FILTER_CALL(GL_NEAREST); |
| EXPECT_CALL(*gl, DrawElements(_, _, _, _)); |
| |
| // transformed tile_quad |
| EXPECT_FILTER_CALL(GL_LINEAR); |
| EXPECT_CALL(*gl, DrawElements(_, _, _, _)); |
| |
| // scaled tile_quad |
| EXPECT_FILTER_CALL(GL_LINEAR); |
| EXPECT_CALL(*gl, DrawElements(_, _, _, _)); |
| |
| // texture_quad without nearest neighbor |
| EXPECT_FILTER_CALL(GL_LINEAR); |
| EXPECT_CALL(*gl, DrawElements(_, _, _, _)); |
| |
| // texture_quad without nearest neighbor |
| EXPECT_FILTER_CALL(GL_LINEAR); |
| EXPECT_CALL(*gl, DrawElements(_, _, _, _)); |
| |
| if (features::IsUsingFastPathForSolidColorQuad()) { |
| // stream video and debug draw quads |
| EXPECT_CALL(*gl, DrawElements(_, _, _, _)).Times(2); |
| } else { |
| // stream video, solid color, and debug draw quads |
| EXPECT_CALL(*gl, DrawElements(_, _, _, _)).Times(3); |
| } |
| } |
| |
| gfx::Size viewport_size(100, 100); |
| DrawFrame(&renderer, viewport_size); |
| Mock::VerifyAndClearExpectations(gl); |
| |
| child_resource_provider->ShutdownAndReleaseAllResources(); |
| } |
| |
| class BufferSubDataTrackingGLES2Interface : public TestGLES2Interface { |
| public: |
| BufferSubDataTrackingGLES2Interface() = default; |
| ~BufferSubDataTrackingGLES2Interface() override = default; |
| |
| void BufferSubData(GLenum target, |
| GLintptr offset, |
| GLsizeiptr size, |
| const void* data) override { |
| if (target != GL_ARRAY_BUFFER) |
| return; |
| DCHECK_EQ(0, offset); |
| last_array_data.resize(size); |
| memcpy(last_array_data.data(), data, size); |
| } |
| |
| std::vector<uint8_t> last_array_data; |
| }; |
| |
| TEST_F(GLRendererTest, DrawYUVVideoDrawQuadWithVisibleRect) { |
| gfx::Size viewport_size(100, 100); |
| |
| auto mock_gl_owned = std::make_unique<BufferSubDataTrackingGLES2Interface>(); |
| BufferSubDataTrackingGLES2Interface* mock_gl = mock_gl_owned.get(); |
| auto provider = TestContextProvider::Create(std::move(mock_gl_owned)); |
| provider->BindToCurrentThread(); |
| |
| cc::FakeOutputSurfaceClient output_surface_client; |
| std::unique_ptr<OutputSurface> output_surface( |
| FakeOutputSurface::Create3d(std::move(provider))); |
| output_surface->BindToClient(&output_surface_client); |
| |
| auto resource_provider = std::make_unique<DisplayResourceProviderGL>( |
| output_surface->context_provider()); |
| |
| RendererSettings settings; |
| FakeRendererGL renderer(&settings, &debug_settings_, output_surface.get(), |
| resource_provider.get()); |
| renderer.Initialize(); |
| renderer.SetVisible(true); |
| |
| AggregatedRenderPass* root_pass = cc::AddRenderPass( |
| &render_passes_in_draw_order_, AggregatedRenderPassId{1}, |
| gfx::Rect(viewport_size), gfx::Transform(), cc::FilterOperations()); |
| root_pass->has_transparent_background = false; |
| |
| gfx::Rect rect(viewport_size); |
| gfx::Rect visible_rect(rect); |
| gfx::RectF tex_coord_rect(0, 0, 1, 1); |
| visible_rect.Inset(10, 20, 30, 40); |
| |
| SharedQuadState* shared_state = root_pass->CreateAndAppendSharedQuadState(); |
| shared_state->SetAll(gfx::Transform(), gfx::Rect(), rect, |
| gfx::MaskFilterInfo(), absl::nullopt, false, 1, |
| SkBlendMode::kSrcOver, 0); |
| |
| YUVVideoDrawQuad* quad = |
| root_pass->CreateAndAppendDrawQuad<YUVVideoDrawQuad>(); |
| quad->SetNew(shared_state, rect, visible_rect, /*needs_blending=*/false, |
| tex_coord_rect, tex_coord_rect, rect.size(), rect.size(), |
| ResourceId(1), ResourceId(1), ResourceId(1), ResourceId(1), |
| gfx::ColorSpace(), 0, 1.0, 8); |
| |
| DrawFrame(&renderer, viewport_size); |
| |
| ASSERT_EQ(96u, mock_gl->last_array_data.size()); |
| float* geometry_binding_vertexes = |
| reinterpret_cast<float*>(mock_gl->last_array_data.data()); |
| |
| const double kEpsilon = 1e-6; |
| EXPECT_NEAR(-0.4f, geometry_binding_vertexes[0], kEpsilon); |
| EXPECT_NEAR(-0.3f, geometry_binding_vertexes[1], kEpsilon); |
| EXPECT_NEAR(0.1f, geometry_binding_vertexes[3], kEpsilon); |
| EXPECT_NEAR(0.2f, geometry_binding_vertexes[4], kEpsilon); |
| |
| EXPECT_NEAR(0.2f, geometry_binding_vertexes[12], kEpsilon); |
| EXPECT_NEAR(0.1f, geometry_binding_vertexes[13], kEpsilon); |
| EXPECT_NEAR(0.7f, geometry_binding_vertexes[15], kEpsilon); |
| EXPECT_NEAR(0.6f, geometry_binding_vertexes[16], kEpsilon); |
| } |
| |
| class NoClearRootRenderPassMockGLES2Interface : public TestGLES2Interface { |
| public: |
| MOCK_METHOD1(Clear, void(GLbitfield mask)); |
| MOCK_METHOD4( |
| DrawElements, |
| void(GLenum mode, GLsizei count, GLenum type, const void* indices)); |
| }; |
| |
| TEST_F(GLRendererTest, ShouldClearRootRenderPass) { |
| auto mock_gl_owned = |
| std::make_unique<NoClearRootRenderPassMockGLES2Interface>(); |
| NoClearRootRenderPassMockGLES2Interface* mock_gl = mock_gl_owned.get(); |
| |
| auto provider = TestContextProvider::Create(std::move(mock_gl_owned)); |
| provider->BindToCurrentThread(); |
| |
| cc::FakeOutputSurfaceClient output_surface_client; |
| std::unique_ptr<OutputSurface> output_surface( |
| FakeOutputSurface::Create3d(std::move(provider))); |
| output_surface->BindToClient(&output_surface_client); |
| |
| auto resource_provider = std::make_unique<DisplayResourceProviderGL>( |
| output_surface->context_provider()); |
| |
| RendererSettings settings; |
| settings.should_clear_root_render_pass = false; |
| |
| FakeRendererGL renderer(&settings, &debug_settings_, output_surface.get(), |
| resource_provider.get()); |
| renderer.Initialize(); |
| renderer.SetVisible(true); |
| |
| gfx::Size viewport_size(10, 10); |
| |
| AggregatedRenderPassId child_pass_id{2}; |
| AggregatedRenderPass* child_pass = cc::AddRenderPass( |
| &render_passes_in_draw_order_, child_pass_id, gfx::Rect(viewport_size), |
| gfx::Transform(), cc::FilterOperations()); |
| cc::AddQuad(child_pass, gfx::Rect(viewport_size), SK_ColorBLUE); |
| |
| AggregatedRenderPassId root_pass_id{1}; |
| AggregatedRenderPass* root_pass = cc::AddRenderPass( |
| &render_passes_in_draw_order_, root_pass_id, gfx::Rect(viewport_size), |
| gfx::Transform(), cc::FilterOperations()); |
| cc::AddQuad(root_pass, gfx::Rect(viewport_size), SK_ColorGREEN); |
| |
| cc::AddRenderPassQuad(root_pass, child_pass); |
| |
| #ifdef NDEBUG |
| GLint clear_bits = GL_COLOR_BUFFER_BIT; |
| #else |
| GLint clear_bits = GL_COLOR_BUFFER_BIT | GL_STENCIL_BUFFER_BIT; |
| #endif |
| |
| // First render pass is not the root one, clearing should happen. |
| EXPECT_CALL(*mock_gl, Clear(clear_bits)).Times(AtLeast(1)); |
| |
| Expectation first_render_pass = |
| EXPECT_CALL(*mock_gl, DrawElements(_, _, _, _)).Times(1); |
| |
| if (features::IsUsingFastPathForSolidColorQuad()) { |
| // The second render pass is the root one, clearing should be prevented. The |
| // one call is expected due to the solid color draw quad which uses glClear |
| // to draw the quad. |
| EXPECT_CALL(*mock_gl, Clear(clear_bits)).Times(1).After(first_render_pass); |
| } else { |
| // The second render pass is the root one, clearing should be prevented. |
| EXPECT_CALL(*mock_gl, Clear(clear_bits)).Times(0).After(first_render_pass); |
| } |
| |
| EXPECT_CALL(*mock_gl, DrawElements(_, _, _, _)) |
| .Times(AnyNumber()) |
| .After(first_render_pass); |
| |
| renderer.DecideRenderPassAllocationsForFrame(render_passes_in_draw_order_); |
| DrawFrame(&renderer, viewport_size); |
| |
| // In multiple render passes all but the root pass should clear the |
| // framebuffer. |
| Mock::VerifyAndClearExpectations(&mock_gl); |
| } |
| |
| class ScissorTestOnClearCheckingGLES2Interface : public TestGLES2Interface { |
| public: |
| ScissorTestOnClearCheckingGLES2Interface() = default; |
| |
| void ClearColor(GLfloat r, GLfloat g, GLfloat b, GLfloat a) override { |
| // RGBA - {0, 0, 0, 0} is used to clear the buffer before drawing onto the |
| // render target. Any other color means a solid color draw quad is being |
| // drawn. |
| if (features::IsUsingFastPathForSolidColorQuad()) |
| is_drawing_solid_color_quad_ = !(r == 0 && g == 0 && b == 0 && a == 0); |
| } |
| |
| void Clear(GLbitfield bits) override { |
| // GL clear is also used to draw solid color draw quads. |
| if ((bits & GL_COLOR_BUFFER_BIT) && is_drawing_solid_color_quad_) |
| return; |
| EXPECT_FALSE(scissor_enabled_); |
| } |
| |
| void Enable(GLenum cap) override { |
| if (cap == GL_SCISSOR_TEST) |
| scissor_enabled_ = true; |
| } |
| |
| void Disable(GLenum cap) override { |
| if (cap == GL_SCISSOR_TEST) |
| scissor_enabled_ = false; |
| } |
| |
| private: |
| bool scissor_enabled_ = false; |
| bool is_drawing_solid_color_quad_ = false; |
| }; |
| |
| TEST_F(GLRendererTest, ScissorTestWhenClearing) { |
| auto gl_owned = std::make_unique<ScissorTestOnClearCheckingGLES2Interface>(); |
| |
| auto provider = TestContextProvider::Create(std::move(gl_owned)); |
| provider->BindToCurrentThread(); |
| |
| cc::FakeOutputSurfaceClient output_surface_client; |
| std::unique_ptr<OutputSurface> output_surface( |
| FakeOutputSurface::Create3d(std::move(provider))); |
| output_surface->BindToClient(&output_surface_client); |
| |
| auto resource_provider = std::make_unique<DisplayResourceProviderGL>( |
| output_surface->context_provider()); |
| |
| RendererSettings settings; |
| FakeRendererGL renderer(&settings, &debug_settings_, output_surface.get(), |
| resource_provider.get()); |
| renderer.Initialize(); |
| EXPECT_FALSE(renderer.use_partial_swap()); |
| renderer.SetVisible(true); |
| |
| gfx::Size viewport_size(100, 100); |
| |
| gfx::Rect grand_child_rect(25, 25); |
| AggregatedRenderPassId grand_child_pass_id{3}; |
| AggregatedRenderPass* grand_child_pass = cc::AddRenderPass( |
| &render_passes_in_draw_order_, grand_child_pass_id, grand_child_rect, |
| gfx::Transform(), cc::FilterOperations()); |
| cc::AddClippedQuad(grand_child_pass, grand_child_rect, SK_ColorYELLOW); |
| |
| gfx::Rect child_rect(50, 50); |
| AggregatedRenderPassId child_pass_id{2}; |
| AggregatedRenderPass* child_pass = |
| cc::AddRenderPass(&render_passes_in_draw_order_, child_pass_id, |
| child_rect, gfx::Transform(), cc::FilterOperations()); |
| cc::AddQuad(child_pass, child_rect, SK_ColorBLUE); |
| |
| AggregatedRenderPassId root_pass_id{1}; |
| AggregatedRenderPass* root_pass = cc::AddRenderPass( |
| &render_passes_in_draw_order_, root_pass_id, gfx::Rect(viewport_size), |
| gfx::Transform(), cc::FilterOperations()); |
| cc::AddQuad(root_pass, gfx::Rect(viewport_size), SK_ColorGREEN); |
| |
| cc::AddRenderPassQuad(root_pass, child_pass); |
| cc::AddRenderPassQuad(child_pass, grand_child_pass); |
| |
| renderer.DecideRenderPassAllocationsForFrame(render_passes_in_draw_order_); |
| DrawFrame(&renderer, viewport_size); |
| } |
| |
| class DiscardCheckingGLES2Interface : public TestGLES2Interface { |
| public: |
| DiscardCheckingGLES2Interface() = default; |
| |
| void DiscardFramebufferEXT(GLenum target, |
| GLsizei numAttachments, |
| const GLenum* attachments) override { |
| ++discarded_; |
| } |
| |
| int discarded() const { return discarded_; } |
| void reset_discarded() { discarded_ = 0; } |
| |
| private: |
| int discarded_ = 0; |
| }; |
| |
| TEST_F(GLRendererTest, NoDiscardOnPartialUpdates) { |
| auto gl_owned = std::make_unique<DiscardCheckingGLES2Interface>(); |
| gl_owned->set_have_post_sub_buffer(true); |
| gl_owned->set_have_discard_framebuffer(true); |
| |
| auto* gl = gl_owned.get(); |
| |
| auto provider = TestContextProvider::Create(std::move(gl_owned)); |
| provider->BindToCurrentThread(); |
| |
| cc::FakeOutputSurfaceClient output_surface_client; |
| auto output_surface = FakeOutputSurface::Create3d(std::move(provider)); |
| output_surface->BindToClient(&output_surface_client); |
| |
| auto resource_provider = std::make_unique<DisplayResourceProviderGL>( |
| output_surface->context_provider()); |
| |
| RendererSettings settings; |
| settings.partial_swap_enabled = true; |
| FakeRendererGL renderer(&settings, &debug_settings_, output_surface.get(), |
| resource_provider.get()); |
| renderer.Initialize(); |
| EXPECT_TRUE(renderer.use_partial_swap()); |
| renderer.SetVisible(true); |
| |
| gfx::Size viewport_size(100, 100); |
| { |
| // Draw one black frame to make sure the output surface is reshaped before |
| // testes. |
| AggregatedRenderPassId root_pass_id{1}; |
| AggregatedRenderPass* root_pass = cc::AddRenderPass( |
| &render_passes_in_draw_order_, root_pass_id, gfx::Rect(viewport_size), |
| gfx::Transform(), cc::FilterOperations()); |
| cc::AddQuad(root_pass, gfx::Rect(viewport_size), SK_ColorBLACK); |
| root_pass->damage_rect = gfx::Rect(viewport_size); |
| |
| renderer.DecideRenderPassAllocationsForFrame(render_passes_in_draw_order_); |
| DrawFrame(&renderer, viewport_size); |
| gl->reset_discarded(); |
| } |
| { |
| // Partial frame, should not discard. |
| AggregatedRenderPassId root_pass_id{1}; |
| AggregatedRenderPass* root_pass = cc::AddRenderPass( |
| &render_passes_in_draw_order_, root_pass_id, gfx::Rect(viewport_size), |
| gfx::Transform(), cc::FilterOperations()); |
| cc::AddQuad(root_pass, gfx::Rect(viewport_size), SK_ColorGREEN); |
| root_pass->damage_rect = gfx::Rect(2, 2, 3, 3); |
| |
| renderer.DecideRenderPassAllocationsForFrame(render_passes_in_draw_order_); |
| DrawFrame(&renderer, viewport_size); |
| EXPECT_EQ(0, gl->discarded()); |
| gl->reset_discarded(); |
| } |
| { |
| // Full frame, should discard. |
| AggregatedRenderPassId root_pass_id{1}; |
| AggregatedRenderPass* root_pass = cc::AddRenderPass( |
| &render_passes_in_draw_order_, root_pass_id, gfx::Rect(viewport_size), |
| gfx::Transform(), cc::FilterOperations()); |
| cc::AddQuad(root_pass, gfx::Rect(viewport_size), SK_ColorGREEN); |
| root_pass->damage_rect = root_pass->output_rect; |
| |
| renderer.DecideRenderPassAllocationsForFrame(render_passes_in_draw_order_); |
| DrawFrame(&renderer, viewport_size); |
| EXPECT_EQ(1, gl->discarded()); |
| gl->reset_discarded(); |
| } |
| { |
| // Full frame, external scissor is set, should not discard. |
| output_surface->set_has_external_stencil_test(true); |
| AggregatedRenderPassId root_pass_id{1}; |
| AggregatedRenderPass* root_pass = cc::AddRenderPass( |
| &render_passes_in_draw_order_, root_pass_id, gfx::Rect(viewport_size), |
| gfx::Transform(), cc::FilterOperations()); |
| cc::AddQuad(root_pass, gfx::Rect(viewport_size), SK_ColorGREEN); |
| root_pass->damage_rect = root_pass->output_rect; |
| root_pass->has_transparent_background = false; |
| |
| renderer.DecideRenderPassAllocationsForFrame(render_passes_in_draw_order_); |
| DrawFrame(&renderer, viewport_size); |
| EXPECT_EQ(0, gl->discarded()); |
| gl->reset_discarded(); |
| output_surface->set_has_external_stencil_test(false); |
| } |
| } |
| |
| class ResourceTrackingGLES2Interface : public TestGLES2Interface { |
| public: |
| ResourceTrackingGLES2Interface() = default; |
| ~ResourceTrackingGLES2Interface() override { CheckNoResources(); } |
| |
| void CheckNoResources() { |
| EXPECT_TRUE(textures_.empty()); |
| EXPECT_TRUE(buffers_.empty()); |
| EXPECT_TRUE(framebuffers_.empty()); |
| EXPECT_TRUE(renderbuffers_.empty()); |
| EXPECT_TRUE(queries_.empty()); |
| EXPECT_TRUE(shaders_.empty()); |
| EXPECT_TRUE(programs_.empty()); |
| } |
| |
| void GenTextures(GLsizei n, GLuint* textures) override { |
| GenIds(&textures_, n, textures); |
| } |
| |
| void GenBuffers(GLsizei n, GLuint* buffers) override { |
| GenIds(&buffers_, n, buffers); |
| } |
| |
| void GenFramebuffers(GLsizei n, GLuint* framebuffers) override { |
| GenIds(&framebuffers_, n, framebuffers); |
| } |
| |
| void GenRenderbuffers(GLsizei n, GLuint* renderbuffers) override { |
| GenIds(&renderbuffers_, n, renderbuffers); |
| } |
| |
| void GenQueriesEXT(GLsizei n, GLuint* queries) override { |
| GenIds(&queries_, n, queries); |
| } |
| |
| GLuint CreateProgram() override { return GenId(&programs_); } |
| |
| GLuint CreateShader(GLenum type) override { return GenId(&shaders_); } |
| |
| void BindTexture(GLenum target, GLuint texture) override { |
| CheckId(&textures_, texture); |
| } |
| |
| void BindBuffer(GLenum target, GLuint buffer) override { |
| CheckId(&buffers_, buffer); |
| } |
| |
| void BindRenderbuffer(GLenum target, GLuint renderbuffer) override { |
| CheckId(&renderbuffers_, renderbuffer); |
| } |
| |
| void BindFramebuffer(GLenum target, GLuint framebuffer) override { |
| CheckId(&framebuffers_, framebuffer); |
| } |
| |
| void UseProgram(GLuint program) override { CheckId(&programs_, program); } |
| |
| void DeleteTextures(GLsizei n, const GLuint* textures) override { |
| DeleteIds(&textures_, n, textures); |
| } |
| |
| void DeleteBuffers(GLsizei n, const GLuint* buffers) override { |
| DeleteIds(&buffers_, n, buffers); |
| } |
| |
| void DeleteFramebuffers(GLsizei n, const GLuint* framebuffers) override { |
| DeleteIds(&framebuffers_, n, framebuffers); |
| } |
| |
| void DeleteRenderbuffers(GLsizei n, const GLuint* renderbuffers) override { |
| DeleteIds(&renderbuffers_, n, renderbuffers); |
| } |
| |
| void DeleteQueriesEXT(GLsizei n, const GLuint* queries) override { |
| DeleteIds(&queries_, n, queries); |
| } |
| |
| void DeleteProgram(GLuint program) override { DeleteId(&programs_, program); } |
| |
| void DeleteShader(GLuint shader) override { DeleteId(&shaders_, shader); } |
| |
| void BufferData(GLenum target, |
| GLsizeiptr size, |
| const void* data, |
| GLenum usage) override {} |
| |
| private: |
| GLuint GenId(std::set<GLuint>* resource_set) { |
| GLuint id = next_id_++; |
| resource_set->insert(id); |
| return id; |
| } |
| |
| void GenIds(std::set<GLuint>* resource_set, GLsizei n, GLuint* ids) { |
| for (GLsizei i = 0; i < n; ++i) |
| ids[i] = GenId(resource_set); |
| } |
| |
| void CheckId(std::set<GLuint>* resource_set, GLuint id) { |
| if (id == 0) |
| return; |
| EXPECT_TRUE(resource_set->find(id) != resource_set->end()); |
| } |
| |
| void DeleteId(std::set<GLuint>* resource_set, GLuint id) { |
| if (id == 0) |
| return; |
| size_t num_erased = resource_set->erase(id); |
| EXPECT_EQ(1u, num_erased); |
| } |
| |
| void DeleteIds(std::set<GLuint>* resource_set, GLsizei n, const GLuint* ids) { |
| for (GLsizei i = 0; i < n; ++i) |
| DeleteId(resource_set, ids[i]); |
| } |
| |
| GLuint next_id_ = 1; |
| std::set<GLuint> textures_; |
| std::set<GLuint> buffers_; |
| std::set<GLuint> framebuffers_; |
| std::set<GLuint> renderbuffers_; |
| std::set<GLuint> queries_; |
| std::set<GLuint> shaders_; |
| std::set<GLuint> programs_; |
| }; |
| |
| TEST_F(GLRendererTest, NoResourceLeak) { |
| auto gl_owned = std::make_unique<ResourceTrackingGLES2Interface>(); |
| auto* gl = gl_owned.get(); |
| |
| auto provider = TestContextProvider::Create(std::move(gl_owned)); |
| provider->BindToCurrentThread(); |
| |
| cc::FakeOutputSurfaceClient output_surface_client; |
| auto output_surface = FakeOutputSurface::Create3d(std::move(provider)); |
| output_surface->BindToClient(&output_surface_client); |
| |
| auto resource_provider = std::make_unique<DisplayResourceProviderGL>( |
| output_surface->context_provider()); |
| |
| { |
| RendererSettings settings; |
| FakeRendererGL renderer(&settings, &debug_settings_, output_surface.get(), |
| resource_provider.get()); |
| renderer.Initialize(); |
| renderer.SetVisible(true); |
| |
| gfx::Size viewport_size(100, 100); |
| |
| AggregatedRenderPassId root_pass_id{1}; |
| AggregatedRenderPass* root_pass = cc::AddRenderPass( |
| &render_passes_in_draw_order_, root_pass_id, gfx::Rect(viewport_size), |
| gfx::Transform(), cc::FilterOperations()); |
| cc::AddQuad(root_pass, gfx::Rect(viewport_size), SK_ColorGREEN); |
| root_pass->damage_rect = gfx::Rect(2, 2, 3, 3); |
| |
| renderer.DecideRenderPassAllocationsForFrame(render_passes_in_draw_order_); |
| DrawFrame(&renderer, viewport_size); |
| } |
| gl->CheckNoResources(); |
| } |
| |
| class DrawElementsGLES2Interface : public TestGLES2Interface { |
| public: |
| MOCK_METHOD4( |
| DrawElements, |
| void(GLenum mode, GLsizei count, GLenum type, const void* indices)); |
| }; |
| |
| class GLRendererSkipTest : public GLRendererTest { |
| protected: |
| GLRendererSkipTest() { |
| auto gl_owned = std::make_unique<StrictMock<DrawElementsGLES2Interface>>(); |
| gl_owned->set_have_post_sub_buffer(true); |
| gl_ = gl_owned.get(); |
| |
| auto provider = TestContextProvider::Create(std::move(gl_owned)); |
| provider->BindToCurrentThread(); |
| |
| output_surface_ = FakeOutputSurface::Create3d(std::move(provider)); |
| output_surface_->BindToClient(&output_surface_client_); |
| |
| resource_provider_ = std::make_unique<DisplayResourceProviderGL>( |
| output_surface_->context_provider()); |
| settings_.partial_swap_enabled = true; |
| renderer_ = std::make_unique<FakeRendererGL>(&settings_, &debug_settings_, |
| output_surface_.get(), |
| resource_provider_.get()); |
| renderer_->Initialize(); |
| renderer_->SetVisible(true); |
| } |
| |
| void DrawBlackFrame(const gfx::Size& viewport_size) { |
| // The feature enables a faster path to draw solid color quads that does not |
| // use GL draw calls but instead uses glClear. |
| if (!features::IsUsingFastPathForSolidColorQuad()) |
| EXPECT_CALL(*gl_, DrawElements(_, _, _, _)).Times(1); |
| |
| AggregatedRenderPassId root_pass_id{1}; |
| AggregatedRenderPass* root_pass = cc::AddRenderPass( |
| &render_passes_in_draw_order_, root_pass_id, gfx::Rect(viewport_size), |
| gfx::Transform(), cc::FilterOperations()); |
| root_pass->damage_rect = gfx::Rect(viewport_size); |
| cc::AddQuad(root_pass, gfx::Rect(viewport_size), SK_ColorBLACK); |
| renderer_->DecideRenderPassAllocationsForFrame( |
| render_passes_in_draw_order_); |
| DrawFrame(renderer_.get(), viewport_size); |
| Mock::VerifyAndClearExpectations(gl_); |
| } |
| |
| StrictMock<DrawElementsGLES2Interface>* gl_; |
| RendererSettings settings_; |
| cc::FakeOutputSurfaceClient output_surface_client_; |
| std::unique_ptr<FakeOutputSurface> output_surface_; |
| std::unique_ptr<DisplayResourceProviderGL> resource_provider_; |
| std::unique_ptr<FakeRendererGL> renderer_; |
| }; |
| |
| TEST_F(GLRendererSkipTest, DrawQuad) { |
| gfx::Size viewport_size(100, 100); |
| gfx::Rect quad_rect = gfx::Rect(20, 20, 20, 20); |
| |
| // Draw the a black frame to make sure output surface is reshaped before |
| // tests. |
| DrawBlackFrame(viewport_size); |
| |
| EXPECT_CALL(*gl_, DrawElements(_, _, _, _)).Times(1); |
| |
| AggregatedRenderPassId root_pass_id{1}; |
| AggregatedRenderPass* root_pass = cc::AddRenderPass( |
| &render_passes_in_draw_order_, root_pass_id, gfx::Rect(viewport_size), |
| gfx::Transform(), cc::FilterOperations()); |
| root_pass->damage_rect = gfx::Rect(0, 0, 25, 25); |
| cc::AddQuad(root_pass, quad_rect, SK_ColorGREEN); |
| |
| // Add rounded corners to the solid color draw quad so that the fast path |
| // of drawing using glClear is not used. |
| root_pass->shared_quad_state_list.front()->mask_filter_info = |
| gfx::MaskFilterInfo(gfx::RRectF(gfx::RectF(quad_rect), 2.f)); |
| |
| renderer_->DecideRenderPassAllocationsForFrame(render_passes_in_draw_order_); |
| DrawFrame(renderer_.get(), viewport_size); |
| } |
| |
| TEST_F(GLRendererSkipTest, SkipVisibleRect) { |
| gfx::Size viewport_size(100, 100); |
| gfx::Rect quad_rect = gfx::Rect(0, 0, 40, 40); |
| |
| // Draw the a black frame to make sure output surface is reshaped before |
| // tests. |
| DrawBlackFrame(viewport_size); |
| |
| AggregatedRenderPassId root_pass_id{1}; |
| AggregatedRenderPass* root_pass = cc::AddRenderPass( |
| &render_passes_in_draw_order_, root_pass_id, gfx::Rect(viewport_size), |
| gfx::Transform(), cc::FilterOperations()); |
| root_pass->damage_rect = gfx::Rect(0, 0, 10, 10); |
| cc::AddQuad(root_pass, quad_rect, SK_ColorGREEN); |
| root_pass->shared_quad_state_list.front()->clip_rect = |
| gfx::Rect(0, 0, 40, 40); |
| root_pass->quad_list.front()->visible_rect = gfx::Rect(20, 20, 20, 20); |
| |
| // Add rounded corners to the solid color draw quad so that the fast path |
| // of drawing using glClear is not used. |
| root_pass->shared_quad_state_list.front()->mask_filter_info = |
| gfx::MaskFilterInfo(gfx::RRectF(gfx::RectF(quad_rect), 1.f)); |
| |
| renderer_->DecideRenderPassAllocationsForFrame(render_passes_in_draw_order_); |
| DrawFrame(renderer_.get(), viewport_size); |
| // DrawElements should not be called because the visible rect is outside the |
| // scissor, even though the clip rect and quad rect intersect the scissor. |
| } |
| |
| TEST_F(GLRendererSkipTest, SkipClippedQuads) { |
| gfx::Size viewport_size(100, 100); |
| gfx::Rect quad_rect = gfx::Rect(25, 25, 90, 90); |
| |
| // Draw the a black frame to make sure output surface is reshaped before |
| // tests. |
| DrawBlackFrame(viewport_size); |
| |
| AggregatedRenderPassId root_pass_id{1}; |
| |
| auto* root_pass = cc::AddRenderPass(&render_passes_in_draw_order_, |
| root_pass_id, gfx::Rect(viewport_size), |
| gfx::Transform(), cc::FilterOperations()); |
| root_pass->damage_rect = gfx::Rect(0, 0, 25, 25); |
| cc::AddClippedQuad(root_pass, quad_rect, SK_ColorGREEN); |
| root_pass->quad_list.front()->rect = gfx::Rect(20, 20, 20, 20); |
| |
| renderer_->DecideRenderPassAllocationsForFrame(render_passes_in_draw_order_); |
| DrawFrame(renderer_.get(), viewport_size); |
| // DrawElements should not be called because the clip rect is outside the |
| // scissor. |
| } |
| |
| TEST_F(GLRendererTest, DrawFramePreservesFramebuffer) { |
| // When using render-to-FBO to display the surface, all rendering is done |
| // to a non-zero FBO. Make sure that the framebuffer is always restored to |
| // the correct framebuffer during rendering, if changed. |
| // Note: there is one path that will set it to 0, but that is after the render |
| // has finished. |
| cc::FakeOutputSurfaceClient output_surface_client; |
| std::unique_ptr<FakeOutputSurface> output_surface( |
| FakeOutputSurface::Create3d()); |
| output_surface->BindToClient(&output_surface_client); |
| |
| auto resource_provider = std::make_unique<DisplayResourceProviderGL>( |
| output_surface->context_provider()); |
| |
| RendererSettings settings; |
| FakeRendererGL renderer(&settings, &debug_settings_, output_surface.get(), |
| resource_provider.get()); |
| renderer.Initialize(); |
| EXPECT_FALSE(renderer.use_partial_swap()); |
| renderer.SetVisible(true); |
| |
| gfx::Size viewport_size(100, 100); |
| gfx::Rect quad_rect = gfx::Rect(20, 20, 20, 20); |
| |
| AggregatedRenderPassId root_pass_id{1}; |
| AggregatedRenderPass* root_pass = cc::AddRenderPass( |
| &render_passes_in_draw_order_, root_pass_id, gfx::Rect(viewport_size), |
| gfx::Transform(), cc::FilterOperations()); |
| cc::AddClippedQuad(root_pass, quad_rect, SK_ColorGREEN); |
| |
| unsigned fbo; |
| gpu::gles2::GLES2Interface* gl = |
| output_surface->context_provider()->ContextGL(); |
| gl->GenFramebuffers(1, &fbo); |
| output_surface->set_framebuffer(fbo, GL_RGB); |
| |
| renderer.DecideRenderPassAllocationsForFrame(render_passes_in_draw_order_); |
| DrawFrame(&renderer, viewport_size); |
| |
| int bound_fbo; |
| gl->GetIntegerv(GL_FRAMEBUFFER_BINDING, &bound_fbo); |
| EXPECT_EQ(static_cast<int>(fbo), bound_fbo); |
| } |
| |
| TEST_F(GLRendererShaderTest, DrawRenderPassQuadShaderPermutations) { |
| gfx::Size viewport_size(60, 75); |
| |
| gfx::Rect child_rect(50, 50); |
| AggregatedRenderPassId child_pass_id{2}; |
| AggregatedRenderPass* child_pass; |
| |
| AggregatedRenderPassId root_pass_id{1}; |
| AggregatedRenderPass* root_pass; |
| |
| auto transfer_resource = TransferableResource::MakeGL( |
| gpu::Mailbox::Generate(), GL_LINEAR, GL_TEXTURE_2D, gpu::SyncToken(), |
| child_rect.size(), false /* is_overlay_candidate */); |
| ResourceId mask = child_resource_provider_->ImportResource(transfer_resource, |
| base::DoNothing()); |
| |
| // Return the mapped resource id. |
| std::unordered_map<ResourceId, ResourceId, ResourceIdHasher> resource_map = |
| cc::SendResourceAndGetChildToParentMap({mask}, resource_provider_.get(), |
| child_resource_provider_.get(), |
| child_context_provider_.get()); |
| ResourceId mapped_mask = resource_map[mask]; |
| |
| float matrix[20]; |
| float amount = 0.5f; |
| matrix[0] = 0.213f + 0.787f * amount; |
| matrix[1] = 0.715f - 0.715f * amount; |
| matrix[2] = 1.f - (matrix[0] + matrix[1]); |
| matrix[3] = matrix[4] = 0; |
| matrix[5] = 0.213f - 0.213f * amount; |
| matrix[6] = 0.715f + 0.285f * amount; |
| matrix[7] = 1.f - (matrix[5] + matrix[6]); |
| matrix[8] = matrix[9] = 0; |
| matrix[10] = 0.213f - 0.213f * amount; |
| matrix[11] = 0.715f - 0.715f * amount; |
| matrix[12] = 1.f - (matrix[10] + matrix[11]); |
| matrix[13] = matrix[14] = 0; |
| matrix[15] = matrix[16] = matrix[17] = matrix[19] = 0; |
| matrix[18] = 1; |
| cc::FilterOperations filters; |
| filters.Append(cc::FilterOperation::CreateReferenceFilter( |
| sk_make_sp<cc::ColorFilterPaintFilter>(SkColorFilters::Matrix(matrix), |
| nullptr))); |
| |
| gfx::Transform transform_causing_aa; |
| transform_causing_aa.Rotate(20.0); |
| |
| for (int i = 0; i <= LAST_BLEND_MODE; ++i) { |
| BlendMode blend_mode = static_cast<BlendMode>(i); |
| SkBlendMode xfer_mode = BlendModeToSkXfermode(blend_mode); |
| settings_.force_blending_with_shaders = (blend_mode != BLEND_MODE_NONE); |
| // RenderPassProgram |
| render_passes_in_draw_order_.clear(); |
| child_pass = |
| cc::AddRenderPass(&render_passes_in_draw_order_, child_pass_id, |
| child_rect, gfx::Transform(), cc::FilterOperations()); |
| |
| root_pass = cc::AddRenderPass(&render_passes_in_draw_order_, root_pass_id, |
| gfx::Rect(viewport_size), gfx::Transform(), |
| cc::FilterOperations()); |
| |
| cc::AddRenderPassQuad(root_pass, child_pass, kInvalidResourceId, |
| gfx::Transform(), xfer_mode); |
| |
| renderer_->DecideRenderPassAllocationsForFrame( |
| render_passes_in_draw_order_); |
| DrawFrame(renderer_.get(), viewport_size); |
| TestRenderPassProgram(TEX_COORD_PRECISION_MEDIUM, blend_mode); |
| |
| // RenderPassColorMatrixProgram |
| render_passes_in_draw_order_.clear(); |
| |
| child_pass = cc::AddRenderPass(&render_passes_in_draw_order_, child_pass_id, |
| child_rect, transform_causing_aa, filters); |
| |
| root_pass = cc::AddRenderPass(&render_passes_in_draw_order_, root_pass_id, |
| gfx::Rect(viewport_size), gfx::Transform(), |
| cc::FilterOperations()); |
| |
| cc::AddRenderPassQuad(root_pass, child_pass, kInvalidResourceId, |
| gfx::Transform(), xfer_mode); |
| |
| renderer_->DecideRenderPassAllocationsForFrame( |
| render_passes_in_draw_order_); |
| DrawFrame(renderer_.get(), viewport_size); |
| TestRenderPassColorMatrixProgram(TEX_COORD_PRECISION_MEDIUM, blend_mode); |
| |
| // RenderPassMaskProgram |
| render_passes_in_draw_order_.clear(); |
| |
| child_pass = |
| cc::AddRenderPass(&render_passes_in_draw_order_, child_pass_id, |
| child_rect, gfx::Transform(), cc::FilterOperations()); |
| |
| root_pass = cc::AddRenderPass(&render_passes_in_draw_order_, root_pass_id, |
| gfx::Rect(viewport_size), gfx::Transform(), |
| cc::FilterOperations()); |
| |
| cc::AddRenderPassQuad(root_pass, child_pass, mapped_mask, gfx::Transform(), |
| xfer_mode); |
| |
| renderer_->DecideRenderPassAllocationsForFrame( |
| render_passes_in_draw_order_); |
| DrawFrame(renderer_.get(), viewport_size); |
| TestRenderPassMaskProgram(TEX_COORD_PRECISION_MEDIUM, SAMPLER_TYPE_2D, |
| blend_mode); |
| |
| // RenderPassMaskColorMatrixProgram |
| render_passes_in_draw_order_.clear(); |
| |
| child_pass = cc::AddRenderPass(&render_passes_in_draw_order_, child_pass_id, |
| child_rect, gfx::Transform(), filters); |
| |
| root_pass = cc::AddRenderPass(&render_passes_in_draw_order_, root_pass_id, |
| gfx::Rect(viewport_size), gfx::Transform(), |
| cc::FilterOperations()); |
| |
| cc::AddRenderPassQuad(root_pass, child_pass, mapped_mask, gfx::Transform(), |
| xfer_mode); |
| |
| renderer_->DecideRenderPassAllocationsForFrame( |
| render_passes_in_draw_order_); |
| DrawFrame(renderer_.get(), viewport_size); |
| TestRenderPassMaskColorMatrixProgram(TEX_COORD_PRECISION_MEDIUM, |
| SAMPLER_TYPE_2D, blend_mode); |
| |
| // RenderPassProgramAA |
| render_passes_in_draw_order_.clear(); |
| |
| child_pass = cc::AddRenderPass(&render_passes_in_draw_order_, child_pass_id, |
| child_rect, transform_causing_aa, |
| cc::FilterOperations()); |
| |
| root_pass = cc::AddRenderPass(&render_passes_in_draw_order_, root_pass_id, |
| gfx::Rect(viewport_size), gfx::Transform(), |
| cc::FilterOperations()); |
| |
| cc::AddRenderPassQuad(root_pass, child_pass, kInvalidResourceId, |
| transform_causing_aa, xfer_mode); |
| |
| renderer_->DecideRenderPassAllocationsForFrame( |
| render_passes_in_draw_order_); |
| DrawFrame(renderer_.get(), viewport_size); |
| TestRenderPassProgramAA(TEX_COORD_PRECISION_MEDIUM, blend_mode); |
| |
| // RenderPassColorMatrixProgramAA |
| render_passes_in_draw_order_.clear(); |
| |
| child_pass = cc::AddRenderPass(&render_passes_in_draw_order_, child_pass_id, |
| child_rect, transform_causing_aa, filters); |
| |
| root_pass = cc::AddRenderPass(&render_passes_in_draw_order_, root_pass_id, |
| gfx::Rect(viewport_size), gfx::Transform(), |
| cc::FilterOperations()); |
| |
| cc::AddRenderPassQuad(root_pass, child_pass, kInvalidResourceId, |
| transform_causing_aa, xfer_mode); |
| |
| renderer_->DecideRenderPassAllocationsForFrame( |
| render_passes_in_draw_order_); |
| DrawFrame(renderer_.get(), viewport_size); |
| TestRenderPassColorMatrixProgramAA(TEX_COORD_PRECISION_MEDIUM, blend_mode); |
| |
| // RenderPassMaskProgramAA |
| render_passes_in_draw_order_.clear(); |
| |
| child_pass = cc::AddRenderPass(&render_passes_in_draw_order_, child_pass_id, |
| child_rect, transform_causing_aa, |
| cc::FilterOperations()); |
| |
| root_pass = cc::AddRenderPass(&render_passes_in_draw_order_, root_pass_id, |
| gfx::Rect(viewport_size), gfx::Transform(), |
| cc::FilterOperations()); |
| |
| cc::AddRenderPassQuad(root_pass, child_pass, mapped_mask, |
| transform_causing_aa, xfer_mode); |
| |
| renderer_->DecideRenderPassAllocationsForFrame( |
| render_passes_in_draw_order_); |
| DrawFrame(renderer_.get(), viewport_size); |
| TestRenderPassMaskProgramAA(TEX_COORD_PRECISION_MEDIUM, SAMPLER_TYPE_2D, |
| blend_mode); |
| |
| // RenderPassMaskColorMatrixProgramAA |
| render_passes_in_draw_order_.clear(); |
| |
| child_pass = cc::AddRenderPass(&render_passes_in_draw_order_, child_pass_id, |
| child_rect, transform_causing_aa, filters); |
| |
| root_pass = cc::AddRenderPass(&render_passes_in_draw_order_, root_pass_id, |
| gfx::Rect(viewport_size), |
| transform_causing_aa, cc::FilterOperations()); |
| |
| cc::AddRenderPassQuad(root_pass, child_pass, mapped_mask, |
| transform_causing_aa, xfer_mode); |
| |
| renderer_->DecideRenderPassAllocationsForFrame( |
| render_passes_in_draw_order_); |
| DrawFrame(renderer_.get(), viewport_size); |
| TestRenderPassMaskColorMatrixProgramAA(TEX_COORD_PRECISION_MEDIUM, |
| SAMPLER_TYPE_2D, blend_mode); |
| } |
| } |
| |
| // At this time, the AA code path cannot be taken if the surface's rect would |
| // project incorrectly by the given transform, because of w<0 clipping. |
| TEST_F(GLRendererShaderTest, DrawRenderPassQuadSkipsAAForClippingTransform) { |
| gfx::Rect child_rect(50, 50); |
| AggregatedRenderPassId child_pass_id{2}; |
| AggregatedRenderPass* child_pass; |
| |
| gfx::Size viewport_size(100, 100); |
| AggregatedRenderPassId root_pass_id{1}; |
| AggregatedRenderPass* root_pass; |
| |
| gfx::Transform transform_preventing_aa; |
| transform_preventing_aa.ApplyPerspectiveDepth(40.0); |
| transform_preventing_aa.RotateAboutYAxis(-20.0); |
| transform_preventing_aa.Scale(30.0, 1.0); |
| |
| // Verify that the test transform and test rect actually do cause the clipped |
| // flag to trigger. Otherwise we are not testing the intended scenario. |
| bool clipped = false; |
| cc::MathUtil::MapQuad(transform_preventing_aa, |
| gfx::QuadF(gfx::RectF(child_rect)), &clipped); |
| ASSERT_TRUE(clipped); |
| |
| child_pass = cc::AddRenderPass(&render_passes_in_draw_order_, child_pass_id, |
| child_rect, transform_preventing_aa, |
| cc::FilterOperations()); |
| |
| root_pass = cc::AddRenderPass(&render_passes_in_draw_order_, root_pass_id, |
| gfx::Rect(viewport_size), gfx::Transform(), |
| cc::FilterOperations()); |
| |
| cc::AddRenderPassQuad(root_pass, child_pass, kInvalidResourceId, |
| transform_preventing_aa, SkBlendMode::kSrcOver); |
| |
| renderer_->DecideRenderPassAllocationsForFrame(render_passes_in_draw_order_); |
| DrawFrame(renderer_.get(), viewport_size); |
| |
| // If use_aa incorrectly ignores clipping, it will use the |
| // RenderPassProgramAA shader instead of the RenderPassProgram. |
| TestRenderPassProgram(TEX_COORD_PRECISION_MEDIUM, BLEND_MODE_NONE); |
| } |
| |
| TEST_F(GLRendererShaderTest, DrawSolidColorShader) { |
| gfx::Size viewport_size(30, 30); // Don't translate out of the viewport. |
| gfx::Size quad_size(3, 3); |
| AggregatedRenderPassId root_pass_id{1}; |
| AggregatedRenderPass* root_pass; |
| |
| gfx::Transform pixel_aligned_transform_causing_aa; |
| pixel_aligned_transform_causing_aa.Translate(25.5f, 25.5f); |
| pixel_aligned_transform_causing_aa.Scale(0.5f, 0.5f); |
| |
| root_pass = cc::AddRenderPass(&render_passes_in_draw_order_, root_pass_id, |
| gfx::Rect(viewport_size), gfx::Transform(), |
| cc::FilterOperations()); |
| cc::AddTransformedQuad(root_pass, gfx::Rect(quad_size), SK_ColorYELLOW, |
| pixel_aligned_transform_causing_aa); |
| |
| renderer_->DecideRenderPassAllocationsForFrame(render_passes_in_draw_order_); |
| DrawFrame(renderer_.get(), viewport_size); |
| |
| TestSolidColorProgramAA(); |
| } |
| |
| class OutputSurfaceMockGLES2Interface : public TestGLES2Interface { |
| public: |
| OutputSurfaceMockGLES2Interface() = default; |
| |
| // Specifically override methods even if they are unused (used in conjunction |
| // with StrictMock). We need to make sure that GLRenderer does not issue |
| // framebuffer-related GLuint calls directly. Instead these are supposed to go |
| // through the OutputSurface abstraction. |
| MOCK_METHOD2(BindFramebuffer, void(GLenum target, GLuint framebuffer)); |
| MOCK_METHOD5(ResizeCHROMIUM, |
| void(GLuint width, |
| GLuint height, |
| float device_scale, |
| GLcolorSpace color_space, |
| GLboolean has_alpha)); |
| MOCK_METHOD4( |
| DrawElements, |
| void(GLenum mode, GLsizei count, GLenum type, const void* indices)); |
| }; |
| |
| class MockOutputSurface : public OutputSurface { |
| public: |
| explicit MockOutputSurface(scoped_refptr<ContextProvider> provider) |
| : OutputSurface(std::move(provider)) {} |
| ~MockOutputSurface() override {} |
| |
| void BindToClient(OutputSurfaceClient*) override {} |
| unsigned UpdateGpuFence() override { return 0; } |
| |
| MOCK_METHOD0(EnsureBackbuffer, void()); |
| MOCK_METHOD0(DiscardBackbuffer, void()); |
| MOCK_METHOD5(Reshape, |
| void(const gfx::Size& size, |
| float scale_factor, |
| const gfx::ColorSpace& color_space, |
| gfx::BufferFormat format, |
| bool use_stencil)); |
| MOCK_METHOD0(BindFramebuffer, void()); |
| MOCK_METHOD1(SetDrawRectangle, void(const gfx::Rect&)); |
| MOCK_METHOD1(SetEnableDCLayers, void(bool)); |
| MOCK_METHOD0(GetFramebufferCopyTextureFormat, GLenum()); |
| MOCK_METHOD1(SwapBuffers_, void(OutputSurfaceFrame& frame)); // NOLINT |
| void SwapBuffers(OutputSurfaceFrame frame) override { SwapBuffers_(frame); } |
| MOCK_CONST_METHOD0(IsDisplayedAsOverlayPlane, bool()); |
| MOCK_CONST_METHOD0(GetOverlayTextureId, unsigned()); |
| MOCK_CONST_METHOD0(HasExternalStencilTest, bool()); |
| MOCK_METHOD0(ApplyExternalStencil, void()); |
| MOCK_METHOD1(SetUpdateVSyncParametersCallback, |
| void(UpdateVSyncParametersCallback)); |
| MOCK_METHOD1(SetDisplayTransformHint, void(gfx::OverlayTransform)); |
| |
| gfx::OverlayTransform GetDisplayTransform() override { |
| return gfx::OVERLAY_TRANSFORM_NONE; |
| } |
| }; |
| |
| class MockOutputSurfaceTest : public GLRendererTest { |
| protected: |
| void SetUp() override { |
| auto gl = std::make_unique<StrictMock<OutputSurfaceMockGLES2Interface>>(); |
| gl->set_have_post_sub_buffer(true); |
| gl_ = gl.get(); |
| auto provider = TestContextProvider::Create(std::move(gl)); |
| provider->BindToCurrentThread(); |
| output_surface_ = |
| std::make_unique<StrictMock<MockOutputSurface>>(std::move(provider)); |
| |
| cc::FakeOutputSurfaceClient output_surface_client_; |
| output_surface_->BindToClient(&output_surface_client_); |
| |
| resource_provider_ = std::make_unique<DisplayResourceProviderGL>( |
| output_surface_->context_provider()); |
| |
| renderer_ = std::make_unique<FakeRendererGL>(&settings_, &debug_settings_, |
| output_surface_.get(), |
| resource_provider_.get()); |
| renderer_->Initialize(); |
| |
| EXPECT_CALL(*output_surface_, EnsureBackbuffer()).Times(1); |
| renderer_->SetVisible(true); |
| Mock::VerifyAndClearExpectations(output_surface_.get()); |
| } |
| |
| void SwapBuffers() { |
| renderer_->SwapBuffers(DirectRenderer::SwapFrameData()); |
| } |
| |
| void DrawFrame(float device_scale_factor, |
| const gfx::Size& viewport_size, |
| bool transparent) { |
| gfx::BufferFormat format = transparent ? gfx::BufferFormat::RGBA_8888 |
| : gfx::BufferFormat::RGBX_8888; |
| AggregatedRenderPassId render_pass_id{1}; |
| AggregatedRenderPass* render_pass = cc::AddRenderPass( |
| &render_passes_in_draw_order_, render_pass_id, gfx::Rect(viewport_size), |
| gfx::Transform(), cc::FilterOperations()); |
| cc::AddQuad(render_pass, gfx::Rect(viewport_size), SK_ColorGREEN); |
| render_pass->has_transparent_background = transparent; |
| |
| EXPECT_CALL(*output_surface_, EnsureBackbuffer()).WillRepeatedly(Return()); |
| |
| EXPECT_CALL(*output_surface_, |
| Reshape(viewport_size, device_scale_factor, _, format, _)) |
| .Times(1); |
| |
| EXPECT_CALL(*output_surface_, BindFramebuffer()).Times(1); |
| |
| EXPECT_CALL(*gl_, DrawElements(_, _, _, _)).Times(1); |
| |
| renderer_->DecideRenderPassAllocationsForFrame( |
| render_passes_in_draw_order_); |
| SurfaceDamageRectList surface_damage_rect_list; |
| renderer_->DrawFrame(&render_passes_in_draw_order_, device_scale_factor, |
| viewport_size, gfx::DisplayColorSpaces(), |
| std::move(surface_damage_rect_list)); |
| } |
| |
| RendererSettings settings_; |
| cc::FakeOutputSurfaceClient output_surface_client_; |
| OutputSurfaceMockGLES2Interface* gl_ = nullptr; |
| std::unique_ptr<StrictMock<MockOutputSurface>> output_surface_; |
| std::unique_ptr<DisplayResourceProviderGL> resource_provider_; |
| std::unique_ptr<FakeRendererGL> renderer_; |
| }; |
| |
| TEST_F(MockOutputSurfaceTest, BackbufferDiscard) { |
| // Drop backbuffer on hide. |
| EXPECT_CALL(*output_surface_, DiscardBackbuffer()).Times(1); |
| renderer_->SetVisible(false); |
| Mock::VerifyAndClearExpectations(output_surface_.get()); |
| |
| // Restore backbuffer on show. |
| EXPECT_CALL(*output_surface_, EnsureBackbuffer()).Times(1); |
| renderer_->SetVisible(true); |
| Mock::VerifyAndClearExpectations(output_surface_.get()); |
| } |
| |
| #if defined(OS_WIN) |
| class MockDCLayerOverlayProcessor : public DCLayerOverlayProcessor { |
| public: |
| MockDCLayerOverlayProcessor() |
| : DCLayerOverlayProcessor(&debug_settings_, |
| /*allowed_yuv_overlay_count=*/1, |
| true) {} |
| ~MockDCLayerOverlayProcessor() override = default; |
| MOCK_METHOD8(Process, |
| void(DisplayResourceProvider* resource_provider, |
| const gfx::RectF& display_rect, |
| const FilterOperationsMap& render_pass_filters, |
| const FilterOperationsMap& render_pass_backdrop_filters, |
| AggregatedRenderPassList* render_passes, |
| gfx::Rect* damage_rect, |
| SurfaceDamageRectList surface_damage_rect_list, |
| DCLayerOverlayList* dc_layer_overlays)); |
| |
| protected: |
| DebugRendererSettings debug_settings_; |
| }; |
| class TestOverlayProcessor : public OverlayProcessorWin { |
| public: |
| explicit TestOverlayProcessor(OutputSurface* output_surface) |
| : OverlayProcessorWin(output_surface, |
| std::make_unique<MockDCLayerOverlayProcessor>()) {} |
| ~TestOverlayProcessor() override = default; |
| |
| MockDCLayerOverlayProcessor* GetTestProcessor() { |
| return static_cast<MockDCLayerOverlayProcessor*>(GetOverlayProcessor()); |
| } |
| }; |
| #elif defined(OS_APPLE) |
| class MockCALayerOverlayProcessor : public CALayerOverlayProcessor { |
| public: |
| MockCALayerOverlayProcessor() = default; |
| ~MockCALayerOverlayProcessor() override = default; |
| |
| MOCK_CONST_METHOD6( |
| ProcessForCALayerOverlays, |
| bool(DisplayResourceProvider* resource_provider, |
| const gfx::RectF& display_rect, |
| const QuadList& quad_list, |
| const base::flat_map<AggregatedRenderPassId, cc::FilterOperations*>& |
| render_pass_filters, |
| const base::flat_map<AggregatedRenderPassId, cc::FilterOperations*>& |
| render_pass_backdrop_filters, |
| CALayerOverlayList* ca_layer_overlays)); |
| }; |
| |
| class TestOverlayProcessor : public OverlayProcessorMac { |
| public: |
| explicit TestOverlayProcessor(OutputSurface* output_surface) |
| : OverlayProcessorMac(std::make_unique<MockCALayerOverlayProcessor>()) {} |
| ~TestOverlayProcessor() override = default; |
| |
| const MockCALayerOverlayProcessor* GetTestProcessor() const { |
| return static_cast<const MockCALayerOverlayProcessor*>( |
| GetOverlayProcessor()); |
| } |
| }; |
| |
| #elif defined(OS_ANDROID) || defined(USE_OZONE) |
| |
| class TestOverlayProcessor : public OverlayProcessorUsingStrategy { |
| public: |
| class Strategy : public OverlayProcessorUsingStrategy::Strategy { |
| public: |
| Strategy() = default; |
| ~Strategy() override = default; |
| |
| MOCK_METHOD8( |
| Attempt, |
| bool(const skia::Matrix44& output_color_matrix, |
| const OverlayProcessorInterface::FilterOperationsMap& |
| render_pass_backdrop_filters, |
| DisplayResourceProvider* resource_provider, |
| AggregatedRenderPassList* render_pass_list, |
| SurfaceDamageRectList* surface_damage_rect_list, |
| const OverlayProcessorInterface::OutputSurfaceOverlayPlane* |
| primary_surface, |
| OverlayCandidateList* candidates, |
| std::vector<gfx::Rect>* content_bounds)); |
| |
| void ProposePrioritized( |
| const skia::Matrix44& output_color_matrix, |
| const FilterOperationsMap& render_pass_backdrop_filters, |
| DisplayResourceProvider* resource_provider, |
| AggregatedRenderPassList* render_pass_list, |
| SurfaceDamageRectList* surface_damage_rect_list, |
| const PrimaryPlane* primary_plane, |
| OverlayProposedCandidateList* candidates, |
| std::vector<gfx::Rect>* content_bounds) override { |
| auto* render_pass = render_pass_list->back().get(); |
| QuadList& quad_list = render_pass->quad_list; |
| OverlayCandidate candidate; |
| candidates->push_back({quad_list.end(), candidate, this}); |
| } |
| |
| MOCK_METHOD9(AttemptPrioritized, |
| bool(const skia::Matrix44& output_color_matrix, |
| const FilterOperationsMap& render_pass_backdrop_filters, |
| DisplayResourceProvider* resource_provider, |
| AggregatedRenderPassList* render_pass_list, |
| SurfaceDamageRectList* surface_damage_rect_list, |
| const PrimaryPlane* primary_plane, |
| OverlayCandidateList* candidates, |
| std::vector<gfx::Rect>* content_bounds, |
| OverlayProposedCandidate* proposed_candidate)); |
| }; |
| |
| bool IsOverlaySupported() const override { return true; } |
| |
| // A list of possible overlay candidates is presented to this function. |
| // The expected result is that those candidates that can be in a separate |
| // plane are marked with |overlay_handled| set to true, otherwise they are |
| // to be traditionally composited. Candidates with |overlay_handled| set to |
| // true must also have their |display_rect| converted to integer |
| // coordinates if necessary. |
| void CheckOverlaySupport( |
| const OverlayProcessorInterface::OutputSurfaceOverlayPlane* primary_plane, |
| OverlayCandidateList* surfaces) override {} |
| |
| Strategy& strategy() { |
| auto* strategy = strategies_.back().get(); |
| return *(static_cast<Strategy*>(strategy)); |
| } |
| |
| MOCK_CONST_METHOD0(NeedsSurfaceDamageRectList, bool()); |
| explicit TestOverlayProcessor(OutputSurface* output_surface) |
| : OverlayProcessorUsingStrategy() { |
| strategies_.push_back(std::make_unique<Strategy>()); |
| prioritization_config_.changing_threshold = false; |
| prioritization_config_.damage_rate_threshold = false; |
| } |
| ~TestOverlayProcessor() override = default; |
| }; |
| #else // Default to no overlay. |
| class TestOverlayProcessor : public OverlayProcessorStub { |
| public: |
| explicit TestOverlayProcessor(OutputSurface* output_surface) |
| : OverlayProcessorStub() {} |
| ~TestOverlayProcessor() override = default; |
| }; |
| #endif |
| |
| void MailboxReleased(const gpu::SyncToken& sync_token, bool lost_resource) {} |
| |
| static void CollectResources(std::vector<ReturnedResource>* array, |
| std::vector<ReturnedResource> returned) { |
| array->insert(array->end(), std::make_move_iterator(returned.begin()), |
| std::make_move_iterator(returned.end())); |
| } |
| |
| TEST_F(GLRendererTest, DontOverlayWithCopyRequests) { |
| cc::FakeOutputSurfaceClient output_surface_client; |
| std::unique_ptr<FakeOutputSurface> output_surface( |
| FakeOutputSurface::Create3d()); |
| #if defined(OS_WIN) |
| output_surface->set_supports_dc_layers(true); |
| #endif |
| output_surface->BindToClient(&output_surface_client); |
| |
| auto parent_resource_provider = std::make_unique<DisplayResourceProviderGL>( |
| output_surface->context_provider()); |
| |
| auto child_context_provider = TestContextProvider::Create(); |
| child_context_provider->BindToCurrentThread(); |
| auto child_resource_provider = std::make_unique<ClientResourceProvider>(); |
| |
| auto transfer_resource = TransferableResource::MakeGL( |
| gpu::Mailbox::Generate(), GL_LINEAR, GL_TEXTURE_2D, gpu::SyncToken(), |
| gfx::Size(256, 256), true); |
| auto release_callback = base::BindOnce(&MailboxReleased); |
| ResourceId resource_id = child_resource_provider->ImportResource( |
| transfer_resource, std::move(release_callback)); |
| |
| std::vector<ReturnedResource> returned_to_child; |
| int child_id = parent_resource_provider->CreateChild( |
| base::BindRepeating(&CollectResources, &returned_to_child), SurfaceId()); |
| |
| // Transfer resource to the parent. |
| std::vector<ResourceId> resource_ids_to_transfer; |
| resource_ids_to_transfer.push_back(resource_id); |
| std::vector<TransferableResource> list; |
| child_resource_provider->PrepareSendToParent( |
| resource_ids_to_transfer, &list, |
| static_cast<RasterContextProvider*>(child_context_provider.get())); |
| parent_resource_provider->ReceiveFromChild(child_id, list); |
| |
| // In DisplayResourceProvider's namespace, use the mapped resource id. |
| std::unordered_map<ResourceId, ResourceId, ResourceIdHasher> resource_map = |
| parent_resource_provider->GetChildToParentMap(child_id); |
| ResourceId parent_resource_id = resource_map[list[0].id]; |
| |
| auto processor = std::make_unique<TestOverlayProcessor>(output_surface.get()); |
| |
| RendererSettings settings; |
| FakeRendererGL renderer(&settings, &debug_settings_, output_surface.get(), |
| parent_resource_provider.get(), processor.get(), |
| base::ThreadTaskRunnerHandle::Get()); |
| renderer.Initialize(); |
| renderer.SetVisible(true); |
| |
| #if defined(OS_APPLE) |
| const MockCALayerOverlayProcessor* mock_ca_processor = |
| processor->GetTestProcessor(); |
| #elif defined(OS_WIN) |
| MockDCLayerOverlayProcessor* dc_processor = processor->GetTestProcessor(); |
| #endif |
| |
| gfx::Size viewport_size(1, 1); |
| AggregatedRenderPass* root_pass = cc::AddRenderPass( |
| &render_passes_in_draw_order_, AggregatedRenderPassId{1}, |
| gfx::Rect(viewport_size), gfx::Transform(), cc::FilterOperations()); |
| root_pass->has_transparent_background = false; |
| root_pass->copy_requests.push_back(CopyOutputRequest::CreateStubForTesting()); |
| |
| bool needs_blending = false; |
| bool premultiplied_alpha = false; |
| bool flipped = false; |
| bool nearest_neighbor = false; |
| float vertex_opacity[4] = {1.0f, 1.0f, 1.0f, 1.0f}; |
| |
| TextureDrawQuad* overlay_quad = |
| root_pass->CreateAndAppendDrawQuad<TextureDrawQuad>(); |
| overlay_quad->SetNew( |
| root_pass->CreateAndAppendSharedQuadState(), gfx::Rect(viewport_size), |
| gfx::Rect(viewport_size), needs_blending, parent_resource_id, |
| premultiplied_alpha, gfx::PointF(0, 0), gfx::PointF(1, 1), |
| SK_ColorTRANSPARENT, vertex_opacity, flipped, nearest_neighbor, |
| /*secure_output_only=*/false, gfx::ProtectedVideoType::kClear); |
| |
| // DirectRenderer::DrawFrame calls into OverlayProcessor::ProcessForOverlays. |
| // Attempt will be called for each strategy in OverlayProcessor. We have |
| // added a fake strategy, so checking for Attempt calls checks if there was |
| // any attempt to overlay, which there shouldn't be. We can't use the quad |
| // list because the render pass is cleaned up by DrawFrame. |
| #if defined(USE_OZONE) || defined(OS_ANDROID) |
| if (features::IsOverlayPrioritizationEnabled()) { |
| EXPECT_CALL(processor->strategy(), |
| AttemptPrioritized(_, _, _, _, _, _, _, _, _)) |
| .Times(0); |
| } else { |
| EXPECT_CALL(processor->strategy(), Attempt(_, _, _, _, _, _, _, _)) |
| .Times(0); |
| } |
| #elif defined(OS_APPLE) |
| EXPECT_CALL(*mock_ca_processor, ProcessForCALayerOverlays(_, _, _, _, _, _)) |
| .Times(0); |
| #elif defined(OS_WIN) |
| EXPECT_CALL(*dc_processor, Process(_, _, _, _, _, _, _, _)).Times(0); |
| #endif |
| DrawFrame(&renderer, viewport_size); |
| #if defined(USE_OZONE) || defined(OS_ANDROID) |
| Mock::VerifyAndClearExpectations(&processor->strategy()); |
| #elif defined(OS_APPLE) |
| Mock::VerifyAndClearExpectations( |
| const_cast<MockCALayerOverlayProcessor*>(mock_ca_processor)); |
| #elif defined(OS_WIN) |
| Mock::VerifyAndClearExpectations( |
| const_cast<MockDCLayerOverlayProcessor*>(dc_processor)); |
| #endif |
| |
| // Without a copy request Attempt() should be called once. |
| root_pass = cc::AddRenderPass( |
| &render_passes_in_draw_order_, AggregatedRenderPassId{1}, |
| gfx::Rect(viewport_size), gfx::Transform(), cc::FilterOperations()); |
| root_pass->has_transparent_background = false; |
| |
| overlay_quad = root_pass->CreateAndAppendDrawQuad<TextureDrawQuad>(); |
| overlay_quad->SetNew( |
| root_pass->CreateAndAppendSharedQuadState(), gfx::Rect(viewport_size), |
| gfx::Rect(viewport_size), needs_blending, parent_resource_id, |
| premultiplied_alpha, gfx::PointF(0, 0), gfx::PointF(1, 1), |
| SK_ColorTRANSPARENT, vertex_opacity, flipped, nearest_neighbor, |
| /*secure_output_only=*/false, gfx::ProtectedVideoType::kClear); |
| #if defined(USE_OZONE) || defined(OS_ANDROID) |
| if (features::IsOverlayPrioritizationEnabled()) { |
| EXPECT_CALL(processor->strategy(), |
| AttemptPrioritized(_, _, _, _, _, _, _, _, _)) |
| .Times(1); |
| } else { |
| EXPECT_CALL(processor->strategy(), Attempt(_, _, _, _, _, _, _, _)) |
| .Times(1); |
| } |
| #elif defined(OS_APPLE) |
| EXPECT_CALL(*mock_ca_processor, ProcessForCALayerOverlays(_, _, _, _, _, _)) |
| .Times(1); |
| #elif defined(OS_WIN) |
| EXPECT_CALL(*dc_processor, Process(_, _, _, _, _, _, _, _)).Times(1); |
| #endif |
| DrawFrame(&renderer, viewport_size); |
| |
| // Transfer resources back from the parent to the child. Set no resources as |
| // being in use. |
| parent_resource_provider->DeclareUsedResourcesFromChild(child_id, |
| ResourceIdSet()); |
| |
| child_resource_provider->RemoveImportedResource(resource_id); |
| child_resource_provider->ShutdownAndReleaseAllResources(); |
| } |
| |
| #if defined(OS_ANDROID) || defined(USE_OZONE) |
| class SingleOverlayOnTopProcessor : public OverlayProcessorUsingStrategy { |
| public: |
| SingleOverlayOnTopProcessor() : OverlayProcessorUsingStrategy() { |
| strategies_.push_back(std::make_unique<OverlayStrategySingleOnTop>(this)); |
| strategies_.push_back(std::make_unique<OverlayStrategyUnderlay>(this)); |
| prioritization_config_.changing_threshold = false; |
| prioritization_config_.damage_rate_threshold = false; |
| } |
| |
| bool NeedsSurfaceDamageRectList() const override { return true; } |
| bool IsOverlaySupported() const override { return true; } |
| |
| void CheckOverlaySupport( |
| const OverlayProcessorInterface::OutputSurfaceOverlayPlane* primary_plane, |
| OverlayCandidateList* surfaces) override { |
| if (!multiple_candidates_) |
| ASSERT_EQ(1U, surfaces->size()); |
| OverlayCandidate& candidate = surfaces->back(); |
| candidate.overlay_handled = true; |
| } |
| |
| void AllowMultipleCandidates() { multiple_candidates_ = true; } |
| |
| private: |
| bool multiple_candidates_ = false; |
| }; |
| |
| class WaitSyncTokenCountingGLES2Interface : public TestGLES2Interface { |
| public: |
| MOCK_METHOD1(WaitSyncTokenCHROMIUM, void(const GLbyte* sync_token)); |
| }; |
| |
| class MockOverlayScheduler { |
| public: |
| MOCK_METHOD7(Schedule, |
| void(int plane_z_order, |
| gfx::OverlayTransform plane_transform, |
| unsigned overlay_texture_id, |
| const gfx::Rect& display_bounds, |
| const gfx::RectF& uv_rect, |
| bool enable_blend, |
| unsigned gpu_fence_id)); |
| }; |
| |
| TEST_F(GLRendererTest, OverlaySyncTokensAreProcessed) { |
| #if defined(USE_X11) |
| // TODO(1096425): Remove this. |
| if (!features::IsUsingOzonePlatform()) |
| GTEST_SKIP(); |
| #endif |
| auto gl_owned = std::make_unique<WaitSyncTokenCountingGLES2Interface>(); |
| WaitSyncTokenCountingGLES2Interface* gl = gl_owned.get(); |
| |
| auto provider = TestContextProvider::Create(std::move(gl_owned)); |
| provider->BindToCurrentThread(); |
| |
| MockOverlayScheduler overlay_scheduler; |
| provider->support()->SetScheduleOverlayPlaneCallback(base::BindRepeating( |
| &MockOverlayScheduler::Schedule, base::Unretained(&overlay_scheduler))); |
| |
| cc::FakeOutputSurfaceClient output_surface_client; |
| std::unique_ptr<OutputSurface> output_surface( |
| FakeOutputSurface::Create3d(std::move(provider))); |
| output_surface->BindToClient(&output_surface_client); |
| |
| auto parent_resource_provider = std::make_unique<DisplayResourceProviderGL>( |
| output_surface->context_provider()); |
| |
| auto child_context_provider = TestContextProvider::Create(); |
| child_context_provider->BindToCurrentThread(); |
| auto child_resource_provider = std::make_unique<ClientResourceProvider>(); |
| |
| gpu::SyncToken sync_token(gpu::CommandBufferNamespace::GPU_IO, |
| gpu::CommandBufferId::FromUnsafeValue(0x123), 29); |
| auto transfer_resource = TransferableResource::MakeGL( |
| gpu::Mailbox::Generate(), GL_LINEAR, GL_TEXTURE_2D, sync_token, |
| gfx::Size(256, 256), true); |
| auto release_callback = base::BindOnce(&MailboxReleased); |
| ResourceId resource_id = child_resource_provider->ImportResource( |
| transfer_resource, std::move(release_callback)); |
| |
| std::vector<ReturnedResource> returned_to_child; |
| int child_id = parent_resource_provider->CreateChild( |
| base::BindRepeating(&CollectResources, &returned_to_child), SurfaceId()); |
| |
| // Transfer resource to the parent. |
| std::vector<ResourceId> resource_ids_to_transfer; |
| resource_ids_to_transfer.push_back(resource_id); |
| std::vector<TransferableResource> list; |
| child_resource_provider->PrepareSendToParent( |
| resource_ids_to_transfer, &list, |
| static_cast<RasterContextProvider*>(child_context_provider.get())); |
| parent_resource_provider->ReceiveFromChild(child_id, list); |
| |
| // In DisplayResourceProvider's namespace, use the mapped resource id. |
| std::unordered_map<ResourceId, ResourceId, ResourceIdHasher> resource_map = |
| parent_resource_provider->GetChildToParentMap(child_id); |
| ResourceId parent_resource_id = resource_map[list[0].id]; |
| |
| RendererSettings settings; |
| auto processor = std::make_unique<SingleOverlayOnTopProcessor>(); |
| FakeRendererGL renderer(&settings, &debug_settings_, output_surface.get(), |
| parent_resource_provider.get(), processor.get(), |
| base::ThreadTaskRunnerHandle::Get()); |
| renderer.Initialize(); |
| renderer.SetVisible(true); |
| |
| gfx::Size viewport_size(1, 1); |
| AggregatedRenderPass* root_pass = cc::AddRenderPass( |
| &render_passes_in_draw_order_, AggregatedRenderPassId{1}, |
| gfx::Rect(viewport_size), gfx::Transform(), cc::FilterOperations()); |
| root_pass->has_transparent_background = false; |
| |
| bool needs_blending = false; |
| bool premultiplied_alpha = false; |
| bool flipped = false; |
| bool nearest_neighbor = false; |
| float vertex_opacity[4] = {1.0f, 1.0f, 1.0f, 1.0f}; |
| gfx::PointF uv_top_left(0, 0); |
| gfx::PointF uv_bottom_right(1, 1); |
| |
| TextureDrawQuad* overlay_quad = |
| root_pass->CreateAndAppendDrawQuad<TextureDrawQuad>(); |
| SharedQuadState* shared_state = root_pass->CreateAndAppendSharedQuadState(); |
| shared_state->SetAll(gfx::Transform(), gfx::Rect(viewport_size), |
| gfx::Rect(viewport_size), gfx::MaskFilterInfo(), |
| absl::nullopt, false, 1, SkBlendMode::kSrcOver, 0); |
| overlay_quad->SetNew(shared_state, gfx::Rect(viewport_size), |
| gfx::Rect(viewport_size), needs_blending, |
| parent_resource_id, premultiplied_alpha, uv_top_left, |
| uv_bottom_right, SK_ColorTRANSPARENT, vertex_opacity, |
| flipped, nearest_neighbor, /*secure_output_only=*/false, |
| gfx::ProtectedVideoType::kClear); |
| |
| // The verified flush flag will be set by |
| // ClientResourceProvider::PrepareSendToParent. Before checking if the |
| // gpu::SyncToken matches, set this flag first. |
| sync_token.SetVerifyFlush(); |
| |
| // Verify that overlay_quad actually gets turned into an overlay, and even |
| // though it's not drawn, that its sync point is waited on. |
| EXPECT_CALL(*gl, WaitSyncTokenCHROMIUM(MatchesSyncToken(sync_token))) |
| .Times(1); |
| |
| EXPECT_CALL( |
| overlay_scheduler, |
| Schedule(1, gfx::OVERLAY_TRANSFORM_NONE, _, gfx::Rect(viewport_size), |
| BoundingRect(uv_top_left, uv_bottom_right), _, _)) |
| .Times(1); |
| |
| DrawFrame(&renderer, viewport_size); |
| |
| // Transfer resources back from the parent to the child. Set no resources as |
| // being in use. |
| parent_resource_provider->DeclareUsedResourcesFromChild(child_id, |
| ResourceIdSet()); |
| |
| child_resource_provider->RemoveImportedResource(resource_id); |
| child_resource_provider->ShutdownAndReleaseAllResources(); |
| } |
| #endif // defined(USE_OZONE) || defined(OS_ANDROID) |
| |
| class OutputColorMatrixMockGLES2Interface : public TestGLES2Interface { |
| public: |
| OutputColorMatrixMockGLES2Interface() = default; |
| |
| MOCK_METHOD4(UniformMatrix4fv, |
| void(GLint location, |
| GLsizei count, |
| GLboolean transpose, |
| const GLfloat* value)); |
| }; |
| |
| TEST_F(GLRendererTest, OutputColorMatrixTest) { |
| // Initialize the mock GL interface, the output surface and the renderer. |
| auto gl_owned = std::make_unique<OutputColorMatrixMockGLES2Interface>(); |
| auto* gl = gl_owned.get(); |
| auto provider = TestContextProvider::Create(std::move(gl_owned)); |
| provider->BindToCurrentThread(); |
| std::unique_ptr<FakeOutputSurface> output_surface( |
| FakeOutputSurface::Create3d(std::move(provider))); |
| cc::FakeOutputSurfaceClient output_surface_client; |
| output_surface->BindToClient(&output_surface_client); |
| auto resource_provider = std::make_unique<DisplayResourceProviderGL>( |
| output_surface->context_provider()); |
| RendererSettings settings; |
| FakeRendererGL renderer(&settings, &debug_settings_, output_surface.get(), |
| resource_provider.get()); |
| renderer.Initialize(); |
| renderer.SetVisible(true); |
| |
| // Set a non-identity color matrix on the output surface. |
| skia::Matrix44 color_matrix(skia::Matrix44::kIdentity_Constructor); |
| color_matrix.set(0, 0, 0.7f); |
| color_matrix.set(1, 1, 0.4f); |
| color_matrix.set(2, 2, 0.5f); |
| output_surface->set_color_matrix(color_matrix); |
| |
| // Create a root and a child passes to test that the output color matrix is |
| // registered only for the root pass. |
| gfx::Size viewport_size(100, 100); |
| AggregatedRenderPassId child_pass_id{2}; |
| AggregatedRenderPass* child_pass = |
| cc::AddRenderPass(&render_passes_in_draw_order_, child_pass_id, |
| gfx::Rect(viewport_size) + gfx::Vector2d(1, 2), |
| gfx::Transform(), cc::FilterOperations()); |
| AggregatedRenderPassId root_pass_id{1}; |
| AggregatedRenderPass* root_pass = cc::AddRenderPass( |
| &render_passes_in_draw_order_, root_pass_id, gfx::Rect(viewport_size), |
| gfx::Transform(), cc::FilterOperations()); |
| root_pass->damage_rect = gfx::Rect(0, 0, 25, 25); |
| cc::AddRenderPassQuad(root_pass, child_pass); |
| |
| // Verify that UniformMatrix4fv() is called only once on the root pass with |
| // the correct matrix values. |
| int call_count = 0; |
| bool output_color_matrix_invoked = false; |
| EXPECT_CALL(*gl, UniformMatrix4fv(_, 1, false, _)) |
| .WillRepeatedly(testing::WithArgs<0, 3>(testing::Invoke( |
| [&color_matrix, &renderer, &call_count, &output_color_matrix_invoked]( |
| int matrix_location, const GLfloat* gl_matrix) { |
| DCHECK(current_program(&renderer)); |
| const int color_matrix_location = |
| current_program(&renderer)->output_color_matrix_location(); |
| |
| if (matrix_location != color_matrix_location) |
| return; |
| |
| call_count++; |
| output_color_matrix_invoked = true; |
| float expected_matrix[16]; |
| color_matrix.asColMajorf(expected_matrix); |
| for (int i = 0; i < 16; ++i) |
| EXPECT_FLOAT_EQ(expected_matrix[i], gl_matrix[i]); |
| }))); |
| |
| renderer.DecideRenderPassAllocationsForFrame(render_passes_in_draw_order_); |
| DrawFrame(&renderer, viewport_size); |
| |
| EXPECT_EQ(1, call_count); |
| EXPECT_TRUE(output_color_matrix_invoked); |
| } |
| |
| class GenerateMipmapMockGLESInterface : public TestGLES2Interface { |
| public: |
| GenerateMipmapMockGLESInterface() = default; |
| |
| MOCK_METHOD3(TexParameteri, void(GLenum target, GLenum pname, GLint param)); |
| MOCK_METHOD1(GenerateMipmap, void(GLenum target)); |
| }; |
| |
| // TODO(crbug.com/803286): Currently npot texture always return false on ubuntu |
| // desktop. The npot texture check is probably failing on desktop GL. This test |
| // crashes DCHECK npot texture to catch this. When |
| // GLRendererPixelTest.DISABLED_TrilinearFiltering got passed, can remove this. |
| TEST_F(GLRendererTest, GenerateMipmap) { |
| // Initialize the mock GL interface, the output surface and the renderer. |
| auto gl_owned = std::make_unique<GenerateMipmapMockGLESInterface>(); |
| gl_owned->set_support_texture_npot(true); |
| |
| auto* gl = gl_owned.get(); |
| auto provider = TestContextProvider::Create(std::move(gl_owned)); |
| provider->BindToCurrentThread(); |
| |
| std::unique_ptr<FakeOutputSurface> output_surface( |
| FakeOutputSurface::Create3d(std::move(provider))); |
| cc::FakeOutputSurfaceClient output_surface_client; |
| output_surface->BindToClient(&output_surface_client); |
| auto resource_provider = std::make_unique<DisplayResourceProviderGL>( |
| output_surface->context_provider()); |
| RendererSettings settings; |
| FakeRendererGL renderer(&settings, &debug_settings_, output_surface.get(), |
| resource_provider.get()); |
| renderer.Initialize(); |
| renderer.SetVisible(true); |
| |
| gfx::Size viewport_size(100, 100); |
| AggregatedRenderPassId child_pass_id{2}; |
| // Create a child pass with mipmap to verify that npot texture is enabled. |
| AggregatedRenderPass* child_pass = |
| cc::AddRenderPass(&render_passes_in_draw_order_, child_pass_id, |
| gfx::Rect(viewport_size) + gfx::Vector2d(1, 2), |
| gfx::Transform(), cc::FilterOperations()); |
| child_pass->generate_mipmap = true; |
| |
| AggregatedRenderPassId root_pass_id{1}; |
| AggregatedRenderPass* root_pass = cc::AddRenderPass( |
| &render_passes_in_draw_order_, root_pass_id, gfx::Rect(viewport_size), |
| gfx::Transform(), cc::FilterOperations()); |
| root_pass->damage_rect = gfx::Rect(0, 0, 25, 25); |
| cc::AddRenderPassQuad(root_pass, child_pass); |
| renderer.DecideRenderPassAllocationsForFrame(render_passes_in_draw_order_); |
| |
| EXPECT_CALL(*gl, TexParameteri(_, _, _)).Times(4); |
| EXPECT_CALL(*gl, GenerateMipmap(GL_TEXTURE_2D)).Times(1); |
| // When generate_mipmap enabled, the GL_TEXTURE_MIN_FILTER should be |
| // GL_LINEAR_MIPMAP_LINEAR. |
| EXPECT_CALL(*gl, TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, |
| GL_LINEAR_MIPMAP_LINEAR)); |
| DrawFrame(&renderer, viewport_size); |
| } |
| |
| class FastSolidColorMockGLES2Interface : public TestGLES2Interface { |
| public: |
| FastSolidColorMockGLES2Interface() = default; |
| |
| MOCK_METHOD1(Enable, void(GLenum cap)); |
| MOCK_METHOD1(Disable, void(GLenum cap)); |
| MOCK_METHOD4(ClearColor, |
| void(GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha)); |
| MOCK_METHOD4(Scissor, void(GLint x, GLint y, GLsizei width, GLsizei height)); |
| }; |
| |
| class GLRendererFastSolidColorTest : public GLRendererTest { |
| public: |
| void SetUp() override { |
| feature_list_.InitAndEnableFeature(features::kFastSolidColorDraw); |
| GLRendererTest::SetUp(); |
| |
| auto gl_owned = std::make_unique<FastSolidColorMockGLES2Interface>(); |
| gl_owned->set_have_post_sub_buffer(true); |
| gl_ = gl_owned.get(); |
| |
| auto provider = TestContextProvider::Create(std::move(gl_owned)); |
| provider->BindToCurrentThread(); |
| |
| output_surface_ = FakeOutputSurface::Create3d(std::move(provider)); |
| output_surface_->BindToClient(&output_surface_client_); |
| |
| resource_provider_ = std::make_unique<DisplayResourceProviderGL>( |
| output_surface_->context_provider()); |
| |
| settings_.partial_swap_enabled = true; |
| settings_.slow_down_compositing_scale_factor = 1; |
| settings_.allow_antialiasing = true; |
| |
| fake_renderer_ = std::make_unique<FakeRendererGL>( |
| &settings_, &debug_settings_, output_surface_.get(), |
| resource_provider_.get()); |
| fake_renderer_->Initialize(); |
| EXPECT_TRUE(fake_renderer_->use_partial_swap()); |
| fake_renderer_->SetVisible(true); |
| } |
| |
| void TearDown() override { |
| resource_provider_.reset(); |
| fake_renderer_.reset(); |
| output_surface_.reset(); |
| gl_ = nullptr; |
| |
| GLRendererTest::TearDown(); |
| } |
| |
| FastSolidColorMockGLES2Interface* gl_ptr() { return gl_; } |
| |
| FakeOutputSurface* output_surface() { return output_surface_.get(); } |
| |
| protected: |
| void AddExpectations(bool use_fast_path, |
| const gfx::Rect& scissor_rect, |
| SkColor color = SK_ColorBLACK, |
| bool enable_stencil = false) { |
| auto* gl = gl_ptr(); |
| |
| InSequence seq; |
| |
| // Restore GL state method calls |
| EXPECT_CALL(*gl, Disable(GL_DEPTH_TEST)); |
| EXPECT_CALL(*gl, Disable(GL_CULL_FACE)); |
| EXPECT_CALL(*gl, Disable(GL_STENCIL_TEST)); |
| EXPECT_CALL(*gl, Enable(GL_BLEND)); |
| EXPECT_CALL(*gl, Disable(GL_SCISSOR_TEST)); |
| EXPECT_CALL(*gl, Scissor(0, 0, 0, 0)); |
| |
| if (!enable_stencil) |
| EXPECT_CALL(*gl, ClearColor(0, 0, 0, 0)); |
| |
| if (use_fast_path) { |
| EXPECT_CALL(*gl, Enable(GL_SCISSOR_TEST)); |
| EXPECT_CALL(*gl, Scissor(scissor_rect.x(), scissor_rect.y(), |
| scissor_rect.width(), scissor_rect.height())); |
| |
| SkColor4f color_f = SkColor4f::FromColor(color); |
| EXPECT_CALL(*gl, |
| ClearColor(color_f.fR, color_f.fG, color_f.fB, color_f.fA)); |
| |
| EXPECT_CALL(*gl, Disable(GL_SCISSOR_TEST)); |
| EXPECT_CALL(*gl, Scissor(0, 0, 0, 0)); |
| } |
| |
| if (enable_stencil) { |
| EXPECT_CALL(*gl, Enable(GL_STENCIL_TEST)); |
| EXPECT_CALL(*gl, Disable(GL_BLEND)); |
| } |
| |
| EXPECT_CALL(*gl, Disable(GL_BLEND)); |
| } |
| |
| void RunTest(const gfx::Size& viewport_size) { |
| fake_renderer_->DecideRenderPassAllocationsForFrame( |
| render_passes_in_draw_order_); |
| DrawFrame(fake_renderer_.get(), viewport_size); |
| |
| auto* gl = gl_ptr(); |
| ASSERT_TRUE(gl); |
| Mock::VerifyAndClearExpectations(gl); |
| } |
| |
| private: |
| FastSolidColorMockGLES2Interface* gl_ = nullptr; |
| std::unique_ptr<FakeRendererGL> fake_renderer_; |
| std::unique_ptr<FakeOutputSurface> output_surface_; |
| std::unique_ptr<DisplayResourceProviderGL> resource_provider_; |
| cc::FakeOutputSurfaceClient output_surface_client_; |
| RendererSettings settings_; |
| base::test::ScopedFeatureList feature_list_; |
| }; |
| |
| TEST_F(GLRendererFastSolidColorTest, RoundedCorners) { |
| gfx::Size viewport_size(500, 500); |
| gfx::Rect root_pass_output_rect(400, 400); |
| gfx::Rect root_pass_damage_rect(10, 20, 300, 200); |
| gfx::Rect quad_rect(0, 50, 100, 100); |
| |
| AggregatedRenderPassId root_pass_id{1}; |
| AggregatedRenderPass* root_pass = cc::AddRenderPassWithDamage( |
| &render_passes_in_draw_order_, root_pass_id, root_pass_output_rect, |
| root_pass_damage_rect, gfx::Transform(), cc::FilterOperations()); |
| root_pass->damage_rect = root_pass_damage_rect; |
| cc::AddQuad(root_pass, quad_rect, SK_ColorRED); |
| |
| root_pass->shared_quad_state_list.front()->mask_filter_info = |
| gfx::MaskFilterInfo(gfx::RRectF(gfx::RectF(quad_rect), 5.f)); |
| |
| // Fast Solid color draw quads should not be executed. |
| AddExpectations(false /*use_fast_path*/, gfx::Rect()); |
| |
| RunTest(viewport_size); |
| } |
| |
| TEST_F(GLRendererFastSolidColorTest, Transform3DSlowPath) { |
| gfx::Size viewport_size(500, 500); |
| gfx::Rect root_pass_damage_rect(10, 20, 300, 200); |
| gfx::Rect quad_rect(0, 50, 100, 100); |
| |
| AggregatedRenderPassId root_pass_id{1}; |
| AggregatedRenderPass* root_pass = cc::AddRenderPass( |
| &render_passes_in_draw_order_, root_pass_id, gfx::Rect(viewport_size), |
| gfx::Transform(), cc::FilterOperations()); |
| root_pass->damage_rect = root_pass_damage_rect; |
| cc::AddQuad(root_pass, quad_rect, SK_ColorRED); |
| |
| gfx::Transform tm_3d; |
| tm_3d.RotateAboutYAxis(30.0); |
| ASSERT_FALSE(tm_3d.IsFlat()); |
| |
| root_pass->shared_quad_state_list.front()->quad_to_target_transform = tm_3d; |
| |
| AddExpectations(false /*use_fast_path*/, gfx::Rect()); |
| |
| RunTest(viewport_size); |
| } |
| |
| TEST_F(GLRendererFastSolidColorTest, NonTransform3DFastPath) { |
| gfx::Size viewport_size(500, 500); |
| gfx::Rect root_pass_damage_rect(10, 20, 300, 200); |
| gfx::Rect quad_rect(0, 0, 200, 200); |
| |
| AggregatedRenderPassId root_pass_id{1}; |
| AggregatedRenderPass* root_pass = cc::AddRenderPass( |
| &render_passes_in_draw_order_, root_pass_id, gfx::Rect(viewport_size), |
| gfx::Transform(), cc::FilterOperations()); |
| root_pass->damage_rect = root_pass_damage_rect; |
| cc::AddQuad(root_pass, quad_rect, SK_ColorRED); |
| |
| gfx::Transform tm_non_3d; |
| tm_non_3d.Translate(10.f, 10.f); |
| ASSERT_TRUE(tm_non_3d.IsFlat()); |
| |
| root_pass->shared_quad_state_list.front()->quad_to_target_transform = |
| tm_non_3d; |
| |
| AddExpectations(true /*use_fast_path*/, gfx::Rect(10, 290, 200, 200), |
| SK_ColorRED); |
| |
| RunTest(viewport_size); |
| } |
| |
| TEST_F(GLRendererFastSolidColorTest, NonAxisAlignSlowPath) { |
| gfx::Size viewport_size(500, 500); |
| gfx::Rect root_pass_damage_rect(10, 20, 300, 200); |
| gfx::Rect quad_rect(0, 0, 200, 200); |
| |
| AggregatedRenderPassId root_pass_id{1}; |
| AggregatedRenderPass* root_pass = cc::AddRenderPass( |
| &render_passes_in_draw_order_, root_pass_id, gfx::Rect(viewport_size), |
| gfx::Transform(), cc::FilterOperations()); |
| root_pass->damage_rect = root_pass_damage_rect; |
| cc::AddQuad(root_pass, quad_rect, SK_ColorRED); |
| |
| gfx::Transform tm_non_axis_align; |
| tm_non_axis_align.RotateAboutZAxis(45.0); |
| ASSERT_TRUE(tm_non_axis_align.IsFlat()); |
| |
| root_pass->shared_quad_state_list.front()->quad_to_target_transform = |
| tm_non_axis_align; |
| |
| AddExpectations(false /*use_fast_path*/, gfx::Rect()); |
| |
| RunTest(viewport_size); |
| } |
| |
| TEST_F(GLRendererFastSolidColorTest, StencilSlowPath) { |
| gfx::Size viewport_size(500, 500); |
| gfx::Rect root_pass_damage_rect(10, 20, 300, 200); |
| gfx::Rect quad_rect(0, 0, 200, 200); |
| |
| AggregatedRenderPassId root_pass_id{1}; |
| AggregatedRenderPass* root_pass = cc::AddRenderPass( |
| &render_passes_in_draw_order_, root_pass_id, gfx::Rect(viewport_size), |
| gfx::Transform(), cc::FilterOperations()); |
| root_pass->damage_rect = root_pass_damage_rect; |
| root_pass->has_transparent_background = false; |
| |
| cc::AddQuad(root_pass, quad_rect, SK_ColorRED); |
| |
| AddExpectations(false /*use_fast_path*/, gfx::Rect(), SK_ColorRED, |
| true /*enable_stencil*/); |
| output_surface()->set_has_external_stencil_test(true); |
| |
| RunTest(viewport_size); |
| } |
| |
| TEST_F(GLRendererFastSolidColorTest, NeedsBlendingSlowPath) { |
| gfx::Size viewport_size(500, 500); |
| gfx::Rect root_pass_damage_rect(2, 3, 300, 200); |
| gfx::Rect full_quad_rect(0, 0, 50, 50); |
| gfx::Rect quad_rect_1(0, 0, 20, 20); |
| gfx::Rect quad_rect_2(20, 0, 20, 20); |
| gfx::Rect quad_rect_3(0, 20, 20, 20); |
| |
| AggregatedRenderPassId root_pass_id{1}; |
| AggregatedRenderPass* root_pass = cc::AddRenderPass( |
| &render_passes_in_draw_order_, root_pass_id, gfx::Rect(viewport_size), |
| gfx::Transform(), cc::FilterOperations()); |
| root_pass->damage_rect = root_pass_damage_rect; |
| |
| cc::AddQuad(root_pass, quad_rect_1, SK_ColorRED); |
| root_pass->quad_list.back()->needs_blending = true; |
| |
| cc::AddQuad(root_pass, quad_rect_2, SK_ColorBLUE); |
| root_pass->shared_quad_state_list.back()->opacity = 0.5f; |
| |
| cc::AddQuad(root_pass, quad_rect_3, SK_ColorGREEN); |
| root_pass->shared_quad_state_list.back()->blend_mode = SkBlendMode::kDstIn; |
| |
| cc::AddQuad(root_pass, full_quad_rect, SK_ColorBLACK); |
| |
| // The first solid color quad would use a fast path, but the other quads that |
| // require blending will use the slower method. |
| AddExpectations(true /*use_fast_path*/, gfx::Rect(0, 450, 50, 50), |
| SK_ColorBLACK, false /*enable_stencil*/); |
| |
| RunTest(viewport_size); |
| } |
| |
| TEST_F(GLRendererFastSolidColorTest, NeedsBlendingFastPath) { |
| gfx::Size viewport_size(500, 500); |
| gfx::Rect root_pass_damage_rect(2, 3, 300, 200); |
| gfx::Rect quad_rect_1(0, 0, 20, 20); |
| gfx::Rect quad_rect_2(20, 0, 20, 20); |
| gfx::Rect quad_rect_3(0, 20, 20, 20); |
| |
| AggregatedRenderPassId root_pass_id{1}; |
| AggregatedRenderPass* root_pass = cc::AddRenderPass( |
| &render_passes_in_draw_order_, root_pass_id, gfx::Rect(viewport_size), |
| gfx::Transform(), cc::FilterOperations()); |
| root_pass->damage_rect = root_pass_damage_rect; |
| |
| cc::AddQuad(root_pass, quad_rect_1, SK_ColorRED); |
| root_pass->quad_list.back()->needs_blending = true; |
| |
| cc::AddQuad(root_pass, quad_rect_2, SK_ColorBLUE); |
| root_pass->shared_quad_state_list.back()->opacity = 0.5f; |
| |
| cc::AddQuad(root_pass, quad_rect_3, SK_ColorGREEN); |
| root_pass->shared_quad_state_list.back()->blend_mode = SkBlendMode::kDstIn; |
| |
| auto* gl = gl_ptr(); |
| |
| // The quads here despite having blend requirements can still use fast path |
| // because they do not intersect with any other quad that has already been |
| // drawn onto the render target. |
| InSequence seq; |
| |
| // // Restore GL state method calls |
| EXPECT_CALL(*gl, Disable(GL_DEPTH_TEST)); |
| EXPECT_CALL(*gl, Disable(GL_CULL_FACE)); |
| EXPECT_CALL(*gl, Disable(GL_STENCIL_TEST)); |
| EXPECT_CALL(*gl, Enable(GL_BLEND)); |
| EXPECT_CALL(*gl, Disable(GL_SCISSOR_TEST)); |
| EXPECT_CALL(*gl, Scissor(0, 0, 0, 0)); |
| EXPECT_CALL(*gl, ClearColor(0, 0, 0, 0)); |
| |
| // Fast path draw used for green quad. |
| EXPECT_CALL(*gl, Enable(GL_SCISSOR_TEST)); |
| EXPECT_CALL(*gl, Scissor(0, 460, 20, 20)); |
| EXPECT_CALL(*gl, ClearColor(0, 1, 0, 1)); |
| EXPECT_CALL(*gl, Disable(GL_SCISSOR_TEST)); |
| EXPECT_CALL(*gl, Scissor(0, 0, 0, 0)); |
| |
| // Fast path draw used for blue quad. |
| EXPECT_CALL(*gl, Enable(GL_SCISSOR_TEST)); |
| EXPECT_CALL(*gl, Scissor(20, 480, 20, 20)); |
| EXPECT_CALL(*gl, ClearColor(0, 0, 0.5f, 0.5f)); |
| EXPECT_CALL(*gl, Disable(GL_SCISSOR_TEST)); |
| EXPECT_CALL(*gl, Scissor(0, 0, 0, 0)); |
| |
| // Fast path draw used for red quad. |
| EXPECT_CALL(*gl, Enable(GL_SCISSOR_TEST)); |
| EXPECT_CALL(*gl, Scissor(0, 480, 20, 20)); |
| EXPECT_CALL(*gl, ClearColor(1, 0, 0, 1)); |
| EXPECT_CALL(*gl, Disable(GL_SCISSOR_TEST)); |
| EXPECT_CALL(*gl, Scissor(0, 0, 0, 0)); |
| |
| EXPECT_CALL(*gl, Disable(GL_BLEND)); |
| |
| RunTest(viewport_size); |
| } |
| |
| TEST_F(GLRendererFastSolidColorTest, AntiAliasSlowPath) { |
| gfx::Size viewport_size(500, 500); |
| gfx::Rect root_pass_damage_rect(10, 20, 300, 200); |
| gfx::Rect quad_rect(0, 0, 200, 200); |
| |
| AggregatedRenderPassId root_pass_id{1}; |
| AggregatedRenderPass* root_pass = cc::AddRenderPass( |
| &render_passes_in_draw_order_, root_pass_id, gfx::Rect(viewport_size), |
| gfx::Transform(), cc::FilterOperations()); |
| root_pass->damage_rect = root_pass_damage_rect; |
| cc::AddQuad(root_pass, quad_rect, SK_ColorRED); |
| |
| gfx::Transform tm_aa; |
| tm_aa.Translate(0.1f, 0.1f); |
| ASSERT_TRUE(tm_aa.IsFlat()); |
| |
| root_pass->shared_quad_state_list.front()->quad_to_target_transform = tm_aa; |
| |
| AddExpectations(false /*use_fast_path*/, gfx::Rect()); |
| |
| RunTest(viewport_size); |
| } |
| |
| class PartialSwapMockGLES2Interface : public TestGLES2Interface { |
| public: |
| PartialSwapMockGLES2Interface() = default; |
| |
| MOCK_METHOD1(Enable, void(GLenum cap)); |
| MOCK_METHOD1(Disable, void(GLenum cap)); |
| MOCK_METHOD4(Scissor, void(GLint x, GLint y, GLsizei width, GLsizei height)); |
| MOCK_METHOD1(SetEnableDCLayersCHROMIUM, void(GLboolean enable)); |
| }; |
| |
| class GLRendererPartialSwapTest : public GLRendererTest { |
| public: |
| void SetUp() override { |
| // Force enable fast solid color draw path. |
| scoped_feature_list_.InitAndEnableFeature(features::kFastSolidColorDraw); |
| GLRendererTest::SetUp(); |
| } |
| |
| protected: |
| void RunTest(bool partial_swap, bool set_draw_rectangle) { |
| auto gl_owned = std::make_unique<PartialSwapMockGLES2Interface>(); |
| gl_owned->set_have_post_sub_buffer(true); |
| |
| auto* gl = gl_owned.get(); |
| |
| auto provider = TestContextProvider::Create(std::move(gl_owned)); |
| provider->BindToCurrentThread(); |
| |
| cc::FakeOutputSurfaceClient output_surface_client; |
| std::unique_ptr<FakeOutputSurface> output_surface( |
| FakeOutputSurface::Create3d(std::move(provider))); |
| output_surface->set_supports_dc_layers(set_draw_rectangle); |
| output_surface->BindToClient(&output_surface_client); |
| |
| auto resource_provider = std::make_unique<DisplayResourceProviderGL>( |
| output_surface->context_provider()); |
| |
| RendererSettings settings; |
| settings.partial_swap_enabled = partial_swap; |
| FakeRendererGL renderer(&settings, &debug_settings_, output_surface.get(), |
| resource_provider.get()); |
| renderer.Initialize(); |
| EXPECT_EQ(partial_swap, renderer.use_partial_swap()); |
| renderer.SetVisible(true); |
| |
| gfx::Size viewport_size(100, 100); |
| gfx::Rect root_pass_output_rect(80, 80); |
| gfx::Rect root_pass_damage_rect(2, 2, 3, 3); |
| |
| // Draw one black frame to make sure the output surface is reshaped before |
| // tests. |
| EXPECT_CALL(*gl, Disable(GL_DEPTH_TEST)).Times(1); |
| EXPECT_CALL(*gl, Disable(GL_CULL_FACE)).Times(1); |
| EXPECT_CALL(*gl, Disable(GL_STENCIL_TEST)).Times(1); |
| EXPECT_CALL(*gl, Enable(GL_BLEND)).Times(1); |
| |
| if (output_surface->capabilities().supports_dc_layers) { |
| EXPECT_CALL(*gl, Disable(GL_SCISSOR_TEST)).Times(1); |
| EXPECT_CALL(*gl, Scissor(0, 0, 0, 0)).Times(1); |
| |
| // Root render pass requires a scissor if the output surface supports |
| // dc layers. |
| EXPECT_CALL(*gl, Enable(GL_SCISSOR_TEST)).Times(3); |
| EXPECT_CALL(*gl, Scissor(0, 0, 100, 100)).Times(3); |
| } else { |
| EXPECT_CALL(*gl, Disable(GL_SCISSOR_TEST)).Times(2); |
| EXPECT_CALL(*gl, Scissor(0, 0, 0, 0)).Times(2); |
| if (set_draw_rectangle) { |
| EXPECT_CALL(*gl, Enable(GL_SCISSOR_TEST)).Times(2); |
| EXPECT_CALL(*gl, Scissor(0, 0, 100, 100)).Times(2); |
| } else { |
| EXPECT_CALL(*gl, Enable(GL_SCISSOR_TEST)).Times(1); |
| EXPECT_CALL(*gl, Scissor(0, 0, 100, 100)).Times(1); |
| } |
| } |
| |
| EXPECT_CALL(*gl, Disable(GL_BLEND)).Times(1); |
| |
| AggregatedRenderPassId root_pass_id{1}; |
| AggregatedRenderPass* root_pass = cc::AddRenderPass( |
| &render_passes_in_draw_order_, root_pass_id, gfx::Rect(viewport_size), |
| gfx::Transform(), cc::FilterOperations()); |
| root_pass->damage_rect = gfx::Rect(viewport_size); |
| cc::AddQuad(root_pass, gfx::Rect(viewport_size), SK_ColorBLACK); |
| |
| renderer.DecideRenderPassAllocationsForFrame(render_passes_in_draw_order_); |
| DrawFrame(&renderer, viewport_size); |
| Mock::VerifyAndClearExpectations(gl); |
| |
| for (int i = 0; i < 2; ++i) { |
| AggregatedRenderPass* root_pass = cc::AddRenderPassWithDamage( |
| &render_passes_in_draw_order_, root_pass_id, root_pass_output_rect, |
| root_pass_damage_rect, gfx::Transform(), cc::FilterOperations()); |
| cc::AddQuad(root_pass, gfx::Rect(root_pass_output_rect), SK_ColorGREEN); |
| |
| InSequence seq; |
| |
| // A bunch of initialization that happens. |
| EXPECT_CALL(*gl, Disable(GL_DEPTH_TEST)); |
| EXPECT_CALL(*gl, Disable(GL_CULL_FACE)); |
| EXPECT_CALL(*gl, Disable(GL_STENCIL_TEST)); |
| EXPECT_CALL(*gl, Enable(GL_BLEND)); |
| EXPECT_CALL(*gl, Disable(GL_SCISSOR_TEST)); |
| EXPECT_CALL(*gl, Scissor(0, 0, 0, 0)); |
| |
| // Partial frame, we should use a scissor to swap only that part when |
| // partial swap is enabled. |
| gfx::Rect output_rectangle = |
| partial_swap ? root_pass_damage_rect : gfx::Rect(viewport_size); |
| |
| // The scissor is flipped, so subtract the y coord and height from the |
| // bottom of the GL viewport. |
| gfx::Rect scissor_rect(output_rectangle.x(), |
| viewport_size.height() - output_rectangle.y() - |
| output_rectangle.height(), |
| output_rectangle.width(), |
| output_rectangle.height()); |
| |
| // Drawing the solid color quad using glClear and scissor rect. |
| EXPECT_CALL(*gl, Enable(GL_SCISSOR_TEST)); |
| EXPECT_CALL(*gl, Scissor(scissor_rect.x(), scissor_rect.y(), |
| scissor_rect.width(), scissor_rect.height())); |
| |
| if (partial_swap || set_draw_rectangle) { |
| EXPECT_CALL(*gl, Enable(GL_SCISSOR_TEST)); |
| EXPECT_CALL(*gl, Scissor(scissor_rect.x(), scissor_rect.y(), |
| scissor_rect.width(), scissor_rect.height())); |
| } |
| |
| // Restore GL state after solid color draw quad. |
| if (partial_swap || set_draw_rectangle) { |
| EXPECT_CALL(*gl, Enable(GL_SCISSOR_TEST)); |
| EXPECT_CALL(*gl, Scissor(scissor_rect.x(), scissor_rect.y(), |
| scissor_rect.width(), scissor_rect.height())); |
| } else { |
| EXPECT_CALL(*gl, Disable(GL_SCISSOR_TEST)); |
| EXPECT_CALL(*gl, Scissor(0, 0, 0, 0)); |
| } |
| |
| // Blending is disabled at the end of the frame. |
| EXPECT_CALL(*gl, Disable(GL_BLEND)); |
| |
| renderer.DecideRenderPassAllocationsForFrame( |
| render_passes_in_draw_order_); |
| DrawFrame(&renderer, viewport_size); |
| |
| if (set_draw_rectangle) { |
| EXPECT_EQ(output_rectangle, output_surface->last_set_draw_rectangle()); |
| } |
| |
| Mock::VerifyAndClearExpectations(gl); |
| } |
| } |
| |
| private: |
| base::test::ScopedFeatureList scoped_feature_list_; |
| }; |
| |
| TEST_F(GLRendererPartialSwapTest, PartialSwap) { |
| RunTest(true, false); |
| } |
| |
| TEST_F(GLRendererPartialSwapTest, NoPartialSwap) { |
| RunTest(false, false); |
| } |
| |
| #if defined(OS_WIN) |
| TEST_F(GLRendererPartialSwapTest, SetDrawRectangle_PartialSwap) { |
| RunTest(true, true); |
| } |
| |
| TEST_F(GLRendererPartialSwapTest, SetDrawRectangle_NoPartialSwap) { |
| RunTest(false, true); |
| } |
| |
| // Test that SetEnableDCLayersCHROMIUM is properly called when enabling |
| // and disabling DC layers. |
| TEST_F(GLRendererTest, DCLayerOverlaySwitch) { |
| auto gl_owned = std::make_unique<PartialSwapMockGLES2Interface>(); |
| gl_owned->set_have_post_sub_buffer(true); |
| auto* gl = gl_owned.get(); |
| |
| auto provider = TestContextProvider::Create(std::move(gl_owned)); |
| provider->BindToCurrentThread(); |
| |
| cc::FakeOutputSurfaceClient output_surface_client; |
| std::unique_ptr<FakeOutputSurface> output_surface( |
| FakeOutputSurface::Create3d(std::move(provider))); |
| output_surface->set_supports_dc_layers(true); |
| output_surface->BindToClient(&output_surface_client); |
| |
| auto parent_resource_provider = std::make_unique<DisplayResourceProviderGL>( |
| output_surface->context_provider()); |
| |
| auto child_context_provider = TestContextProvider::Create(); |
| child_context_provider->BindToCurrentThread(); |
| auto child_resource_provider = std::make_unique<ClientResourceProvider>(); |
| |
| auto transfer_resource = TransferableResource::MakeGL( |
| gpu::Mailbox::Generate(), GL_LINEAR, GL_TEXTURE_2D, gpu::SyncToken(), |
| gfx::Size(256, 256), true); |
| auto release_callback = base::BindOnce(&MailboxReleased); |
| ResourceId resource_id = child_resource_provider->ImportResource( |
| transfer_resource, std::move(release_callback)); |
| |
| std::vector<ReturnedResource> returned_to_child; |
| int child_id = parent_resource_provider->CreateChild( |
| base::BindRepeating(&CollectResources, &returned_to_child), SurfaceId()); |
| |
| // Transfer resource to the parent. |
| std::vector<ResourceId> resource_ids_to_transfer; |
| resource_ids_to_transfer.push_back(resource_id); |
| std::vector<TransferableResource> list; |
| child_resource_provider->PrepareSendToParent( |
| resource_ids_to_transfer, &list, |
| static_cast<RasterContextProvider*>(child_context_provider.get())); |
| parent_resource_provider->ReceiveFromChild(child_id, list); |
| // In DisplayResourceProvider's namespace, use the mapped resource id. |
| std::unordered_map<ResourceId, ResourceId, ResourceIdHasher> resource_map = |
| parent_resource_provider->GetChildToParentMap(child_id); |
| ResourceId parent_resource_id = resource_map[list[0].id]; |
| |
| auto processor = std::make_unique<OverlayProcessorWin>( |
| output_surface.get(), |
| std::make_unique<DCLayerOverlayProcessor>( |
| &debug_settings_, /*allowed_yuv_overlay_count=*/1, true)); |
| |
| RendererSettings settings; |
| settings.partial_swap_enabled = true; |
| FakeRendererGL renderer(&settings, &debug_settings_, output_surface.get(), |
| parent_resource_provider.get(), processor.get()); |
| renderer.Initialize(); |
| renderer.SetVisible(true); |
| |
| gfx::Size viewport_size(100, 100); |
| |
| for (int i = 0; i < 65; i++) { |
| AggregatedRenderPassId root_pass_id{1}; |
| AggregatedRenderPass* root_pass = cc::AddRenderPass( |
| &render_passes_in_draw_order_, root_pass_id, gfx::Rect(viewport_size), |
| gfx::Transform(), cc::FilterOperations()); |
| if (i == 0) { |
| gfx::Rect rect(0, 0, 100, 100); |
| bool needs_blending = false; |
| gfx::RectF tex_coord_rect(0, 0, 1, 1); |
| SharedQuadState* shared_state = |
| root_pass->CreateAndAppendSharedQuadState(); |
| shared_state->SetAll(gfx::Transform(), rect, rect, gfx::MaskFilterInfo(), |
| absl::nullopt, false, 1, SkBlendMode::kSrcOver, 0); |
| YUVVideoDrawQuad* quad = |
| root_pass->CreateAndAppendDrawQuad<YUVVideoDrawQuad>(); |
| quad->SetNew(shared_state, rect, rect, needs_blending, tex_coord_rect, |
| tex_coord_rect, rect.size(), rect.size(), parent_resource_id, |
| parent_resource_id, parent_resource_id, parent_resource_id, |
| gfx::ColorSpace(), 0, 1.0, 8); |
| } |
| |
| // A bunch of initialization that happens. |
| EXPECT_CALL(*gl, Disable(_)).Times(AnyNumber()); |
| EXPECT_CALL(*gl, Enable(_)).Times(AnyNumber()); |
| EXPECT_CALL(*gl, Scissor(_, _, _, _)).Times(AnyNumber()); |
| |
| // Partial frame, we should use a scissor to swap only that part when |
| // partial swap is enabled. |
| root_pass->damage_rect = gfx::Rect(2, 2, 3, 3); |
| // Frame 0 should be completely damaged because it's the first. |
| // Frame 1 should be because it changed. Frame 60 should be |
| // because it's disabling DC layers. |
| gfx::Rect output_rectangle = (i == 0 || i == 1 || i == 60) |
| ? root_pass->output_rect |
| : root_pass->damage_rect; |
| |
| // Frame 0 should have DC Layers enabled because of the overlay. |
| // After 60 frames of no overlays DC layers should be disabled again. |
| if (i == 0) |
| EXPECT_CALL(*gl, SetEnableDCLayersCHROMIUM(GL_TRUE)); |
| else if (i == 60) |
| EXPECT_CALL(*gl, SetEnableDCLayersCHROMIUM(GL_FALSE)); |
| |
| renderer.DecideRenderPassAllocationsForFrame(render_passes_in_draw_order_); |
| DrawFrame(&renderer, viewport_size); |
| EXPECT_EQ(output_rectangle, output_surface->last_set_draw_rectangle()); |
| testing::Mock::VerifyAndClearExpectations(gl); |
| } |
| |
| // Transfer resources back from the parent to the child. Set no resources as |
| // being in use. |
| parent_resource_provider->DeclareUsedResourcesFromChild(child_id, |
| ResourceIdSet()); |
| |
| child_resource_provider->RemoveImportedResource(resource_id); |
| child_resource_provider->ShutdownAndReleaseAllResources(); |
| } |
| #endif |
| |
| class GLRendererWithMockContextTest : public ::testing::Test { |
| protected: |
| class MockContextSupport : public TestContextSupport { |
| public: |
| MockContextSupport() {} |
| MOCK_METHOD1(SetAggressivelyFreeResources, |
| void(bool aggressively_free_resources)); |
| }; |
| |
| void SetUp() override { |
| auto context_support = std::make_unique<MockContextSupport>(); |
| context_support_ptr_ = context_support.get(); |
| auto context_provider = |
| TestContextProvider::Create(std::move(context_support)); |
| ASSERT_EQ(context_provider->BindToCurrentThread(), |
| gpu::ContextResult::kSuccess); |
| output_surface_ = FakeOutputSurface::Create3d(std::move(context_provider)); |
| output_surface_->BindToClient(&output_surface_client_); |
| resource_provider_ = std::make_unique<DisplayResourceProviderGL>( |
| output_surface_->context_provider()); |
| renderer_ = std::make_unique<GLRenderer>( |
| &settings_, &debug_settings_, output_surface_.get(), |
| resource_provider_.get(), nullptr, nullptr); |
| renderer_->Initialize(); |
| } |
| |
| RendererSettings settings_; |
| DebugRendererSettings debug_settings_; |
| cc::FakeOutputSurfaceClient output_surface_client_; |
| MockContextSupport* context_support_ptr_; |
| std::unique_ptr<OutputSurface> output_surface_; |
| std::unique_ptr<DisplayResourceProviderGL> resource_provider_; |
| std::unique_ptr<GLRenderer> renderer_; |
| }; |
| |
| TEST_F(GLRendererWithMockContextTest, |
| ContextPurgedWhenRendererBecomesInvisible) { |
| EXPECT_CALL(*context_support_ptr_, SetAggressivelyFreeResources(false)); |
| renderer_->SetVisible(true); |
| Mock::VerifyAndClearExpectations(context_support_ptr_); |
| |
| EXPECT_CALL(*context_support_ptr_, SetAggressivelyFreeResources(true)); |
| renderer_->SetVisible(false); |
| Mock::VerifyAndClearExpectations(context_support_ptr_); |
| } |
| |
| #if defined(USE_OZONE) || defined(OS_ANDROID) |
| class ContentBoundsOverlayProcessor : public OverlayProcessorUsingStrategy { |
| public: |
| class Strategy : public OverlayProcessorUsingStrategy::Strategy { |
| public: |
| explicit Strategy(const std::vector<gfx::Rect>& content_bounds) |
| : content_bounds_(content_bounds) {} |
| ~Strategy() override = default; |
| |
| bool Attempt(const skia::Matrix44& output_color_matrix, |
| const OverlayProcessorInterface::FilterOperationsMap& |
| render_pass_backdrop_filters, |
| DisplayResourceProvider* resource_provider, |
| AggregatedRenderPassList* render_pass_list, |
| SurfaceDamageRectList* surface_damage_rect_list, |
| const PrimaryPlane* primary_plane, |
| OverlayCandidateList* candidates, |
| std::vector<gfx::Rect>* content_bounds) override { |
| content_bounds->insert(content_bounds->end(), content_bounds_.begin(), |
| content_bounds_.end()); |
| return true; |
| } |
| |
| void ProposePrioritized( |
| const skia::Matrix44& output_color_matrix, |
| const FilterOperationsMap& render_pass_backdrop_filters, |
| DisplayResourceProvider* resource_provider, |
| AggregatedRenderPassList* render_pass_list, |
| SurfaceDamageRectList* surface_damage_rect_list, |
| const PrimaryPlane* primary_plane, |
| OverlayProposedCandidateList* candidates, |
| std::vector<gfx::Rect>* content_bounds) override { |
| auto* render_pass = render_pass_list->back().get(); |
| QuadList& quad_list = render_pass->quad_list; |
| OverlayCandidate candidate; |
| // Adding a mock candidate to the propose list so that |
| // 'AttemptPrioritized' will be called. |
| candidates->push_back({quad_list.end(), candidate, this}); |
| } |
| |
| bool AttemptPrioritized( |
| const skia::Matrix44& output_color_matrix, |
| const FilterOperationsMap& render_pass_backdrop_filters, |
| DisplayResourceProvider* resource_provider, |
| AggregatedRenderPassList* render_pass_list, |
| SurfaceDamageRectList* surface_damage_rect_list, |
| const PrimaryPlane* primary_plane, |
| OverlayCandidateList* candidates, |
| std::vector<gfx::Rect>* content_bounds, |
| OverlayProposedCandidate* proposed_candidate) override { |
| content_bounds->insert(content_bounds->end(), content_bounds_.begin(), |
| content_bounds_.end()); |
| return true; |
| } |
| |
| private: |
| const std::vector<gfx::Rect> content_bounds_; |
| }; |
| |
| explicit ContentBoundsOverlayProcessor( |
| const std::vector<gfx::Rect>& content_bounds) |
| : OverlayProcessorUsingStrategy(), content_bounds_(content_bounds) { |
| strategies_.push_back( |
| std::make_unique<Strategy>(std::move(content_bounds_))); |
| prioritization_config_.changing_threshold = false; |
| prioritization_config_.damage_rate_threshold = false; |
| } |
| |
| Strategy& strategy() { return static_cast<Strategy&>(*strategies_.back()); } |
| // Empty mock methods since this test set up uses strategies, which are only |
| // for ozone and android. |
| MOCK_CONST_METHOD0(NeedsSurfaceDamageRectList, bool()); |
| bool IsOverlaySupported() const override { return true; } |
| |
| // A list of possible overlay candidates is presented to this function. |
| // The expected result is that those candidates that can be in a separate |
| // plane are marked with |overlay_handled| set to true, otherwise they are |
| // to be traditionally composited. Candidates with |overlay_handled| set to |
| // true must also have their |display_rect| converted to integer |
| // coordinates if necessary. |
| void CheckOverlaySupport( |
| const OverlayProcessorInterface::OutputSurfaceOverlayPlane* primary_plane, |
| OverlayCandidateList* surfaces) override {} |
| |
| private: |
| std::vector<gfx::Rect> content_bounds_; |
| }; |
| |
| class GLRendererSwapWithBoundsTest : public GLRendererTest { |
| protected: |
| void RunTest(const std::vector<gfx::Rect>& content_bounds) { |
| auto gl_owned = std::make_unique<TestGLES2Interface>(); |
| gl_owned->set_have_swap_buffers_with_bounds(true); |
| |
| auto provider = TestContextProvider::Create(std::move(gl_owned)); |
| provider->BindToCurrentThread(); |
| |
| cc::FakeOutputSurfaceClient output_surface_client; |
| std::unique_ptr<FakeOutputSurface> output_surface( |
| FakeOutputSurface::Create3d(std::move(provider))); |
| output_surface->BindToClient(&output_surface_client); |
| |
| auto resource_provider = std::make_unique<DisplayResourceProviderGL>( |
| output_surface->context_provider()); |
| |
| RendererSettings settings; |
| auto processor = |
| std::make_unique<ContentBoundsOverlayProcessor>(content_bounds); |
| FakeRendererGL renderer(&settings, &debug_settings_, output_surface.get(), |
| resource_provider.get(), processor.get()); |
| renderer.Initialize(); |
| EXPECT_EQ(true, renderer.use_swap_with_bounds()); |
| renderer.SetVisible(true); |
| |
| gfx::Size viewport_size(100, 100); |
| |
| { |
| AggregatedRenderPassId root_pass_id{1}; |
| cc::AddRenderPass(&render_passes_in_draw_order_, root_pass_id, |
| gfx::Rect(viewport_size), gfx::Transform(), |
| cc::FilterOperations()); |
| |
| renderer.DecideRenderPassAllocationsForFrame( |
| render_passes_in_draw_order_); |
| DrawFrame(&renderer, viewport_size); |
| renderer.SwapBuffers({}); |
| |
| std::vector<gfx::Rect> expected_content_bounds; |
| EXPECT_EQ(content_bounds, |
| output_surface->last_sent_frame()->content_bounds); |
| } |
| } |
| }; |
| |
| TEST_F(GLRendererSwapWithBoundsTest, EmptyContent) { |
| std::vector<gfx::Rect> content_bounds; |
| RunTest(content_bounds); |
| } |
| |
| TEST_F(GLRendererSwapWithBoundsTest, NonEmpty) { |
| std::vector<gfx::Rect> content_bounds; |
| content_bounds.push_back(gfx::Rect(0, 0, 10, 10)); |
| content_bounds.push_back(gfx::Rect(20, 20, 30, 30)); |
| RunTest(content_bounds); |
| } |
| #endif // defined(USE_OZONE) || defined(OS_ANDROID) |
| |
| #if defined(OS_APPLE) |
| class MockCALayerGLES2Interface : public TestGLES2Interface { |
| public: |
| MOCK_METHOD6(ScheduleCALayerSharedStateCHROMIUM, |
| void(GLfloat opacity, |
| GLboolean is_clipped, |
| const GLfloat* clip_rect, |
| const GLfloat* rounded_corner_bounds, |
| GLint sorting_context_id, |
| const GLfloat* transform)); |
| MOCK_METHOD6(ScheduleCALayerCHROMIUM, |
| void(GLuint contents_texture_id, |
| const GLfloat* contents_rect, |
| GLuint background_color, |
| GLuint edge_aa_mask, |
| const GLfloat* bounds_rect, |
| GLuint filter)); |
| MOCK_METHOD2(ScheduleCALayerInUseQueryCHROMIUM, |
| void(GLsizei count, const GLuint* textures)); |
| MOCK_METHOD5( |
| Uniform4f, |
| void(GLint location, GLfloat x, GLfloat y, GLfloat z, GLfloat w)); |
| }; |
| |
| class CALayerGLRendererTest : public GLRendererTest { |
| protected: |
| void SetUp() override { |
| // A mock GLES2Interface that can watch CALayer stuff happen. |
| auto gles2_interface = std::make_unique<MockCALayerGLES2Interface>(); |
| // Support image storage for GpuMemoryBuffers, needed for |
| // CALayers/IOSurfaces backed by textures. |
| gles2_interface->set_support_texture_storage_image(true); |
| // Allow the renderer to make an empty SwapBuffers - skipping even the |
| // root RenderPass. |
| gles2_interface->set_have_commit_overlay_planes(true); |
| |
| gl_ = gles2_interface.get(); |
| |
| auto provider = TestContextProvider::Create(std::move(gles2_interface)); |
| provider->BindToCurrentThread(); |
| |
| cc::FakeOutputSurfaceClient output_surface_client; |
| output_surface_ = FakeOutputSurface::Create3d(std::move(provider)); |
| output_surface_->BindToClient(&output_surface_client); |
| |
| display_resource_provider_ = std::make_unique<DisplayResourceProviderGL>( |
| output_surface_->context_provider()); |
| |
| settings_ = std::make_unique<RendererSettings>(); |
| // This setting is enabled to use CALayer overlays. |
| settings_->release_overlay_resources_after_gpu_query = true; |
| // The Mac TestOverlayProcessor default to enable CALayer overlays, then all |
| // damage is removed and we can skip the root RenderPass, swapping empty. |
| overlay_processor_ = std::make_unique<OverlayProcessorMac>( |
| std::make_unique<CALayerOverlayProcessor>()); |
| renderer_ = std::make_unique<FakeRendererGL>( |
| settings_.get(), &debug_settings_, output_surface_.get(), |
| display_resource_provider_.get(), overlay_processor_.get(), |
| base::ThreadTaskRunnerHandle::Get()); |
| renderer_->Initialize(); |
| renderer_->SetVisible(true); |
| } |
| |
| void TearDown() override { |
| renderer_.reset(); |
| display_resource_provider_.reset(); |
| output_surface_.reset(); |
| } |
| |
| void DrawBlackFrame(const gfx::Size& viewport_size) { |
| AggregatedRenderPassId root_pass_id{1}; |
| |
| AggregatedRenderPass* root_pass = cc::AddRenderPass( |
| &render_passes_in_draw_order_, root_pass_id, gfx::Rect(viewport_size), |
| gfx::Transform(), cc::FilterOperations()); |
| cc::AddQuad(root_pass, gfx::Rect(viewport_size), SK_ColorBLACK); |
| |
| renderer().DecideRenderPassAllocationsForFrame( |
| render_passes_in_draw_order_); |
| |
| DrawFrame(&renderer(), viewport_size); |
| renderer().SwapBuffers(DirectRenderer::SwapFrameData()); |
| renderer().SwapBuffersComplete(/*release_fence=*/gfx::GpuFenceHandle()); |
| Mock::VerifyAndClearExpectations(&gl()); |
| } |
| |
| MockCALayerGLES2Interface& gl() const { return *gl_; } |
| FakeRendererGL& renderer() const { return *renderer_; } |
| FakeOutputSurface& output_surface() const { return *output_surface_; } |
| |
| private: |
| MockCALayerGLES2Interface* gl_; |
| std::unique_ptr<FakeOutputSurface> output_surface_; |
| std::unique_ptr<DisplayResourceProviderGL> display_resource_provider_; |
| std::unique_ptr<RendererSettings> settings_; |
| std::unique_ptr<OverlayProcessorInterface> overlay_processor_; |
| std::unique_ptr<FakeRendererGL> renderer_; |
| }; |
| |
| TEST_F(CALayerGLRendererTest, CALayerOverlaysWithAllQuadsPromoted) { |
| gfx::Size viewport_size(10, 10); |
| |
| // Draw an empty frame to make sure output surface is reshaped before tests. |
| DrawBlackFrame(viewport_size); |
| |
| // This frame has a root pass with a CompositorRenderPassDrawQuad pointing to |
| // a child pass that is at 1,2 to make it identifiable. |
| AggregatedRenderPassId child_pass_id{2}; |
| AggregatedRenderPassId root_pass_id{1}; |
| { |
| AggregatedRenderPass* child_pass = |
| cc::AddRenderPass(&render_passes_in_draw_order_, child_pass_id, |
| gfx::Rect(viewport_size) + gfx::Vector2d(1, 2), |
| gfx::Transform(), cc::FilterOperations()); |
| AggregatedRenderPass* root_pass = cc::AddRenderPass( |
| &render_passes_in_draw_order_, root_pass_id, gfx::Rect(viewport_size), |
| gfx::Transform(), cc::FilterOperations()); |
| cc::AddRenderPassQuad(root_pass, child_pass, kInvalidResourceId, |
| gfx::Transform(), SkBlendMode::kSrcOver); |
| } |
| |
| renderer().DecideRenderPassAllocationsForFrame(render_passes_in_draw_order_); |
| |
| // The child pass is drawn, promoted to an overlay, and scheduled as a |
| // CALayer. |
| { |
| InSequence sequence; |
| EXPECT_CALL(gl(), ScheduleCALayerSharedStateCHROMIUM(_, _, _, _, _, _)); |
| EXPECT_CALL(gl(), ScheduleCALayerCHROMIUM(_, _, _, _, _, _)) |
| .WillOnce( |
| Invoke([](GLuint contents_texture_id, const GLfloat* contents_rect, |
| GLuint background_color, GLuint edge_aa_mask, |
| const GLfloat* bounds_rect, GLuint filter) { |
| // This is the child CompositorRenderPassDrawQuad. |
| EXPECT_EQ(1, bounds_rect[0]); |
| EXPECT_EQ(2, bounds_rect[1]); |
| })); |
| } |
| DrawFrame(&renderer(), viewport_size); |
| Mock::VerifyAndClearExpectations(&gl()); |
| |
| renderer().SwapBuffers(DirectRenderer::SwapFrameData()); |
| |
| // The damage was eliminated when everything was promoted to CALayers. |
| ASSERT_TRUE(output_surface().last_sent_frame()->sub_buffer_rect); |
| EXPECT_TRUE(output_surface().last_sent_frame()->sub_buffer_rect->IsEmpty()); |
| |
| // Frame number 2. Same inputs, except... |
| { |
| AggregatedRenderPass* child_pass = |
| cc::AddRenderPass(&render_passes_in_draw_order_, child_pass_id, |
| gfx::Rect(viewport_size) + gfx::Vector2d(1, 2), |
| gfx::Transform(), cc::FilterOperations()); |
| AggregatedRenderPass* root_pass = cc::AddRenderPass( |
| &render_passes_in_draw_order_, root_pass_id, gfx::Rect(viewport_size), |
| gfx::Transform(), cc::FilterOperations()); |
| cc::AddRenderPassQuad(root_pass, child_pass, kInvalidResourceId, |
| gfx::Transform(), SkBlendMode::kSrcOver); |
| |
| // Use a cached RenderPass for the child. |
| child_pass->cache_render_pass = true; |
| } |
| |
| renderer().DecideRenderPassAllocationsForFrame(render_passes_in_draw_order_); |
| |
| // The child CompositorRenderPassDrawQuad gets promoted again, but importantly |
| // it did not itself have to be drawn this time as it can use the cached |
| // texture. Because we can skip the child pass, and the root pass (all quads |
| // were promoted), this exposes edge cases in GLRenderer if it assumes we draw |
| // at least one RenderPass. This still works, doesn't crash, etc, and the |
| // CompositorRenderPassDrawQuad is emitted. |
| { |
| InSequence sequence; |
| EXPECT_CALL(gl(), ScheduleCALayerSharedStateCHROMIUM(_, _, _, _, _, _)); |
| EXPECT_CALL(gl(), ScheduleCALayerCHROMIUM(_, _, _, _, _, _)); |
| } |
| DrawFrame(&renderer(), viewport_size); |
| Mock::VerifyAndClearExpectations(&gl()); |
| |
| renderer().SwapBuffers(DirectRenderer::SwapFrameData()); |
| } |
| |
| TEST_F(CALayerGLRendererTest, CALayerRoundRects) { |
| gfx::Size viewport_size(10, 10); |
| |
| // Draw an empty frame to make sure output surface is reshaped before tests. |
| DrawBlackFrame(viewport_size); |
| |
| for (size_t subtest = 0; subtest < 3; ++subtest) { |
| AggregatedRenderPass* child_pass = cc::AddRenderPass( |
| &render_passes_in_draw_order_, AggregatedRenderPassId{1}, |
| gfx::Rect(250, 250), gfx::Transform(), cc::FilterOperations()); |
| |
| AggregatedRenderPassId root_pass_id{1}; |
| AggregatedRenderPass* root_pass = cc::AddRenderPass( |
| &render_passes_in_draw_order_, root_pass_id, gfx::Rect(viewport_size), |
| gfx::Transform(), cc::FilterOperations()); |
| auto* quad = cc::AddRenderPassQuad(root_pass, child_pass); |
| SharedQuadState* sqs = |
| const_cast<SharedQuadState*>(quad->shared_quad_state); |
| |
| sqs->clip_rect = gfx::Rect(2, 2, 6, 6); |
| const float radius = 2; |
| sqs->mask_filter_info = |
| gfx::MaskFilterInfo(gfx::RRectF(gfx::RectF(*sqs->clip_rect), radius)); |
| |
| switch (subtest) { |
| case 0: |
| // Subtest 0 is a simple round rect that matches the clip rect, and |
| // should be handled by CALayers. |
| EXPECT_CALL(gl(), Uniform4f(_, _, _, _, _)).Times(1); |
| EXPECT_CALL(gl(), ScheduleCALayerSharedStateCHROMIUM(_, _, _, _, _, _)) |
| .Times(1); |
| EXPECT_CALL(gl(), ScheduleCALayerCHROMIUM(_, _, _, _, _, _)).Times(1); |
| break; |
| case 1: |
| // Subtest 1 doesn't match clip and rounded rect, but we can still |
| // use CALayers. |
| sqs->clip_rect = gfx::Rect(3, 3, 4, 4); |
| EXPECT_CALL(gl(), Uniform4f(_, _, _, _, _)).Times(1); |
| EXPECT_CALL(gl(), ScheduleCALayerCHROMIUM(_, _, _, _, _, _)).Times(1); |
| break; |
| case 2: |
| // Subtest 2 has a non-simple rounded rect. |
| gfx::RRectF rounded_corner_bounds = |
| sqs->mask_filter_info.rounded_corner_bounds(); |
| rounded_corner_bounds.SetCornerRadii(gfx::RRectF::Corner::kUpperLeft, 1, |
| 1); |
| sqs->mask_filter_info = gfx::MaskFilterInfo(rounded_corner_bounds); |
| // Called 2 extra times in order to set up the rounded corner |
| // parameters in the shader, because the CALayer is not handling |
| // the rounded corners. |
| EXPECT_CALL(gl(), Uniform4f(_, _, _, _, _)).Times(3); |
| EXPECT_CALL(gl(), ScheduleCALayerCHROMIUM(_, _, _, _, _, _)).Times(0); |
| break; |
| } |
| |
| renderer().DecideRenderPassAllocationsForFrame( |
| render_passes_in_draw_order_); |
| DrawFrame(&renderer(), viewport_size); |
| Mock::VerifyAndClearExpectations(&gl()); |
| } |
| } |
| |
| TEST_F(CALayerGLRendererTest, CALayerOverlaysReusesTextureWithDifferentSizes) { |
| gfx::Size viewport_size(300, 300); |
| |
| // Draw an empty frame to make sure output surface is reshaped before tests. |
| DrawBlackFrame(viewport_size); |
| |
| // This frame has a root pass with a CompositorRenderPassDrawQuad pointing to |
| // a child pass that is at 1,2 to make it identifiable. The child's size is |
| // 250x251, but it will be rounded up to a multiple of 64 in order to promote |
| // easier texture reuse. See https://crbug.com/146070. |
| AggregatedRenderPassId child_pass_id{2}; |
| AggregatedRenderPassId root_pass_id{1}; |
| { |
| AggregatedRenderPass* child_pass = |
| cc::AddRenderPass(&render_passes_in_draw_order_, child_pass_id, |
| gfx::Rect(250, 251) + gfx::Vector2d(1, 2), |
| gfx::Transform(), cc::FilterOperations()); |
| AggregatedRenderPass* root_pass = cc::AddRenderPass( |
| &render_passes_in_draw_order_, root_pass_id, gfx::Rect(viewport_size), |
| gfx::Transform(), cc::FilterOperations()); |
| cc::AddRenderPassQuad(root_pass, child_pass, kInvalidResourceId, |
| gfx::Transform(), SkBlendMode::kSrcOver); |
| } |
| |
| renderer().DecideRenderPassAllocationsForFrame(render_passes_in_draw_order_); |
| |
| // The child pass is drawn, promoted to an overlay, and scheduled as a |
| // CALayer. The bounds of the texture are rounded up to 256x256. We save the |
| // texture ID to make sure we reuse it correctly. |
| uint32_t saved_texture_id = 0; |
| { |
| InSequence sequence; |
| EXPECT_CALL(gl(), ScheduleCALayerSharedStateCHROMIUM(_, _, _, _, _, _)); |
| EXPECT_CALL(gl(), ScheduleCALayerCHROMIUM(_, _, _, _, _, _)) |
| .WillOnce( |
| Invoke([&](GLuint contents_texture_id, const GLfloat* contents_rect, |
| GLuint background_color, GLuint edge_aa_mask, |
| const GLfloat* bounds_rect, GLuint filter) { |
| // This is the child CompositorRenderPassDrawQuad. |
| EXPECT_EQ(1, bounds_rect[0]); |
| EXPECT_EQ(2, bounds_rect[1]); |
| // The size is rounded to a multiple of 64. |
| EXPECT_EQ(256, bounds_rect[2]); |
| EXPECT_EQ(256, bounds_rect[3]); |
| saved_texture_id = contents_texture_id; |
| })); |
| } |
| DrawFrame(&renderer(), viewport_size); |
| Mock::VerifyAndClearExpectations(&gl()); |
| renderer().SwapBuffers(DirectRenderer::SwapFrameData()); |
| |
| // ScheduleCALayerCHROMIUM happened and used a non-0 texture. |
| EXPECT_NE(saved_texture_id, 0u); |
| |
| // The damage was eliminated when everything was promoted to CALayers. |
| ASSERT_TRUE(output_surface().last_sent_frame()->sub_buffer_rect); |
| EXPECT_TRUE(output_surface().last_sent_frame()->sub_buffer_rect->IsEmpty()); |
| |
| // The texture will be checked to verify if it is free yet. |
| EXPECT_CALL(gl(), ScheduleCALayerInUseQueryCHROMIUM(1, _)); |
| renderer().SwapBuffersComplete(/*release_fence=*/gfx::GpuFenceHandle()); |
| Mock::VerifyAndClearExpectations(&gl()); |
| |
| // Frame number 2. We change the size of the child RenderPass to be smaller |
| // than the next multiple of 64, but larger than half the previous size so |
| // that our texture reuse heuristics will reuse the texture if it is free. |
| // For now, it is not. |
| { |
| AggregatedRenderPass* child_pass = |
| cc::AddRenderPass(&render_passes_in_draw_order_, child_pass_id, |
| gfx::Rect(190, 191) + gfx::Vector2d(1, 2), |
| gfx::Transform(), cc::FilterOperations()); |
| AggregatedRenderPass* root_pass = cc::AddRenderPass( |
| &render_passes_in_draw_order_, root_pass_id, gfx::Rect(viewport_size), |
| gfx::Transform(), cc::FilterOperations()); |
| cc::AddRenderPassQuad(root_pass, child_pass, kInvalidResourceId, |
| gfx::Transform(), SkBlendMode::kSrcOver); |
| } |
| |
| renderer().DecideRenderPassAllocationsForFrame(render_passes_in_draw_order_); |
| |
| // The child RenderPass will use a new 192x192 texture, since the last texture |
| // is still in use. |
| { |
| InSequence sequence; |
| EXPECT_CALL(gl(), ScheduleCALayerSharedStateCHROMIUM(_, _, _, _, _, _)); |
| EXPECT_CALL(gl(), ScheduleCALayerCHROMIUM(_, _, _, _, _, _)) |
| .WillOnce( |
| Invoke([&](GLuint contents_texture_id, const GLfloat* contents_rect, |
| GLuint background_color, GLuint edge_aa_mask, |
| const GLfloat* bounds_rect, GLuint filter) { |
| // New texture id. |
| EXPECT_NE(saved_texture_id, contents_texture_id); |
| EXPECT_EQ(1, bounds_rect[0]); |
| EXPECT_EQ(2, bounds_rect[1]); |
| // The texture is 192x192 since we snap up to multiples of 64. |
| EXPECT_EQ(192, bounds_rect[2]); |
| EXPECT_EQ(192, bounds_rect[3]); |
| })); |
| } |
| DrawFrame(&renderer(), viewport_size); |
| Mock::VerifyAndClearExpectations(&gl()); |
| renderer().SwapBuffers(DirectRenderer::SwapFrameData()); |
| |
| // There are now 2 textures to check if they are free. |
| EXPECT_CALL(gl(), ScheduleCALayerInUseQueryCHROMIUM(2, _)); |
| renderer().SwapBuffersComplete(/*release_fence=*/gfx::GpuFenceHandle()); |
| Mock::VerifyAndClearExpectations(&gl()); |
| |
| // The first (256x256) texture is returned to the GLRenderer. |
| renderer().DidReceiveTextureInUseResponses({{saved_texture_id, false}}); |
| |
| // Frame number 3 looks just like frame number 2. The child RenderPass is |
| // smaller than the next multiple of 64 from the released texture, but larger |
| // than half of its size so that our texture reuse heuristics will kick in. |
| { |
| AggregatedRenderPass* child_pass = |
| cc::AddRenderPass(&render_passes_in_draw_order_, child_pass_id, |
| gfx::Rect(190, 191) + gfx::Vector2d(1, 2), |
| gfx::Transform(), cc::FilterOperations()); |
| AggregatedRenderPass* root_pass = cc::AddRenderPass( |
| &render_passes_in_draw_order_, root_pass_id, gfx::Rect(viewport_size), |
| gfx::Transform(), cc::FilterOperations()); |
| cc::AddRenderPassQuad(root_pass, child_pass, kInvalidResourceId, |
| gfx::Transform(), SkBlendMode::kSrcOver); |
| } |
| |
| renderer().DecideRenderPassAllocationsForFrame(render_passes_in_draw_order_); |
| |
| // The child RenderPass would try to use a 192x192 texture, but since we have |
| // an existing 256x256 texture, we can reuse that. |
| { |
| InSequence sequence; |
| EXPECT_CALL(gl(), ScheduleCALayerSharedStateCHROMIUM(_, _, _, _, _, _)); |
| EXPECT_CALL(gl(), ScheduleCALayerCHROMIUM(_, _, _, _, _, _)) |
| .WillOnce( |
| Invoke([&](GLuint contents_texture_id, const GLfloat* contents_rect, |
| GLuint background_color, GLuint edge_aa_mask, |
| const GLfloat* bounds_rect, GLuint filter) { |
| // The first texture is reused. |
| EXPECT_EQ(saved_texture_id, contents_texture_id); |
| // This is the child CompositorRenderPassDrawQuad. |
| EXPECT_EQ(1, bounds_rect[0]); |
| EXPECT_EQ(2, bounds_rect[1]); |
| // The size here is the size of the texture being used, not |
| // the size we tried to use (192x192). |
| EXPECT_EQ(256, bounds_rect[2]); |
| EXPECT_EQ(256, bounds_rect[3]); |
| })); |
| } |
| DrawFrame(&renderer(), viewport_size); |
| Mock::VerifyAndClearExpectations(&gl()); |
| renderer().SwapBuffers(DirectRenderer::SwapFrameData()); |
| } |
| |
| TEST_F(CALayerGLRendererTest, CALayerOverlaysDontReuseTooBigTexture) { |
| gfx::Size viewport_size(300, 300); |
| |
| // Draw an empty frame to make sure output surface is reshaped before tests. |
| DrawBlackFrame(viewport_size); |
| |
| // This frame has a root pass with a CompositorRenderPassDrawQuad pointing to |
| // a child pass that is at 1,2 to make it identifiable. The child's size is |
| // 250x251, but it will be rounded up to a multiple of 64 in order to promote |
| // easier texture reuse. See https://crbug.com/146070. |
| AggregatedRenderPassId child_pass_id{2}; |
| AggregatedRenderPassId root_pass_id{1}; |
| { |
| AggregatedRenderPass* child_pass = |
| cc::AddRenderPass(&render_passes_in_draw_order_, child_pass_id, |
| gfx::Rect(250, 251) + gfx::Vector2d(1, 2), |
| gfx::Transform(), cc::FilterOperations()); |
| AggregatedRenderPass* root_pass = cc::AddRenderPass( |
| &render_passes_in_draw_order_, root_pass_id, gfx::Rect(viewport_size), |
| gfx::Transform(), cc::FilterOperations()); |
| cc::AddRenderPassQuad(root_pass, child_pass, kInvalidResourceId, |
| gfx::Transform(), SkBlendMode::kSrcOver); |
| } |
| |
| renderer().DecideRenderPassAllocationsForFrame(render_passes_in_draw_order_); |
| |
| // The child pass is drawn, promoted to an overlay, and scheduled as a |
| // CALayer. The bounds of the texture are rounded up to 256x256. We save the |
| // texture ID to make sure we reuse it correctly. |
| uint32_t saved_texture_id = 0; |
| { |
| InSequence sequence; |
| EXPECT_CALL(gl(), ScheduleCALayerSharedStateCHROMIUM(_, _, _, _, _, _)); |
| EXPECT_CALL(gl(), ScheduleCALayerCHROMIUM(_, _, _, _, _, _)) |
| .WillOnce( |
| Invoke([&](GLuint contents_texture_id, const GLfloat* contents_rect, |
| GLuint background_color, GLuint edge_aa_mask, |
| const GLfloat* bounds_rect, GLuint filter) { |
| // This is the child CompositorRenderPassDrawQuad. |
| EXPECT_EQ(1, bounds_rect[0]); |
| EXPECT_EQ(2, bounds_rect[1]); |
| // The size is rounded to a multiple of 64. |
| EXPECT_EQ(256, bounds_rect[2]); |
| EXPECT_EQ(256, bounds_rect[3]); |
| saved_texture_id = contents_texture_id; |
| })); |
| } |
| DrawFrame(&renderer(), viewport_size); |
| Mock::VerifyAndClearExpectations(&gl()); |
| renderer().SwapBuffers(DirectRenderer::SwapFrameData()); |
| |
| // ScheduleCALayerCHROMIUM happened and used a non-0 texture. |
| EXPECT_NE(saved_texture_id, 0u); |
| |
| // The damage was eliminated when everything was promoted to CALayers. |
| ASSERT_TRUE(output_surface().last_sent_frame()->sub_buffer_rect); |
| EXPECT_TRUE(output_surface().last_sent_frame()->sub_buffer_rect->IsEmpty()); |
| |
| // The texture will be checked to verify if it is free yet. |
| EXPECT_CALL(gl(), ScheduleCALayerInUseQueryCHROMIUM(1, _)); |
| renderer().SwapBuffersComplete(/*release_fence=*/gfx::GpuFenceHandle()); |
| Mock::VerifyAndClearExpectations(&gl()); |
| |
| // Frame number 2. We change the size of the child RenderPass to be much |
| // smaller. |
| { |
| AggregatedRenderPass* child_pass = |
| cc::AddRenderPass(&render_passes_in_draw_order_, child_pass_id, |
| gfx::Rect(20, 21) + gfx::Vector2d(1, 2), |
| gfx::Transform(), cc::FilterOperations()); |
| AggregatedRenderPass* root_pass = cc::AddRenderPass( |
| &render_passes_in_draw_order_, root_pass_id, gfx::Rect(viewport_size), |
| gfx::Transform(), cc::FilterOperations()); |
| cc::AddRenderPassQuad(root_pass, child_pass, kInvalidResourceId, |
| gfx::Transform(), SkBlendMode::kSrcOver); |
| } |
| |
| renderer().DecideRenderPassAllocationsForFrame(render_passes_in_draw_order_); |
| |
| // The child RenderPass will use a new 64x64 texture, since the last texture |
| // is still in use. |
| { |
| InSequence sequence; |
| EXPECT_CALL(gl(), ScheduleCALayerSharedStateCHROMIUM(_, _, _, _, _, _)); |
| EXPECT_CALL(gl(), ScheduleCALayerCHROMIUM(_, _, _, _, _, _)) |
| .WillOnce( |
| Invoke([&](GLuint contents_texture_id, const GLfloat* contents_rect, |
| GLuint background_color, GLuint edge_aa_mask, |
| const GLfloat* bounds_rect, GLuint filter) { |
| // New texture id. |
| EXPECT_NE(saved_texture_id, contents_texture_id); |
| EXPECT_EQ(1, bounds_rect[0]); |
| EXPECT_EQ(2, bounds_rect[1]); |
| // The texture is 64x64 since we snap up to multiples of 64. |
| EXPECT_EQ(64, bounds_rect[2]); |
| EXPECT_EQ(64, bounds_rect[3]); |
| })); |
| } |
| DrawFrame(&renderer(), viewport_size); |
| Mock::VerifyAndClearExpectations(&gl()); |
| renderer().SwapBuffers(DirectRenderer::SwapFrameData()); |
| |
| // There are now 2 textures to check if they are free. |
| EXPECT_CALL(gl(), ScheduleCALayerInUseQueryCHROMIUM(2, _)); |
| renderer().SwapBuffersComplete(/*release_fence=*/gfx::GpuFenceHandle()); |
| Mock::VerifyAndClearExpectations(&gl()); |
| |
| // The first (256x256) texture is returned to the GLRenderer. |
| renderer().DidReceiveTextureInUseResponses({{saved_texture_id, false}}); |
| |
| // Frame number 3 looks just like frame number 2. The child RenderPass is |
| // too small to reuse the old texture. |
| { |
| AggregatedRenderPass* child_pass = |
| cc::AddRenderPass(&render_passes_in_draw_order_, child_pass_id, |
| gfx::Rect(20, 21) + gfx::Vector2d(1, 2), |
| gfx::Transform(), cc::FilterOperations()); |
| AggregatedRenderPass* root_pass = cc::AddRenderPass( |
| &render_passes_in_draw_order_, root_pass_id, gfx::Rect(viewport_size), |
| gfx::Transform(), cc::FilterOperations()); |
| cc::AddRenderPassQuad(root_pass, child_pass, kInvalidResourceId, |
| gfx::Transform(), SkBlendMode::kSrcOver); |
| } |
| |
| renderer().DecideRenderPassAllocationsForFrame(render_passes_in_draw_order_); |
| |
| // The child RenderPass would try to use a 64x64 texture. We have a free and |
| // existing 256x256 texture, but it's too large for us to reuse it. |
| { |
| InSequence sequence; |
| EXPECT_CALL(gl(), ScheduleCALayerSharedStateCHROMIUM(_, _, _, _, _, _)); |
| EXPECT_CALL(gl(), ScheduleCALayerCHROMIUM(_, _, _, _, _, _)) |
| .WillOnce( |
| Invoke([&](GLuint contents_texture_id, const GLfloat* contents_rect, |
| GLuint background_color, GLuint edge_aa_mask, |
| const GLfloat* bounds_rect, GLuint filter) { |
| // The first texture is not reused. |
| EXPECT_NE(saved_texture_id, contents_texture_id); |
| // This is the child CompositorRenderPassDrawQuad. |
| EXPECT_EQ(1, bounds_rect[0]); |
| EXPECT_EQ(2, bounds_rect[1]); |
| // The new texture has a smaller size. |
| EXPECT_EQ(64, bounds_rect[2]); |
| EXPECT_EQ(64, bounds_rect[3]); |
| })); |
| } |
| DrawFrame(&renderer(), viewport_size); |
| Mock::VerifyAndClearExpectations(&gl()); |
| renderer().SwapBuffers(DirectRenderer::SwapFrameData()); |
| } |
| |
| TEST_F(CALayerGLRendererTest, CALayerOverlaysReuseAfterNoSwapBuffers) { |
| gfx::Size viewport_size(300, 300); |
| |
| // This frame has a root pass with a CompositorRenderPassDrawQuad pointing to |
| // a child pass that is at 1,2 to make it identifiable. |
| AggregatedRenderPassId child_pass_id{2}; |
| AggregatedRenderPassId root_pass_id{1}; |
| { |
| AggregatedRenderPass* child_pass = |
| cc::AddRenderPass(&render_passes_in_draw_order_, child_pass_id, |
| gfx::Rect(100, 100) + gfx::Vector2d(1, 2), |
| gfx::Transform(), cc::FilterOperations()); |
| AggregatedRenderPass* root_pass = cc::AddRenderPass( |
| &render_passes_in_draw_order_, root_pass_id, gfx::Rect(viewport_size), |
| gfx::Transform(), cc::FilterOperations()); |
| cc::AddRenderPassQuad(root_pass, child_pass, kInvalidResourceId, |
| gfx::Transform(), SkBlendMode::kSrcOver); |
| } |
| |
| renderer().DecideRenderPassAllocationsForFrame(render_passes_in_draw_order_); |
| |
| // The child pass is drawn, promoted to an overlay, and scheduled as a |
| // CALayer. We save the texture ID to make sure we reuse it correctly. |
| uint32_t saved_texture_id = 0; |
| { |
| InSequence sequence; |
| EXPECT_CALL(gl(), ScheduleCALayerSharedStateCHROMIUM(_, _, _, _, _, _)); |
| EXPECT_CALL(gl(), ScheduleCALayerCHROMIUM(_, _, _, _, _, _)) |
| .WillOnce( |
| Invoke([&](GLuint contents_texture_id, const GLfloat* contents_rect, |
| GLuint background_color, GLuint edge_aa_mask, |
| const GLfloat* bounds_rect, GLuint filter) { |
| // This is the child CompositorRenderPassDrawQuad. |
| EXPECT_EQ(1, bounds_rect[0]); |
| EXPECT_EQ(2, bounds_rect[1]); |
| saved_texture_id = contents_texture_id; |
| })); |
| } |
| DrawFrame(&renderer(), viewport_size); |
| Mock::VerifyAndClearExpectations(&gl()); |
| |
| // ScheduleCALayerCHROMIUM happened and used a non-0 texture. |
| EXPECT_NE(saved_texture_id, 0u); |
| |
| // SwapBuffers() is *not* called though! Display can do this sometimes. |
| |
| // Frame number 2. We can not reuse the texture since the last one isn't |
| // returned yet. We use a different size so we can control which texture gets |
| // reused later. |
| { |
| AggregatedRenderPass* child_pass = |
| cc::AddRenderPass(&render_passes_in_draw_order_, child_pass_id, |
| gfx::Rect(200, 200) + gfx::Vector2d(1, 2), |
| gfx::Transform(), cc::FilterOperations()); |
| AggregatedRenderPass* root_pass = cc::AddRenderPass( |
| &render_passes_in_draw_order_, root_pass_id, gfx::Rect(viewport_size), |
| gfx::Transform(), cc::FilterOperations()); |
| cc::AddRenderPassQuad(root_pass, child_pass, kInvalidResourceId, |
| gfx::Transform(), SkBlendMode::kSrcOver); |
| } |
| |
| renderer().DecideRenderPassAllocationsForFrame(render_passes_in_draw_order_); |
| |
| uint32_t second_saved_texture_id = 0; |
| { |
| InSequence sequence; |
| EXPECT_CALL(gl(), ScheduleCALayerSharedStateCHROMIUM(_, _, _, _, _, _)); |
| EXPECT_CALL(gl(), ScheduleCALayerCHROMIUM(_, _, _, _, _, _)) |
| .WillOnce( |
| Invoke([&](GLuint contents_texture_id, const GLfloat* contents_rect, |
| GLuint background_color, GLuint edge_aa_mask, |
| const GLfloat* bounds_rect, GLuint filter) { |
| // New texture id. |
| EXPECT_NE(saved_texture_id, contents_texture_id); |
| EXPECT_EQ(1, bounds_rect[0]); |
| EXPECT_EQ(2, bounds_rect[1]); |
| second_saved_texture_id = contents_texture_id; |
| })); |
| } |
| DrawFrame(&renderer(), viewport_size); |
| Mock::VerifyAndClearExpectations(&gl()); |
| |
| // SwapBuffers() *does* happen this time. |
| renderer().SwapBuffers(DirectRenderer::SwapFrameData()); |
| |
| // There are 2 textures to check if they are free. |
| EXPECT_CALL(gl(), ScheduleCALayerInUseQueryCHROMIUM(2, _)); |
| renderer().SwapBuffersComplete(/*release_fence=*/gfx::GpuFenceHandle()); |
| Mock::VerifyAndClearExpectations(&gl()); |
| |
| // Both textures get returned and the 2nd one can be reused. |
| renderer().DidReceiveTextureInUseResponses( |
| {{saved_texture_id, false}, {second_saved_texture_id, false}}); |
| |
| // Frame number 3 looks just like frame number 2. |
| { |
| AggregatedRenderPass* child_pass = |
| cc::AddRenderPass(&render_passes_in_draw_order_, child_pass_id, |
| gfx::Rect(200, 200) + gfx::Vector2d(1, 2), |
| gfx::Transform(), cc::FilterOperations()); |
| AggregatedRenderPass* root_pass = cc::AddRenderPass( |
| &render_passes_in_draw_order_, root_pass_id, gfx::Rect(viewport_size), |
| gfx::Transform(), cc::FilterOperations()); |
| cc::AddRenderPassQuad(root_pass, child_pass, kInvalidResourceId, |
| gfx::Transform(), SkBlendMode::kSrcOver); |
| } |
| |
| renderer().DecideRenderPassAllocationsForFrame(render_passes_in_draw_order_); |
| |
| // The 2nd texture that we sent has been returned so we can reuse it. We |
| // verify that happened. |
| { |
| InSequence sequence; |
| EXPECT_CALL(gl(), ScheduleCALayerSharedStateCHROMIUM(_, _, _, _, _, _)); |
| EXPECT_CALL(gl(), ScheduleCALayerCHROMIUM(_, _, _, _, _, _)) |
| .WillOnce( |
| Invoke([&](GLuint contents_texture_id, const GLfloat* contents_rect, |
| GLuint background_color, GLuint edge_aa_mask, |
| const GLfloat* bounds_rect, GLuint filter) { |
| // The second texture is reused. |
| EXPECT_EQ(second_saved_texture_id, contents_texture_id); |
| // This is the child CompositorRenderPassDrawQuad. |
| EXPECT_EQ(1, bounds_rect[0]); |
| EXPECT_EQ(2, bounds_rect[1]); |
| })); |
| } |
| DrawFrame(&renderer(), viewport_size); |
| Mock::VerifyAndClearExpectations(&gl()); |
| renderer().SwapBuffers(DirectRenderer::SwapFrameData()); |
| } |
| |
| TEST_F(CALayerGLRendererTest, CALayerOverlaysReuseManyIfReturnedSlowly) { |
| gfx::Size viewport_size(300, 300); |
| |
| // Draw an empty frame to make sure output surface is reshaped before tests. |
| DrawBlackFrame(viewport_size); |
| |
| // Each frame has a root pass with a CompositorRenderPassDrawQuad pointing to |
| // a child pass. We generate a bunch of frames and swap them, each with a |
| // different child RenderPass id, without getting any of the resources back |
| // from the OS. |
| AggregatedRenderPassId root_pass_id{1}; |
| |
| // The number is at least 2 larger than the number of textures we expect to |
| // reuse, so that we can leave one in the OS, and have 1 texture returned but |
| // not reused. |
| const int kNumSendManyTextureIds = 7; |
| uint32_t sent_texture_ids[kNumSendManyTextureIds]; |
| for (int i = 0; i < kNumSendManyTextureIds; ++i) { |
| AggregatedRenderPass* child_pass = cc::AddRenderPass( |
| &render_passes_in_draw_order_, AggregatedRenderPassId{i + 2}, |
| gfx::Rect(250, 251) + gfx::Vector2d(1, 2), gfx::Transform(), |
| cc::FilterOperations()); |
| AggregatedRenderPass* root_pass = cc::AddRenderPass( |
| &render_passes_in_draw_order_, root_pass_id, gfx::Rect(viewport_size), |
| gfx::Transform(), cc::FilterOperations()); |
| cc::AddRenderPassQuad(root_pass, child_pass, kInvalidResourceId, |
| gfx::Transform(), SkBlendMode::kSrcOver); |
| |
| renderer().DecideRenderPassAllocationsForFrame( |
| render_passes_in_draw_order_); |
| |
| InSequence sequence; |
| EXPECT_CALL(gl(), ScheduleCALayerSharedStateCHROMIUM(_, _, _, _, _, _)); |
| EXPECT_CALL(gl(), ScheduleCALayerCHROMIUM(_, _, _, _, _, _)) |
| .WillOnce( |
| Invoke([&](GLuint contents_texture_id, const GLfloat* contents_rect, |
| GLuint background_color, GLuint edge_aa_mask, |
| const GLfloat* bounds_rect, GLuint filter) { |
| // This is the child CompositorRenderPassDrawQuad. |
| EXPECT_EQ(1, bounds_rect[0]); |
| EXPECT_EQ(2, bounds_rect[1]); |
| sent_texture_ids[i] = contents_texture_id; |
| })); |
| DrawFrame(&renderer(), viewport_size); |
| Mock::VerifyAndClearExpectations(&gl()); |
| renderer().SwapBuffers(DirectRenderer::SwapFrameData()); |
| |
| // ScheduleCALayerCHROMIUM happened and used a non-0 texture. |
| EXPECT_NE(sent_texture_ids[i], 0u); |
| |
| // The damage was eliminated when everything was promoted to CALayers. |
| ASSERT_TRUE(output_surface().last_sent_frame()->sub_buffer_rect); |
| EXPECT_TRUE(output_surface().last_sent_frame()->sub_buffer_rect->IsEmpty()); |
| |
| // All sent textures will be checked to verify if they are free yet. |
| EXPECT_CALL(gl(), ScheduleCALayerInUseQueryCHROMIUM(i + 1, _)); |
| renderer().SwapBuffersComplete(/*release_fence=*/gfx::GpuFenceHandle()); |
| Mock::VerifyAndClearExpectations(&gl()); |
| } |
| |
| // Now all but 1 texture get returned by the OS, so they are all inserted |
| // into the cache for reuse. |
| std::vector<uint32_t> returned_texture_ids; |
| for (int i = 0; i < kNumSendManyTextureIds - 1; ++i) { |
| uint32_t id = sent_texture_ids[i]; |
| renderer().DidReceiveTextureInUseResponses({{id, false}}); |
| returned_texture_ids.push_back(id); |
| } |
| |
| // We should keep *some* of these textures around to reuse them across |
| // multiple frames. https://crbug.com/146070 motivates this, and empirical |
| // testing found 5 to be a good number. |
| const int kNumSendReusedTextures = 5; |
| // See comment on |kNumSendManyTextureIds|. |
| ASSERT_LT(kNumSendReusedTextures, kNumSendManyTextureIds - 1); |
| |
| for (int i = 0; i < kNumSendReusedTextures + 1; ++i) { |
| // We use different RenderPass ids to ensure that the cache allows reuse |
| // even if they don't match. |
| AggregatedRenderPass* child_pass = cc::AddRenderPass( |
| &render_passes_in_draw_order_, AggregatedRenderPassId{i + 100}, |
| gfx::Rect(250, 251) + gfx::Vector2d(1, 2), gfx::Transform(), |
| cc::FilterOperations()); |
| AggregatedRenderPass* root_pass = cc::AddRenderPass( |
| &render_passes_in_draw_order_, root_pass_id, gfx::Rect(viewport_size), |
| gfx::Transform(), cc::FilterOperations()); |
| cc::AddRenderPassQuad(root_pass, child_pass, kInvalidResourceId, |
| gfx::Transform(), SkBlendMode::kSrcOver); |
| |
| renderer().DecideRenderPassAllocationsForFrame( |
| render_passes_in_draw_order_); |
| |
| InSequence sequence; |
| EXPECT_CALL(gl(), ScheduleCALayerSharedStateCHROMIUM(_, _, _, _, _, _)); |
| EXPECT_CALL(gl(), ScheduleCALayerCHROMIUM(_, _, _, _, _, _)) |
| .WillOnce(Invoke([&](GLuint contents_texture_id, |
| const GLfloat* contents_rect, |
| GLuint background_color, GLuint edge_aa_mask, |
| const GLfloat* bounds_rect, GLuint filter) { |
| // This is the child CompositorRenderPassDrawQuad. |
| EXPECT_EQ(1, bounds_rect[0]); |
| EXPECT_EQ(2, bounds_rect[1]); |
| |
| if (i < kNumSendReusedTextures) { |
| // The texture id should be from the set of returned ones. |
| EXPECT_THAT(returned_texture_ids, Contains(contents_texture_id)); |
| base::Erase(returned_texture_ids, contents_texture_id); |
| } else { |
| // More textures were returned at once than we expect to reuse |
| // so eventually we should be making a new texture to show we're |
| // not just keeping infinity textures in the cache. |
| EXPECT_THAT(returned_texture_ids, |
| Not(Contains(contents_texture_id))); |
| // This shows that there was some returned id that we didn't use. |
| EXPECT_FALSE(returned_texture_ids.empty()); |
| } |
| })); |
| DrawFrame(&renderer(), viewport_size); |
| Mock::VerifyAndClearExpectations(&gl()); |
| renderer().SwapBuffers(DirectRenderer::SwapFrameData()); |
| |
| // All sent textures will be checked to verify if they are free yet. There's |
| // also 1 outstanding texture to check for that wasn't returned yet from the |
| // above loop. |
| EXPECT_CALL(gl(), ScheduleCALayerInUseQueryCHROMIUM(i + 2, _)); |
| renderer().SwapBuffersComplete(/*release_fence=*/gfx::GpuFenceHandle()); |
| Mock::VerifyAndClearExpectations(&gl()); |
| } |
| } |
| |
| TEST_F(CALayerGLRendererTest, CALayerOverlaysCachedTexturesAreFreed) { |
| gfx::Size viewport_size(300, 300); |
| |
| // Draw an empty frame to make sure output surface is reshaped before tests. |
| DrawBlackFrame(viewport_size); |
| |
| // Each frame has a root pass with a CompositorRenderPassDrawQuad pointing to |
| // a child pass. We generate a bunch of frames and swap them, each with a |
| // different child RenderPass id, without getting any of the resources back |
| // from the OS. |
| AggregatedRenderPassId child_pass_id{2}; |
| AggregatedRenderPassId root_pass_id{1}; |
| |
| // We send a whole bunch of textures as overlays to the OS. |
| const int kNumSendManyTextureIds = 7; |
| uint32_t sent_texture_ids[kNumSendManyTextureIds]; |
| for (int i = 0; i < kNumSendManyTextureIds; ++i) { |
| AggregatedRenderPass* child_pass = cc::AddRenderPass( |
| &render_passes_in_draw_order_, AggregatedRenderPassId{i + 2}, |
| gfx::Rect(250, 251) + gfx::Vector2d(1, 2), gfx::Transform(), |
| cc::FilterOperations()); |
| AggregatedRenderPass* root_pass = cc::AddRenderPass( |
| &render_passes_in_draw_order_, root_pass_id, gfx::Rect(viewport_size), |
| gfx::Transform(), cc::FilterOperations()); |
| cc::AddRenderPassQuad(root_pass, child_pass, kInvalidResourceId, |
| gfx::Transform(), SkBlendMode::kSrcOver); |
| |
| renderer().DecideRenderPassAllocationsForFrame( |
| render_passes_in_draw_order_); |
| |
| InSequence sequence; |
| EXPECT_CALL(gl(), ScheduleCALayerSharedStateCHROMIUM(_, _, _, _, _, _)); |
| EXPECT_CALL(gl(), ScheduleCALayerCHROMIUM(_, _, _, _, _, _)) |
| .WillOnce( |
| Invoke([&](GLuint contents_texture_id, const GLfloat* contents_rect, |
| GLuint background_color, GLuint edge_aa_mask, |
| const GLfloat* bounds_rect, GLuint filter) { |
| // This is the child CompositorRenderPassDrawQuad. |
| EXPECT_EQ(1, bounds_rect[0]); |
| EXPECT_EQ(2, bounds_rect[1]); |
| sent_texture_ids[i] = contents_texture_id; |
| })); |
| DrawFrame(&renderer(), viewport_size); |
| Mock::VerifyAndClearExpectations(&gl()); |
| renderer().SwapBuffers(DirectRenderer::SwapFrameData()); |
| |
| // ScheduleCALayerCHROMIUM happened and used a non-0 texture. |
| EXPECT_NE(sent_texture_ids[i], 0u); |
| |
| // The damage was eliminated when everything was promoted to CALayers. |
| ASSERT_TRUE(output_surface().last_sent_frame()->sub_buffer_rect); |
| EXPECT_TRUE(output_surface().last_sent_frame()->sub_buffer_rect->IsEmpty()); |
| |
| // All sent textures will be checked to verify if they are free yet. |
| EXPECT_CALL(gl(), ScheduleCALayerInUseQueryCHROMIUM(i + 1, _)); |
| renderer().SwapBuffersComplete(/*release_fence=*/gfx::GpuFenceHandle()); |
| Mock::VerifyAndClearExpectations(&gl()); |
| } |
| |
| // Now all but 1 texture get returned by the OS, so they are all inserted |
| // into the cache for reuse. |
| std::vector<uint32_t> returned_texture_ids; |
| for (int i = 0; i < kNumSendManyTextureIds - 1; ++i) { |
| uint32_t id = sent_texture_ids[i]; |
| renderer().DidReceiveTextureInUseResponses({{id, false}}); |
| returned_texture_ids.push_back(id); |
| } |
| |
| // We generate a bunch of frames that don't use the cache, one less than the |
| // number of textures returned. |
| for (int i = 0; i < kNumSendManyTextureIds - 2; ++i) { |
| AggregatedRenderPass* root_pass = cc::AddRenderPass( |
| &render_passes_in_draw_order_, root_pass_id, gfx::Rect(viewport_size), |
| gfx::Transform(), cc::FilterOperations()); |
| cc::AddQuad(root_pass, gfx::Rect(100, 100), SK_ColorRED); |
| |
| renderer().DecideRenderPassAllocationsForFrame( |
| render_passes_in_draw_order_); |
| |
| InSequence sequence; |
| EXPECT_CALL(gl(), ScheduleCALayerSharedStateCHROMIUM(_, _, _, _, _, _)); |
| EXPECT_CALL(gl(), ScheduleCALayerCHROMIUM(_, _, _, _, _, _)); |
| DrawFrame(&renderer(), viewport_size); |
| Mock::VerifyAndClearExpectations(&gl()); |
| renderer().SwapBuffers(DirectRenderer::SwapFrameData()); |
| |
| // There's just 1 outstanding RenderPass texture to query for. |
| EXPECT_CALL(gl(), ScheduleCALayerInUseQueryCHROMIUM(1, _)); |
| renderer().SwapBuffersComplete(/*release_fence=*/gfx::GpuFenceHandle()); |
| Mock::VerifyAndClearExpectations(&gl()); |
| } |
| |
| // By now the cache should be empty, to show that we don't keep cached |
| // textures that won't be used forever. We generate a frame with a |
| // CompositorRenderPassDrawQuad and verify that it does not reuse a texture |
| // from the (empty) cache. |
| { |
| AggregatedRenderPass* child_pass = |
| cc::AddRenderPass(&render_passes_in_draw_order_, child_pass_id, |
| gfx::Rect(250, 251) + gfx::Vector2d(1, 2), |
| gfx::Transform(), cc::FilterOperations()); |
| AggregatedRenderPass* root_pass = cc::AddRenderPass( |
| &render_passes_in_draw_order_, root_pass_id, gfx::Rect(viewport_size), |
| gfx::Transform(), cc::FilterOperations()); |
| cc::AddRenderPassQuad(root_pass, child_pass, kInvalidResourceId, |
| gfx::Transform(), SkBlendMode::kSrcOver); |
| } |
| |
| renderer().DecideRenderPassAllocationsForFrame(render_passes_in_draw_order_); |
| |
| InSequence sequence; |
| EXPECT_CALL(gl(), ScheduleCALayerSharedStateCHROMIUM(_, _, _, _, _, _)); |
| EXPECT_CALL(gl(), ScheduleCALayerCHROMIUM(_, _, _, _, _, _)) |
| .WillOnce(Invoke([&](GLuint contents_texture_id, |
| const GLfloat* contents_rect, |
| GLuint background_color, GLuint edge_aa_mask, |
| const GLfloat* bounds_rect, GLuint filter) { |
| // This is the child CompositorRenderPassDrawQuad. |
| EXPECT_EQ(1, bounds_rect[0]); |
| EXPECT_EQ(2, bounds_rect[1]); |
| |
| // More textures were returned at once than we expect to reuse |
| // so eventually we should be making a new texture to show we're |
| // not just keeping infinity textures in the cache. |
| EXPECT_THAT(returned_texture_ids, Not(Contains(contents_texture_id))); |
| // This shows that there was some returned id that we didn't use. |
| EXPECT_FALSE(returned_texture_ids.empty()); |
| })); |
| DrawFrame(&renderer(), viewport_size); |
| Mock::VerifyAndClearExpectations(&gl()); |
| renderer().SwapBuffers(DirectRenderer::SwapFrameData()); |
| } |
| #endif |
| |
| class FramebufferWatchingGLRenderer : public FakeRendererGL { |
| public: |
| FramebufferWatchingGLRenderer(RendererSettings* settings, |
| const DebugRendererSettings* debug_settings, |
| OutputSurface* output_surface, |
| DisplayResourceProviderGL* resource_provider) |
| : FakeRendererGL(settings, |
| debug_settings, |
| output_surface, |
| resource_provider) {} |
| |
| void BindFramebufferToOutputSurface() override { |
| ++bind_root_framebuffer_calls_; |
| FakeRendererGL::BindFramebufferToOutputSurface(); |
| } |
| |
| void BindFramebufferToTexture( |
| const AggregatedRenderPassId render_pass_id) override { |
| ++bind_child_framebuffer_calls_; |
| FakeRendererGL::BindFramebufferToTexture(render_pass_id); |
| } |
| |
| int bind_root_framebuffer_calls() const { |
| return bind_root_framebuffer_calls_; |
| } |
| int bind_child_framebuffer_calls() const { |
| return bind_child_framebuffer_calls_; |
| } |
| |
| void ResetBindCalls() { |
| bind_root_framebuffer_calls_ = bind_child_framebuffer_calls_ = 0; |
| } |
| |
| private: |
| int bind_root_framebuffer_calls_ = 0; |
| int bind_child_framebuffer_calls_ = 0; |
| }; |
| |
| TEST_F(GLRendererTest, UndamagedRenderPassStillDrawnWhenNoPartialSwap) { |
| auto provider = TestContextProvider::Create(); |
| provider->UnboundTestContextGL()->set_have_post_sub_buffer(true); |
| provider->BindToCurrentThread(); |
| |
| cc::FakeOutputSurfaceClient output_surface_client; |
| auto output_surface = FakeOutputSurface::Create3d(std::move(provider)); |
| output_surface->BindToClient(&output_surface_client); |
| |
| auto resource_provider = std::make_unique<DisplayResourceProviderGL>( |
| output_surface->context_provider()); |
| |
| for (int i = 0; i < 2; ++i) { |
| bool use_partial_swap = i == 0; |
| SCOPED_TRACE(use_partial_swap); |
| |
| RendererSettings settings; |
| settings.partial_swap_enabled = use_partial_swap; |
| FramebufferWatchingGLRenderer renderer(&settings, &debug_settings_, |
| output_surface.get(), |
| resource_provider.get()); |
| renderer.Initialize(); |
| EXPECT_EQ(use_partial_swap, renderer.use_partial_swap()); |
| renderer.SetVisible(true); |
| |
| gfx::Size viewport_size(100, 100); |
| gfx::Rect child_rect(10, 10); |
| |
| // First frame, the child and root RenderPass each have damage. |
| AggregatedRenderPass* child_pass = cc::AddRenderPass( |
| &render_passes_in_draw_order_, AggregatedRenderPassId{2}, child_rect, |
| gfx::Transform(), cc::FilterOperations()); |
| cc::AddQuad(child_pass, child_rect, SK_ColorGREEN); |
| child_pass->damage_rect = child_rect; |
| |
| AggregatedRenderPass* root_pass = cc::AddRenderPass( |
| &render_passes_in_draw_order_, AggregatedRenderPassId{1}, |
| gfx::Rect(viewport_size), gfx::Transform(), cc::FilterOperations()); |
| cc::AddQuad(root_pass, gfx::Rect(viewport_size), SK_ColorRED); |
| cc::AddRenderPassQuad(root_pass, child_pass, kInvalidResourceId, |
| gfx::Transform(), SkBlendMode::kSrcOver); |
| root_pass->damage_rect = gfx::Rect(viewport_size); |
| |
| EXPECT_EQ(0, renderer.bind_root_framebuffer_calls()); |
| EXPECT_EQ(0, renderer.bind_child_framebuffer_calls()); |
| |
| renderer.DecideRenderPassAllocationsForFrame(render_passes_in_draw_order_); |
| DrawFrame(&renderer, viewport_size); |
| |
| // We had to draw the root, and the child. |
| EXPECT_EQ(1, renderer.bind_child_framebuffer_calls()); |
| // When the CompositorRenderPassDrawQuad in the root is drawn, we may |
| // re-bind the root framebuffer. So it can be bound more than once. |
| EXPECT_GE(renderer.bind_root_framebuffer_calls(), 1); |
| |
| // Reset counting. |
| renderer.ResetBindCalls(); |
| |
| // Second frame, the child RenderPass has no damage in it. |
| child_pass = cc::AddRenderPass(&render_passes_in_draw_order_, |
| AggregatedRenderPassId{2}, child_rect, |
| gfx::Transform(), cc::FilterOperations()); |
| cc::AddQuad(child_pass, child_rect, SK_ColorGREEN); |
| child_pass->damage_rect = gfx::Rect(); |
| |
| // Root RenderPass has some damage that doesn't intersect the child. |
| root_pass = cc::AddRenderPass( |
| &render_passes_in_draw_order_, AggregatedRenderPassId{1}, |
| gfx::Rect(viewport_size), gfx::Transform(), cc::FilterOperations()); |
| cc::AddQuad(root_pass, gfx::Rect(viewport_size), SK_ColorRED); |
| cc::AddRenderPassQuad(root_pass, child_pass, kInvalidResourceId, |
| gfx::Transform(), SkBlendMode::kSrcOver); |
| root_pass->damage_rect = gfx::Rect(child_rect.right(), 0, 10, 10); |
| |
| EXPECT_EQ(0, renderer.bind_root_framebuffer_calls()); |
| EXPECT_EQ(0, renderer.bind_child_framebuffer_calls()); |
| |
| renderer.DecideRenderPassAllocationsForFrame(render_passes_in_draw_order_); |
| DrawFrame(&renderer, viewport_size); |
| |
| if (use_partial_swap) { |
| // Without damage overlapping the child, it didn't need to be drawn (it |
| // may choose to anyway but that'd be a waste). So we don't check for |
| // |bind_child_framebuffer_calls|. But the root should have been drawn. |
| EXPECT_EQ(renderer.bind_root_framebuffer_calls(), 1); |
| } else { |
| // Without partial swap, we have to draw the child still, this means |
| // the child is bound as the framebuffer. |
| EXPECT_EQ(1, renderer.bind_child_framebuffer_calls()); |
| // When the CompositorRenderPassDrawQuad in the root is drawn, as it must |
| // be since we must draw the entire output, we may re-bind the root |
| // framebuffer. So it can be bound more than once. |
| EXPECT_GE(renderer.bind_root_framebuffer_calls(), 1); |
| } |
| } |
| } |
| |
| #if defined(USE_OZONE) || defined(OS_ANDROID) |
| class GLRendererWithGpuFenceTest : public GLRendererTest { |
| protected: |
| GLRendererWithGpuFenceTest() { |
| auto provider = TestContextProvider::Create(); |
| provider->BindToCurrentThread(); |
| provider->TestContextGL()->set_have_commit_overlay_planes(true); |
| test_context_support_ = provider->support(); |
| |
| output_surface_ = FakeOutputSurface::Create3d(std::move(provider)); |
| output_surface_->set_overlay_texture_id(kSurfaceOverlayTextureId); |
| output_surface_->set_gpu_fence_id(kGpuFenceId); |
| resource_provider_ = std::make_unique<DisplayResourceProviderGL>( |
| output_surface_->context_provider()); |
| overlay_processor_ = std::make_unique<SingleOverlayOnTopProcessor>(); |
| overlay_processor_->AllowMultipleCandidates(); |
| renderer_ = std::make_unique<FakeRendererGL>( |
| &settings_, &debug_settings_, output_surface_.get(), |
| resource_provider_.get(), overlay_processor_.get(), |
| base::ThreadTaskRunnerHandle::Get()); |
| renderer_->Initialize(); |
| renderer_->SetVisible(true); |
| |
| test_context_support_->SetScheduleOverlayPlaneCallback( |
| base::BindRepeating(&MockOverlayScheduler::Schedule, |
| base::Unretained(&overlay_scheduler_))); |
| } |
| |
| ~GLRendererWithGpuFenceTest() override { |
| if (child_resource_provider_) |
| child_resource_provider_->ShutdownAndReleaseAllResources(); |
| } |
| |
| ResourceId create_overlay_resource() { |
| child_context_provider_ = TestContextProvider::Create(); |
| child_context_provider_->BindToCurrentThread(); |
| |
| child_resource_provider_ = std::make_unique<ClientResourceProvider>(); |
| auto transfer_resource = TransferableResource::MakeGL( |
| gpu::Mailbox::Generate(), GL_LINEAR, GL_TEXTURE_2D, gpu::SyncToken(), |
| gfx::Size(256, 256), true); |
| ResourceId client_resource_id = child_resource_provider_->ImportResource( |
| transfer_resource, base::DoNothing()); |
| |
| std::unordered_map<ResourceId, ResourceId, ResourceIdHasher> resource_map = |
| cc::SendResourceAndGetChildToParentMap( |
| {client_resource_id}, resource_provider_.get(), |
| child_resource_provider_.get(), child_context_provider_.get()); |
| return resource_map[client_resource_id]; |
| } |
| |
| static constexpr unsigned kSurfaceOverlayTextureId = 33; |
| static constexpr unsigned kGpuFenceId = 66; |
| static constexpr unsigned kGpuNoFenceId = 0; |
| |
| TestContextSupport* test_context_support_; |
| |
| cc::FakeOutputSurfaceClient output_surface_client_; |
| std::unique_ptr<FakeOutputSurface> output_surface_; |
| std::unique_ptr<DisplayResourceProviderGL> resource_provider_; |
| scoped_refptr<TestContextProvider> child_context_provider_; |
| std::unique_ptr<ClientResourceProvider> child_resource_provider_; |
| RendererSettings settings_; |
| std::unique_ptr<SingleOverlayOnTopProcessor> overlay_processor_; |
| std::unique_ptr<FakeRendererGL> renderer_; |
| MockOverlayScheduler overlay_scheduler_; |
| }; |
| |
| TEST_F(GLRendererWithGpuFenceTest, GpuFenceIdIsUsedWithRootRenderPassOverlay) { |
| gfx::Size viewport_size(100, 100); |
| AggregatedRenderPass* root_pass = cc::AddRenderPass( |
| &render_passes_in_draw_order_, AggregatedRenderPassId{1}, |
| gfx::Rect(viewport_size), gfx::Transform(), cc::FilterOperations()); |
| root_pass->has_transparent_background = false; |
| |
| EXPECT_CALL(overlay_scheduler_, |
| Schedule(0, gfx::OVERLAY_TRANSFORM_NONE, kSurfaceOverlayTextureId, |
| gfx::Rect(viewport_size), _, _, kGpuFenceId)) |
| .Times(1); |
| DrawFrame(renderer_.get(), viewport_size); |
| } |
| |
| TEST_F(GLRendererWithGpuFenceTest, |
| GpuFenceIdIsUsedOnlyForRootRenderPassOverlay) { |
| #if defined(USE_X11) |
| // TODO(1096425): Remove this. |
| if (!features::IsUsingOzonePlatform()) |
| GTEST_SKIP(); |
| #endif |
| gfx::Size viewport_size(100, 100); |
| AggregatedRenderPass* root_pass = cc::AddRenderPass( |
| &render_passes_in_draw_order_, AggregatedRenderPassId{1}, |
| gfx::Rect(viewport_size), gfx::Transform(), cc::FilterOperations()); |
| root_pass->has_transparent_background = false; |
| |
| bool needs_blending = false; |
| bool premultiplied_alpha = false; |
| bool flipped = false; |
| bool nearest_neighbor = false; |
| float vertex_opacity[4] = {1.0f, 1.0f, 1.0f, 1.0f}; |
| gfx::PointF uv_top_left(0, 0); |
| gfx::PointF uv_bottom_right(1, 1); |
| |
| TextureDrawQuad* overlay_quad = |
| root_pass->CreateAndAppendDrawQuad<TextureDrawQuad>(); |
| SharedQuadState* shared_state = root_pass->CreateAndAppendSharedQuadState(); |
| shared_state->SetAll(gfx::Transform(), gfx::Rect(viewport_size), |
| gfx::Rect(50, 50), gfx::MaskFilterInfo(), absl::nullopt, |
| false, 1, SkBlendMode::kSrcOver, 0); |
| overlay_quad->SetNew( |
| shared_state, gfx::Rect(viewport_size), gfx::Rect(viewport_size), |
| needs_blending, create_overlay_resource(), premultiplied_alpha, |
| uv_top_left, uv_bottom_right, SK_ColorTRANSPARENT, vertex_opacity, |
| flipped, nearest_neighbor, |
| /*secure_output_only=*/false, gfx::ProtectedVideoType::kClear); |
| |
| EXPECT_CALL(overlay_scheduler_, |
| Schedule(0, gfx::OVERLAY_TRANSFORM_NONE, kSurfaceOverlayTextureId, |
| gfx::Rect(viewport_size), _, _, kGpuFenceId)) |
| .Times(1); |
| EXPECT_CALL(overlay_scheduler_, |
| Schedule(1, gfx::OVERLAY_TRANSFORM_NONE, _, |
| gfx::Rect(viewport_size), _, _, kGpuNoFenceId)) |
| .Times(1); |
| DrawFrame(renderer_.get(), viewport_size); |
| } |
| #endif // defined(USE_OZONE) || defined(OS_ANDROID) |
| |
| } // namespace |
| } // namespace viz |